-
Notifications
You must be signed in to change notification settings - Fork 65
/
select2-3.js
268 lines (230 loc) · 8.29 KB
/
select2-3.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
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);