Commit 437e945c67eaf2fa915ef5e92602beca2f65f68c
1 parent
9d0c2c87
Exists in
master
and in
3 other branches
tags input working but still have to fix that they get the value from value prop…
…erty instead of text, and fixed pagination amount
Showing
4 changed files
with
721 additions
and
1 deletions
Show diff stats
... | ... | @@ -0,0 +1,55 @@ |
1 | +.bootstrap-tagsinput { | |
2 | + background-color: #fff; | |
3 | + border: 1px solid #ccc; | |
4 | + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); | |
5 | + display: inline-block; | |
6 | + padding: 4px 6px; | |
7 | + color: #555; | |
8 | + vertical-align: middle; | |
9 | + border-radius: 4px; | |
10 | + max-width: 100%; | |
11 | + line-height: 22px; | |
12 | + cursor: text; | |
13 | +} | |
14 | +.bootstrap-tagsinput input { | |
15 | + border: none; | |
16 | + box-shadow: none; | |
17 | + outline: none; | |
18 | + background-color: transparent; | |
19 | + padding: 0 6px; | |
20 | + margin: 0; | |
21 | + width: auto; | |
22 | + max-width: inherit; | |
23 | +} | |
24 | +.bootstrap-tagsinput.form-control input::-moz-placeholder { | |
25 | + color: #777; | |
26 | + opacity: 1; | |
27 | +} | |
28 | +.bootstrap-tagsinput.form-control input:-ms-input-placeholder { | |
29 | + color: #777; | |
30 | +} | |
31 | +.bootstrap-tagsinput.form-control input::-webkit-input-placeholder { | |
32 | + color: #777; | |
33 | +} | |
34 | +.bootstrap-tagsinput input:focus { | |
35 | + border: none; | |
36 | + box-shadow: none; | |
37 | +} | |
38 | +.bootstrap-tagsinput .tag { | |
39 | + margin-right: 2px; | |
40 | + color: white; | |
41 | +} | |
42 | +.bootstrap-tagsinput .tag [data-role="remove"] { | |
43 | + margin-left: 8px; | |
44 | + cursor: pointer; | |
45 | +} | |
46 | +.bootstrap-tagsinput .tag [data-role="remove"]:after { | |
47 | + content: "x"; | |
48 | + padding: 0px 2px; | |
49 | +} | |
50 | +.bootstrap-tagsinput .tag [data-role="remove"]:hover { | |
51 | + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); | |
52 | +} | |
53 | +.bootstrap-tagsinput .tag [data-role="remove"]:hover:active { | |
54 | + box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); | |
55 | +} | ... | ... |
... | ... | @@ -0,0 +1,646 @@ |
1 | +(function ($) { | |
2 | + "use strict"; | |
3 | + | |
4 | + var defaultOptions = { | |
5 | + tagClass: function(item) { | |
6 | + return 'label label-info'; | |
7 | + }, | |
8 | + itemValue: function(item) { | |
9 | + return item ? item.toString() : item; | |
10 | + }, | |
11 | + itemText: function(item) { | |
12 | + return this.itemValue(item); | |
13 | + }, | |
14 | + itemTitle: function(item) { | |
15 | + return null; | |
16 | + }, | |
17 | + freeInput: true, | |
18 | + addOnBlur: true, | |
19 | + maxTags: undefined, | |
20 | + maxChars: undefined, | |
21 | + confirmKeys: [13, 44], | |
22 | + delimiter: ',', | |
23 | + delimiterRegex: null, | |
24 | + cancelConfirmKeysOnEmpty: true, | |
25 | + onTagExists: function(item, $tag) { | |
26 | + $tag.hide().fadeIn(); | |
27 | + }, | |
28 | + trimValue: false, | |
29 | + allowDuplicates: false | |
30 | + }; | |
31 | + | |
32 | + /** | |
33 | + * Constructor function | |
34 | + */ | |
35 | + function TagsInput(element, options) { | |
36 | + this.itemsArray = []; | |
37 | + | |
38 | + this.$element = $(element); | |
39 | + this.$element.hide(); | |
40 | + | |
41 | + this.isSelect = (element.tagName === 'SELECT'); | |
42 | + this.multiple = (this.isSelect && element.hasAttribute('multiple')); | |
43 | + this.objectItems = options && options.itemValue; | |
44 | + this.placeholderText = element.hasAttribute('placeholder') ? this.$element.attr('placeholder') : ''; | |
45 | + this.inputSize = Math.max(1, this.placeholderText.length); | |
46 | + | |
47 | + this.$container = $('<div class="bootstrap-tagsinput"></div>'); | |
48 | + this.$input = $('<input type="text" placeholder="' + this.placeholderText + '"/>').appendTo(this.$container); | |
49 | + | |
50 | + this.$element.before(this.$container); | |
51 | + | |
52 | + this.build(options); | |
53 | + } | |
54 | + | |
55 | + TagsInput.prototype = { | |
56 | + constructor: TagsInput, | |
57 | + | |
58 | + /** | |
59 | + * Adds the given item as a new tag. Pass true to dontPushVal to prevent | |
60 | + * updating the elements val() | |
61 | + */ | |
62 | + add: function(item, dontPushVal, options) { | |
63 | + var self = this; | |
64 | + | |
65 | + if (self.options.maxTags && self.itemsArray.length >= self.options.maxTags) | |
66 | + return; | |
67 | + | |
68 | + // Ignore falsey values, except false | |
69 | + if (item !== false && !item) | |
70 | + return; | |
71 | + | |
72 | + // Trim value | |
73 | + if (typeof item === "string" && self.options.trimValue) { | |
74 | + item = $.trim(item); | |
75 | + } | |
76 | + | |
77 | + // Throw an error when trying to add an object while the itemValue option was not set | |
78 | + if (typeof item === "object" && !self.objectItems) | |
79 | + throw("Can't add objects when itemValue option is not set"); | |
80 | + | |
81 | + // Ignore strings only containg whitespace | |
82 | + if (item.toString().match(/^\s*$/)) | |
83 | + return; | |
84 | + | |
85 | + // If SELECT but not multiple, remove current tag | |
86 | + if (self.isSelect && !self.multiple && self.itemsArray.length > 0) | |
87 | + self.remove(self.itemsArray[0]); | |
88 | + | |
89 | + if (typeof item === "string" && this.$element[0].tagName === 'INPUT') { | |
90 | + var delimiter = (self.options.delimiterRegex) ? self.options.delimiterRegex : self.options.delimiter; | |
91 | + var items = item.split(delimiter); | |
92 | + if (items.length > 1) { | |
93 | + for (var i = 0; i < items.length; i++) { | |
94 | + this.add(items[i], true); | |
95 | + } | |
96 | + | |
97 | + if (!dontPushVal) | |
98 | + self.pushVal(); | |
99 | + return; | |
100 | + } | |
101 | + } | |
102 | + | |
103 | + var itemValue = self.options.itemValue(item), | |
104 | + itemText = self.options.itemText(item), | |
105 | + tagClass = self.options.tagClass(item), | |
106 | + itemTitle = self.options.itemTitle(item); | |
107 | + | |
108 | + // Ignore items allready added | |
109 | + var existing = $.grep(self.itemsArray, function(item) { return self.options.itemValue(item) === itemValue; } )[0]; | |
110 | + if (existing && !self.options.allowDuplicates) { | |
111 | + // Invoke onTagExists | |
112 | + if (self.options.onTagExists) { | |
113 | + var $existingTag = $(".tag", self.$container).filter(function() { return $(this).data("item") === existing; }); | |
114 | + self.options.onTagExists(item, $existingTag); | |
115 | + } | |
116 | + return; | |
117 | + } | |
118 | + | |
119 | + // if length greater than limit | |
120 | + if (self.items().toString().length + item.length + 1 > self.options.maxInputLength) | |
121 | + return; | |
122 | + | |
123 | + // raise beforeItemAdd arg | |
124 | + var beforeItemAddEvent = $.Event('beforeItemAdd', { item: item, cancel: false, options: options}); | |
125 | + self.$element.trigger(beforeItemAddEvent); | |
126 | + if (beforeItemAddEvent.cancel) | |
127 | + return; | |
128 | + | |
129 | + // register item in internal array and map | |
130 | + self.itemsArray.push(item); | |
131 | + | |
132 | + // add a tag element | |
133 | + | |
134 | + var $tag = $('<span class="tag ' + htmlEncode(tagClass) + (itemTitle !== null ? ('" title="' + itemTitle) : '') + '">' + htmlEncode(itemText) + '<span data-role="remove"></span></span>'); | |
135 | + $tag.data('item', item); | |
136 | + self.findInputWrapper().before($tag); | |
137 | + $tag.after(' '); | |
138 | + | |
139 | + // add <option /> if item represents a value not present in one of the <select />'s options | |
140 | + if (self.isSelect && !$('option[value="' + encodeURIComponent(itemValue) + '"]',self.$element)[0]) { | |
141 | + var $option = $('<option selected>' + htmlEncode(itemText) + '</option>'); | |
142 | + $option.data('item', item); | |
143 | + $option.attr('value', itemValue); | |
144 | + self.$element.append($option); | |
145 | + } | |
146 | + | |
147 | + if (!dontPushVal) | |
148 | + self.pushVal(); | |
149 | + | |
150 | + // Add class when reached maxTags | |
151 | + if (self.options.maxTags === self.itemsArray.length || self.items().toString().length === self.options.maxInputLength) | |
152 | + self.$container.addClass('bootstrap-tagsinput-max'); | |
153 | + | |
154 | + self.$element.trigger($.Event('itemAdded', { item: item, options: options })); | |
155 | + }, | |
156 | + | |
157 | + /** | |
158 | + * Removes the given item. Pass true to dontPushVal to prevent updating the | |
159 | + * elements val() | |
160 | + */ | |
161 | + remove: function(item, dontPushVal, options) { | |
162 | + var self = this; | |
163 | + | |
164 | + if (self.objectItems) { | |
165 | + if (typeof item === "object") | |
166 | + item = $.grep(self.itemsArray, function(other) { return self.options.itemValue(other) == self.options.itemValue(item); } ); | |
167 | + else | |
168 | + item = $.grep(self.itemsArray, function(other) { return self.options.itemValue(other) == item; } ); | |
169 | + | |
170 | + item = item[item.length-1]; | |
171 | + } | |
172 | + | |
173 | + if (item) { | |
174 | + var beforeItemRemoveEvent = $.Event('beforeItemRemove', { item: item, cancel: false, options: options }); | |
175 | + self.$element.trigger(beforeItemRemoveEvent); | |
176 | + if (beforeItemRemoveEvent.cancel) | |
177 | + return; | |
178 | + | |
179 | + $('.tag', self.$container).filter(function() { return $(this).data('item') === item; }).remove(); | |
180 | + $('option', self.$element).filter(function() { return $(this).data('item') === item; }).remove(); | |
181 | + if($.inArray(item, self.itemsArray) !== -1) | |
182 | + self.itemsArray.splice($.inArray(item, self.itemsArray), 1); | |
183 | + } | |
184 | + | |
185 | + if (!dontPushVal) | |
186 | + self.pushVal(); | |
187 | + | |
188 | + // Remove class when reached maxTags | |
189 | + if (self.options.maxTags > self.itemsArray.length) | |
190 | + self.$container.removeClass('bootstrap-tagsinput-max'); | |
191 | + | |
192 | + self.$element.trigger($.Event('itemRemoved', { item: item, options: options })); | |
193 | + }, | |
194 | + | |
195 | + /** | |
196 | + * Removes all items | |
197 | + */ | |
198 | + removeAll: function() { | |
199 | + var self = this; | |
200 | + | |
201 | + $('.tag', self.$container).remove(); | |
202 | + $('option', self.$element).remove(); | |
203 | + | |
204 | + while(self.itemsArray.length > 0) | |
205 | + self.itemsArray.pop(); | |
206 | + | |
207 | + self.pushVal(); | |
208 | + }, | |
209 | + | |
210 | + /** | |
211 | + * Refreshes the tags so they match the text/value of their corresponding | |
212 | + * item. | |
213 | + */ | |
214 | + refresh: function() { | |
215 | + var self = this; | |
216 | + $('.tag', self.$container).each(function() { | |
217 | + var $tag = $(this), | |
218 | + item = $tag.data('item'), | |
219 | + itemValue = self.options.itemValue(item), | |
220 | + itemText = self.options.itemText(item), | |
221 | + tagClass = self.options.tagClass(item); | |
222 | + | |
223 | + // Update tag's class and inner text | |
224 | + $tag.attr('class', null); | |
225 | + $tag.addClass('tag ' + htmlEncode(tagClass)); | |
226 | + $tag.contents().filter(function() { | |
227 | + return this.nodeType == 3; | |
228 | + })[0].nodeValue = htmlEncode(itemText); | |
229 | + | |
230 | + if (self.isSelect) { | |
231 | + var option = $('option', self.$element).filter(function() { return $(this).data('item') === item; }); | |
232 | + option.attr('value', itemValue); | |
233 | + } | |
234 | + }); | |
235 | + }, | |
236 | + | |
237 | + /** | |
238 | + * Returns the items added as tags | |
239 | + */ | |
240 | + items: function() { | |
241 | + return this.itemsArray; | |
242 | + }, | |
243 | + | |
244 | + /** | |
245 | + * Assembly value by retrieving the value of each item, and set it on the | |
246 | + * element. | |
247 | + */ | |
248 | + pushVal: function() { | |
249 | + var self = this, | |
250 | + val = $.map(self.items(), function(item) { | |
251 | + return self.options.itemValue(item).toString(); | |
252 | + }); | |
253 | + | |
254 | + self.$element.val(val, true).trigger('change'); | |
255 | + }, | |
256 | + | |
257 | + /** | |
258 | + * Initializes the tags input behaviour on the element | |
259 | + */ | |
260 | + build: function(options) { | |
261 | + var self = this; | |
262 | + | |
263 | + self.options = $.extend({}, defaultOptions, options); | |
264 | + // When itemValue is set, freeInput should always be false | |
265 | + if (self.objectItems) | |
266 | + self.options.freeInput = false; | |
267 | + | |
268 | + makeOptionItemFunction(self.options, 'itemValue'); | |
269 | + makeOptionItemFunction(self.options, 'itemText'); | |
270 | + makeOptionFunction(self.options, 'tagClass'); | |
271 | + | |
272 | + // Typeahead Bootstrap version 2.3.2 | |
273 | + if (self.options.typeahead) { | |
274 | + var typeahead = self.options.typeahead || {}; | |
275 | + | |
276 | + makeOptionFunction(typeahead, 'source'); | |
277 | + | |
278 | + self.$input.typeahead($.extend({}, typeahead, { | |
279 | + source: function (query, process) { | |
280 | + function processItems(items) { | |
281 | + var texts = []; | |
282 | + | |
283 | + for (var i = 0; i < items.length; i++) { | |
284 | + var text = self.options.itemText(items[i]); | |
285 | + map[text] = items[i]; | |
286 | + texts.push(text); | |
287 | + } | |
288 | + process(texts); | |
289 | + } | |
290 | + | |
291 | + this.map = {}; | |
292 | + var map = this.map, | |
293 | + data = typeahead.source(query); | |
294 | + | |
295 | + if ($.isFunction(data.success)) { | |
296 | + // support for Angular callbacks | |
297 | + data.success(processItems); | |
298 | + } else if ($.isFunction(data.then)) { | |
299 | + // support for Angular promises | |
300 | + data.then(processItems); | |
301 | + } else { | |
302 | + // support for functions and jquery promises | |
303 | + $.when(data) | |
304 | + .then(processItems); | |
305 | + } | |
306 | + }, | |
307 | + updater: function (text) { | |
308 | + self.add(this.map[text]); | |
309 | + return this.map[text]; | |
310 | + }, | |
311 | + matcher: function (text) { | |
312 | + return (text.toLowerCase().indexOf(this.query.trim().toLowerCase()) !== -1); | |
313 | + }, | |
314 | + sorter: function (texts) { | |
315 | + return texts.sort(); | |
316 | + }, | |
317 | + highlighter: function (text) { | |
318 | + var regex = new RegExp( '(' + this.query + ')', 'gi' ); | |
319 | + return text.replace( regex, "<strong>$1</strong>" ); | |
320 | + } | |
321 | + })); | |
322 | + } | |
323 | + | |
324 | + // typeahead.js | |
325 | + if (self.options.typeaheadjs) { | |
326 | + var typeaheadConfig = null; | |
327 | + var typeaheadDatasets = {}; | |
328 | + | |
329 | + // Determine if main configurations were passed or simply a dataset | |
330 | + var typeaheadjs = self.options.typeaheadjs; | |
331 | + if ($.isArray(typeaheadjs)) { | |
332 | + typeaheadConfig = typeaheadjs[0]; | |
333 | + typeaheadDatasets = typeaheadjs[1]; | |
334 | + } else { | |
335 | + typeaheadDatasets = typeaheadjs; | |
336 | + } | |
337 | + | |
338 | + self.$input.typeahead(typeaheadConfig, typeaheadDatasets).on('typeahead:selected', $.proxy(function (obj, datum) { | |
339 | + if (typeaheadDatasets.valueKey) | |
340 | + self.add(datum[typeaheadDatasets.valueKey]); | |
341 | + else | |
342 | + self.add(datum); | |
343 | + self.$input.typeahead('val', ''); | |
344 | + }, self)); | |
345 | + } | |
346 | + | |
347 | + self.$container.on('click', $.proxy(function(event) { | |
348 | + if (! self.$element.attr('disabled')) { | |
349 | + self.$input.removeAttr('disabled'); | |
350 | + } | |
351 | + self.$input.focus(); | |
352 | + }, self)); | |
353 | + | |
354 | + if (self.options.addOnBlur && self.options.freeInput) { | |
355 | + self.$input.on('focusout', $.proxy(function(event) { | |
356 | + // HACK: only process on focusout when no typeahead opened, to | |
357 | + // avoid adding the typeahead text as tag | |
358 | + if ($('.typeahead, .twitter-typeahead', self.$container).length === 0) { | |
359 | + self.add(self.$input.val()); | |
360 | + self.$input.val(''); | |
361 | + } | |
362 | + }, self)); | |
363 | + } | |
364 | + | |
365 | + | |
366 | + self.$container.on('keydown', 'input', $.proxy(function(event) { | |
367 | + var $input = $(event.target), | |
368 | + $inputWrapper = self.findInputWrapper(); | |
369 | + | |
370 | + if (self.$element.attr('disabled')) { | |
371 | + self.$input.attr('disabled', 'disabled'); | |
372 | + return; | |
373 | + } | |
374 | + | |
375 | + switch (event.which) { | |
376 | + // BACKSPACE | |
377 | + case 8: | |
378 | + if (doGetCaretPosition($input[0]) === 0) { | |
379 | + var prev = $inputWrapper.prev(); | |
380 | + if (prev.length) { | |
381 | + self.remove(prev.data('item')); | |
382 | + } | |
383 | + } | |
384 | + break; | |
385 | + | |
386 | + // DELETE | |
387 | + case 46: | |
388 | + if (doGetCaretPosition($input[0]) === 0) { | |
389 | + var next = $inputWrapper.next(); | |
390 | + if (next.length) { | |
391 | + self.remove(next.data('item')); | |
392 | + } | |
393 | + } | |
394 | + break; | |
395 | + | |
396 | + // LEFT ARROW | |
397 | + case 37: | |
398 | + // Try to move the input before the previous tag | |
399 | + var $prevTag = $inputWrapper.prev(); | |
400 | + if ($input.val().length === 0 && $prevTag[0]) { | |
401 | + $prevTag.before($inputWrapper); | |
402 | + $input.focus(); | |
403 | + } | |
404 | + break; | |
405 | + // RIGHT ARROW | |
406 | + case 39: | |
407 | + // Try to move the input after the next tag | |
408 | + var $nextTag = $inputWrapper.next(); | |
409 | + if ($input.val().length === 0 && $nextTag[0]) { | |
410 | + $nextTag.after($inputWrapper); | |
411 | + $input.focus(); | |
412 | + } | |
413 | + break; | |
414 | + default: | |
415 | + // ignore | |
416 | + } | |
417 | + | |
418 | + // Reset internal input's size | |
419 | + var textLength = $input.val().length, | |
420 | + wordSpace = Math.ceil(textLength / 5), | |
421 | + size = textLength + wordSpace + 1; | |
422 | + $input.attr('size', Math.max(this.inputSize, $input.val().length)); | |
423 | + }, self)); | |
424 | + | |
425 | + self.$container.on('keypress', 'input', $.proxy(function(event) { | |
426 | + var $input = $(event.target); | |
427 | + | |
428 | + if (self.$element.attr('disabled')) { | |
429 | + self.$input.attr('disabled', 'disabled'); | |
430 | + return; | |
431 | + } | |
432 | + | |
433 | + var text = $input.val(), | |
434 | + maxLengthReached = self.options.maxChars && text.length >= self.options.maxChars; | |
435 | + if (self.options.freeInput && (keyCombinationInList(event, self.options.confirmKeys) || maxLengthReached)) { | |
436 | + // Only attempt to add a tag if there is data in the field | |
437 | + if (text.length !== 0) { | |
438 | + self.add(maxLengthReached ? text.substr(0, self.options.maxChars) : text); | |
439 | + $input.val(''); | |
440 | + } | |
441 | + | |
442 | + // If the field is empty, let the event triggered fire as usual | |
443 | + if (self.options.cancelConfirmKeysOnEmpty === false) { | |
444 | + event.preventDefault(); | |
445 | + } | |
446 | + } | |
447 | + | |
448 | + // Reset internal input's size | |
449 | + var textLength = $input.val().length, | |
450 | + wordSpace = Math.ceil(textLength / 5), | |
451 | + size = textLength + wordSpace + 1; | |
452 | + $input.attr('size', Math.max(this.inputSize, $input.val().length)); | |
453 | + }, self)); | |
454 | + | |
455 | + // Remove icon clicked | |
456 | + self.$container.on('click', '[data-role=remove]', $.proxy(function(event) { | |
457 | + if (self.$element.attr('disabled')) { | |
458 | + return; | |
459 | + } | |
460 | + self.remove($(event.target).closest('.tag').data('item')); | |
461 | + }, self)); | |
462 | + | |
463 | + // Only add existing value as tags when using strings as tags | |
464 | + if (self.options.itemValue === defaultOptions.itemValue) { | |
465 | + if (self.$element[0].tagName === 'INPUT') { | |
466 | + self.add(self.$element.val()); | |
467 | + } else { | |
468 | + $('option', self.$element).each(function() { | |
469 | + self.add($(this).attr('value'), true); | |
470 | + }); | |
471 | + } | |
472 | + } | |
473 | + }, | |
474 | + | |
475 | + /** | |
476 | + * Removes all tagsinput behaviour and unregsiter all event handlers | |
477 | + */ | |
478 | + destroy: function() { | |
479 | + var self = this; | |
480 | + | |
481 | + // Unbind events | |
482 | + self.$container.off('keypress', 'input'); | |
483 | + self.$container.off('click', '[role=remove]'); | |
484 | + | |
485 | + self.$container.remove(); | |
486 | + self.$element.removeData('tagsinput'); | |
487 | + self.$element.show(); | |
488 | + }, | |
489 | + | |
490 | + /** | |
491 | + * Sets focus on the tagsinput | |
492 | + */ | |
493 | + focus: function() { | |
494 | + this.$input.focus(); | |
495 | + }, | |
496 | + | |
497 | + /** | |
498 | + * Returns the internal input element | |
499 | + */ | |
500 | + input: function() { | |
501 | + return this.$input; | |
502 | + }, | |
503 | + | |
504 | + /** | |
505 | + * Returns the element which is wrapped around the internal input. This | |
506 | + * is normally the $container, but typeahead.js moves the $input element. | |
507 | + */ | |
508 | + findInputWrapper: function() { | |
509 | + var elt = this.$input[0], | |
510 | + container = this.$container[0]; | |
511 | + while(elt && elt.parentNode !== container) | |
512 | + elt = elt.parentNode; | |
513 | + | |
514 | + return $(elt); | |
515 | + } | |
516 | + }; | |
517 | + | |
518 | + /** | |
519 | + * Register JQuery plugin | |
520 | + */ | |
521 | + $.fn.tagsinput = function(arg1, arg2, arg3) { | |
522 | + var results = []; | |
523 | + | |
524 | + this.each(function() { | |
525 | + var tagsinput = $(this).data('tagsinput'); | |
526 | + // Initialize a new tags input | |
527 | + if (!tagsinput) { | |
528 | + tagsinput = new TagsInput(this, arg1); | |
529 | + $(this).data('tagsinput', tagsinput); | |
530 | + results.push(tagsinput); | |
531 | + | |
532 | + if (this.tagName === 'SELECT') { | |
533 | + $('option', $(this)).attr('selected', 'selected'); | |
534 | + } | |
535 | + | |
536 | + // Init tags from $(this).val() | |
537 | + $(this).val($(this).val()); | |
538 | + } else if (!arg1 && !arg2) { | |
539 | + // tagsinput already exists | |
540 | + // no function, trying to init | |
541 | + results.push(tagsinput); | |
542 | + } else if(tagsinput[arg1] !== undefined) { | |
543 | + // Invoke function on existing tags input | |
544 | + if(tagsinput[arg1].length === 3 && arg3 !== undefined){ | |
545 | + var retVal = tagsinput[arg1](arg2, null, arg3); | |
546 | + }else{ | |
547 | + var retVal = tagsinput[arg1](arg2); | |
548 | + } | |
549 | + if (retVal !== undefined) | |
550 | + results.push(retVal); | |
551 | + } | |
552 | + }); | |
553 | + | |
554 | + if ( typeof arg1 == 'string') { | |
555 | + // Return the results from the invoked function calls | |
556 | + return results.length > 1 ? results : results[0]; | |
557 | + } else { | |
558 | + return results; | |
559 | + } | |
560 | + }; | |
561 | + | |
562 | + $.fn.tagsinput.Constructor = TagsInput; | |
563 | + | |
564 | + /** | |
565 | + * Most options support both a string or number as well as a function as | |
566 | + * option value. This function makes sure that the option with the given | |
567 | + * key in the given options is wrapped in a function | |
568 | + */ | |
569 | + function makeOptionItemFunction(options, key) { | |
570 | + if (typeof options[key] !== 'function') { | |
571 | + var propertyName = options[key]; | |
572 | + options[key] = function(item) { return item[propertyName]; }; | |
573 | + } | |
574 | + } | |
575 | + function makeOptionFunction(options, key) { | |
576 | + if (typeof options[key] !== 'function') { | |
577 | + var value = options[key]; | |
578 | + options[key] = function() { return value; }; | |
579 | + } | |
580 | + } | |
581 | + /** | |
582 | + * HtmlEncodes the given value | |
583 | + */ | |
584 | + var htmlEncodeContainer = $('<div />'); | |
585 | + function htmlEncode(value) { | |
586 | + if (value) { | |
587 | + return htmlEncodeContainer.text(value).html(); | |
588 | + } else { | |
589 | + return ''; | |
590 | + } | |
591 | + } | |
592 | + | |
593 | + /** | |
594 | + * Returns the position of the caret in the given input field | |
595 | + * http://flightschool.acylt.com/devnotes/caret-position-woes/ | |
596 | + */ | |
597 | + function doGetCaretPosition(oField) { | |
598 | + var iCaretPos = 0; | |
599 | + if (document.selection) { | |
600 | + oField.focus (); | |
601 | + var oSel = document.selection.createRange(); | |
602 | + oSel.moveStart ('character', -oField.value.length); | |
603 | + iCaretPos = oSel.text.length; | |
604 | + } else if (oField.selectionStart || oField.selectionStart == '0') { | |
605 | + iCaretPos = oField.selectionStart; | |
606 | + } | |
607 | + return (iCaretPos); | |
608 | + } | |
609 | + | |
610 | + /** | |
611 | + * Returns boolean indicates whether user has pressed an expected key combination. | |
612 | + * @param object keyPressEvent: JavaScript event object, refer | |
613 | + * http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html | |
614 | + * @param object lookupList: expected key combinations, as in: | |
615 | + * [13, {which: 188, shiftKey: true}] | |
616 | + */ | |
617 | + function keyCombinationInList(keyPressEvent, lookupList) { | |
618 | + var found = false; | |
619 | + $.each(lookupList, function (index, keyCombination) { | |
620 | + if (typeof (keyCombination) === 'number' && keyPressEvent.which === keyCombination) { | |
621 | + found = true; | |
622 | + return false; | |
623 | + } | |
624 | + | |
625 | + if (keyPressEvent.which === keyCombination.which) { | |
626 | + var alt = !keyCombination.hasOwnProperty('altKey') || keyPressEvent.altKey === keyCombination.altKey, | |
627 | + shift = !keyCombination.hasOwnProperty('shiftKey') || keyPressEvent.shiftKey === keyCombination.shiftKey, | |
628 | + ctrl = !keyCombination.hasOwnProperty('ctrlKey') || keyPressEvent.ctrlKey === keyCombination.ctrlKey; | |
629 | + if (alt && shift && ctrl) { | |
630 | + found = true; | |
631 | + return false; | |
632 | + } | |
633 | + } | |
634 | + }); | |
635 | + | |
636 | + return found; | |
637 | + } | |
638 | + | |
639 | + /** | |
640 | + * Initialize tagsinput behaviour on inputs and selects which have | |
641 | + * data-role=tagsinput | |
642 | + */ | |
643 | + $(function() { | |
644 | + $("input[data-role=tagsinput], select[multiple][data-role=tagsinput]").tagsinput(); | |
645 | + }); | |
646 | +})(window.jQuery); | ... | ... |
subjects/templates/subjects/create.html
... | ... | @@ -2,6 +2,20 @@ |
2 | 2 | |
3 | 3 | {% load widget_tweaks static i18n permission_tags django_bootstrap_breadcrumbs switchevenodd %} |
4 | 4 | |
5 | +{% block style %} | |
6 | + | |
7 | + {{block.super}} | |
8 | + <link rel="stylesheet" type="text/css" href="{% static "css/bootstrap-tagsinput.css" %}"> | |
9 | + | |
10 | + | |
11 | +{% endblock style %} | |
12 | + | |
13 | +{% block javascript %} | |
14 | + {{block.super}} | |
15 | + <script type="text/javascript" src="{% static "js/bootstrap-tagsinput.js" %} "></script> | |
16 | + | |
17 | +{% endblock javascript %} | |
18 | + | |
5 | 19 | {% block breadcrumbs %} |
6 | 20 | {% clear_breadcrumbs %} |
7 | 21 | {% breadcrumb 'Home' 'subjects:home' %} |
... | ... | @@ -57,6 +71,9 @@ |
57 | 71 | </div> |
58 | 72 | </div> |
59 | 73 | </div> |
74 | + {% elif field.auto_id == 'id_markers'%} | |
75 | + <label> {{field.label}} </label> | |
76 | + {% render_field field class='form-control' data-role="tagsinput" %} | |
60 | 77 | {% else %} |
61 | 78 | <div class="form-group {% if form.has_error %} has-error {% endif %} is-fileinput"> |
62 | 79 | {% if field.auto_id != 'id_visible' %} |
... | ... | @@ -190,6 +207,8 @@ |
190 | 207 | btn.switchClass("fa-angle-down", "fa-angle-right", 250, "easeInOutQuad"); |
191 | 208 | } |
192 | 209 | }); |
210 | + | |
211 | + | |
193 | 212 | </script> |
194 | 213 | {% endblock content %} |
195 | 214 | ... | ... |
subjects/views.py
... | ... | @@ -42,7 +42,7 @@ class HomeView(LoginRequiredMixin, ListView): |
42 | 42 | subjects = Subject.objects.all() |
43 | 43 | subjects = [subject for subject in subjects if self.request.user in subject.students.all() or self.request.user in subject.professor.all()] |
44 | 44 | |
45 | - paginator = Paginator(subjects, 2) | |
45 | + paginator = Paginator(subjects, 10) | |
46 | 46 | |
47 | 47 | page = self.request.GET.get('page') |
48 | 48 | try: | ... | ... |