-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Small UI changes to forms before 1.4.0 (#214)
- Loading branch information
Showing
7 changed files
with
859 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,268 @@ | ||
/** Tagulous adaptor for Select2 v3 */ | ||
(function ($) { | ||
if (!window.Select2 || !window.Tagulous) { | ||
return; | ||
} | ||
|
||
/** Select2 monkeypatching | ||
Adds support for new option, quotedTags | ||
This is used by tagulous to add quote support to the tag parser, | ||
without affecting other use of select2 on the page. | ||
*/ | ||
var MultiSelect2 = Select2['class'].multi, | ||
oldGetVal = MultiSelect2.prototype.getVal, | ||
oldSetVal = MultiSelect2.prototype.setVal | ||
; | ||
|
||
MultiSelect2.prototype.getVal = function () { | ||
/** Parse tag string into tags */ | ||
if (this.select || !this.opts.quotedTags) { | ||
return oldGetVal.call(this); | ||
} | ||
return Tagulous.parseTags( | ||
this.opts.element.val(), this.opts.spaceDelimiter | ||
); | ||
}; | ||
MultiSelect2.prototype.setVal = function (val) { | ||
/** Join tags into a string */ | ||
if (this.select || !this.opts.quotedTags) { | ||
return oldSetVal.call(this, val); | ||
} | ||
|
||
var str = Tagulous.renderTags(val); | ||
this.opts.element.val(str); | ||
}; | ||
|
||
/** Select2 option functions | ||
These replace default options with quote-aware ones | ||
*/ | ||
function initSelectionSingle(element, callback) { | ||
var val = element.val(); | ||
callback({id: val, text: val}); | ||
} | ||
function initSelectionMulti_factory(opts) { | ||
/** initSelection has no way to get options, so need to use closure */ | ||
return function (element, callback) { | ||
/** Initialises selection for fields with multiple tags */ | ||
var tags = Tagulous.parseTags(element.val(), opts.spaceDelimiter), | ||
data = [], | ||
i | ||
; | ||
for (i=0; i<tags.length; i++) { | ||
data.push({id: tags[i], text: tags[i]}); | ||
} | ||
callback(data); | ||
}; | ||
} | ||
|
||
function tokenizer(input, selection, selectCallback, opts) { | ||
/** Tokenises input and detects when a tag has been completed */ | ||
if (!this.opts.quotedTags) { | ||
return $.fn.select2.defaults.tokenizer.call( | ||
this, input, selection, selectCallback, opts | ||
); | ||
} | ||
|
||
// Still need to be able to create search options | ||
if (!opts.createSearchChoice) return undefined; | ||
|
||
// Parse with raw | ||
var parsed = Tagulous.parseTags(input, opts.spaceDelimiter, true), | ||
tags = parsed[0], | ||
raws = parsed[1], | ||
lastRaw = raws.slice(-1)[0], | ||
i, j, token | ||
; | ||
|
||
if (!tags.length) { | ||
return input; | ||
} | ||
|
||
// Check for incomplete partial tag | ||
// If more than one tag then raw was pasted - assume all complete | ||
if (lastRaw === null && tags.length < 2) { | ||
// Last tag wasn't completed - return it to input | ||
tags.pop(); | ||
raws.pop(); | ||
lastRaw = raws.slice(-1)[0]; | ||
} | ||
|
||
for (i=0; i<tags.length; i++) { | ||
token = opts.createSearchChoice.call(this, tags[i], selection); | ||
|
||
// De-dupe using select2 logic (without equal call) | ||
if (token !== undefined && token !== null && opts.id(token) !== undefined && opts.id(token) !== null) { | ||
dupe = false; | ||
for (j = 0, l = selection.length; j < l; j++) { | ||
if (opts.id(token) === opts.id(selection[j])) { | ||
dupe = true; break; | ||
} | ||
} | ||
|
||
if (!dupe) selectCallback(token); | ||
} | ||
// End select2 dedupe logic | ||
} | ||
|
||
// Return whatever was left after the last completed tag was parsed | ||
return (lastRaw === undefined) ? input : lastRaw; | ||
} | ||
|
||
function createSearchChoice(term, data) { | ||
/** Creates quote-aware search options from new input | ||
Also makes SingleTagField look like a select field | ||
*/ | ||
// Thanks to https://github.com/select2/select2/issues/521 | ||
if (this.opts.multiple && this.opts.quotedTags) { | ||
var tags = Tagulous.parseTags(term, this.opts.spaceDelimiter); | ||
if (tags.length == 1) { | ||
term = tags[0]; | ||
} | ||
} | ||
if ($(data).filter( | ||
function () { | ||
return this.text.localeCompare(term) === 0; | ||
}).length === 0 | ||
) { | ||
return {id:term, text:term}; | ||
} | ||
} | ||
|
||
/** Apply select2 to a specified element | ||
Arguments: | ||
el The DOM or jQuery object to use as the tag element | ||
canDefer If true and tag-options.defer is set, this field | ||
will not be initialised. | ||
*/ | ||
function apply_select2(el, canDefer) { | ||
// Convert element to jQuery object (if it isn't already) | ||
var $el = $(el), | ||
thisTagField = this, | ||
|
||
// Get info from element | ||
isSingle = $el.data('tag-type') === "single", | ||
options = $el.data('tag-options') || {}, | ||
settings = options.autocomplete_settings || {}, | ||
list = $el.data('tag-list'), | ||
url = $el.data('tag-url'), | ||
|
||
// Other values | ||
$blank, args, field_args | ||
; | ||
|
||
// See if this is a deferred tag | ||
if (canDefer && settings.defer) { | ||
return $el; | ||
} | ||
delete settings.defer; | ||
|
||
// Clear out first option if it's Django's blank value | ||
$blank = $el | ||
.find('option:first[value=""]:contains("---------")') | ||
.text('') | ||
; | ||
|
||
// Default constructor args, which can be overridden | ||
args = { | ||
width: 'resolve' | ||
}; | ||
|
||
// Merge in any overrides | ||
field_args = settings; | ||
if (field_args) { | ||
$.extend(args, field_args); | ||
} | ||
|
||
// Merge in common compulsory settings | ||
$.extend(args, { | ||
// Our overriden methods | ||
tokenizer: tokenizer, | ||
createSearchChoice: createSearchChoice, | ||
|
||
// Things defined by field/tag options, which can't be overridden | ||
multiple: !isSingle, | ||
quotedTags: true, | ||
allowClear: !options.required, | ||
maximumSelectionSize: isSingle ? 1 : options.max_count || 0, | ||
spaceDelimiter: options.space_delimiter !== false | ||
}); | ||
|
||
// Add in any specific to the field type | ||
if (isSingle) { | ||
args['initSelection'] = initSelectionSingle; | ||
} else { | ||
args['initSelection'] = initSelectionMulti_factory(args); | ||
} | ||
if (url) { | ||
args['ajax'] = { | ||
url: url, | ||
dataType: 'json', | ||
data: function (term, page) { | ||
return {q:term, p:page}; | ||
}, | ||
results: function (data) { | ||
data['results'] = listToData(data['results']); | ||
return data; | ||
} | ||
}; | ||
|
||
// Merge in override ajax values | ||
if (field_args && field_args.ajax) { | ||
$.extend(args['ajax'], field_args.ajax); | ||
} | ||
|
||
} else if (isSingle) { | ||
// Make SingleTagField look like a select, set data not tags | ||
args['data'] = listToData(list); | ||
|
||
} else { | ||
// Multiple tags, normal tags mode appropriate | ||
args['tags'] = list || []; | ||
} | ||
|
||
// Initialise | ||
return $el.select2(args); | ||
} | ||
|
||
/** Select2 initialiser | ||
This initialises select2 on this | ||
Arguments: | ||
$el The jQuery object to use as the tag selector | ||
canDefer If true and tag-options.defer is set, this field | ||
will not be initialised. | ||
*/ | ||
function select2($el, canDefer) { | ||
return $el.each(function () { | ||
apply_select2(this, canDefer); | ||
}); | ||
} | ||
|
||
function listToData(list) { | ||
/** Convert a list of tags into an object with tag:tag key/vals */ | ||
var data = [], i; | ||
if (!list) { | ||
return data; | ||
} | ||
for (i=0; i<list.length; i++) { | ||
data.push({id:list[i], text:list[i]}); | ||
} | ||
return data; | ||
} | ||
|
||
// Make functions public | ||
$.extend(Tagulous, { | ||
select2: select2 | ||
}); | ||
|
||
// Finally, initialise the tags | ||
$(function () { | ||
// Initialise tag fields which exists | ||
return select2($('input[data-tagulous]'), true); | ||
}); | ||
})(jQuery); |
Oops, something went wrong.