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