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