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