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