1var is_touch_device = false; 2if (('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)) { 3 var octopus = document.getElementsByClassName('octopus'); 4 for (var i = 0; i < octopus.length; i++) { 5 octopus[i].className = octopus[i].className + ' octopus-touch'; 6 } 7 is_touch_device = true; 8} 9;/** 10 * Bootstrap Multiselect (https://github.com/davidstutz/bootstrap-multiselect) 11 * 12 * Apache License, Version 2.0: 13 * Copyright (c) 2012 - 2015 David Stutz 14 * 15 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 16 * use this file except in compliance with the License. You may obtain a 17 * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 18 * 19 * Unless required by applicable law or agreed to in writing, software 20 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 21 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 22 * License for the specific language governing permissions and limitations 23 * under the License. 24 * 25 * BSD 3-Clause License: 26 * Copyright (c) 2012 - 2015 David Stutz 27 * All rights reserved. 28 * 29 * Redistribution and use in source and binary forms, with or without 30 * modification, are permitted provided that the following conditions are met: 31 * - Redistributions of source code must retain the above copyright notice, 32 * this list of conditions and the following disclaimer. 33 * - Redistributions in binary form must reproduce the above copyright notice, 34 * this list of conditions and the following disclaimer in the documentation 35 * and/or other materials provided with the distribution. 36 * - Neither the name of David Stutz nor the names of its contributors may be 37 * used to endorse or promote products derived from this software without 38 * specific prior written permission. 39 * 40 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 41 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 42 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 43 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 44 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 45 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 46 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 47 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 48 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 49 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 50 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 51 */ 52!function ($) { 53 "use strict";// jshint ;_; 54 55 if (typeof ko !== 'undefined' && ko.bindingHandlers && !ko.bindingHandlers.multiselect) { 56 ko.bindingHandlers.multiselect = { 57 after: ['options', 'value', 'selectedOptions', 'enable', 'disable'], 58 59 init: function(element, valueAccessor, allBindings, viewModel, bindingContext) { 60 var $element = $(element); 61 var config = ko.toJS(valueAccessor()); 62 63 $element.multiselect(config); 64 65 if (allBindings.has('options')) { 66 var options = allBindings.get('options'); 67 if (ko.isObservable(options)) { 68 ko.computed({ 69 read: function() { 70 options(); 71 setTimeout(function() { 72 var ms = $element.data('multiselect'); 73 if (ms) 74 ms.updateOriginalOptions();//Not sure how beneficial this is. 75 $element.multiselect('rebuild'); 76 }, 1); 77 }, 78 disposeWhenNodeIsRemoved: element 79 }); 80 } 81 } 82 83 //value and selectedOptions are two-way, so these will be triggered even by our own actions. 84 //It needs some way to tell if they are triggered because of us or because of outside change. 85 //It doesn't loop but it's a waste of processing. 86 if (allBindings.has('value')) { 87 var value = allBindings.get('value'); 88 if (ko.isObservable(value)) { 89 ko.computed({ 90 read: function() {
vendor: 5,487 bytes, lines 91-231
91 value(); 92 setTimeout(function() { 93 $element.multiselect('refresh'); 94 }, 1); 95 }, 96 disposeWhenNodeIsRemoved: element 97 }).extend({ rateLimit: 100, notifyWhenChangesStop: true }); 98 } 99 } 100 101 //Switched from arrayChange subscription to general subscription using 'refresh'. 102 //Not sure performance is any better using 'select' and 'deselect'. 103 if (allBindings.has('selectedOptions')) { 104 var selectedOptions = allBindings.get('selectedOptions'); 105 if (ko.isObservable(selectedOptions)) { 106 ko.computed({ 107 read: function() { 108 selectedOptions(); 109 setTimeout(function() { 110 $element.multiselect('refresh'); 111 }, 1); 112 }, 113 disposeWhenNodeIsRemoved: element 114 }).extend({ rateLimit: 100, notifyWhenChangesStop: true }); 115 } 116 } 117 118 var setEnabled = function (enable) { 119 setTimeout(function () { 120 if (enable) 121 $element.multiselect('enable'); 122 else 123 $element.multiselect('disable'); 124 }); 125 }; 126 127 if (allBindings.has('enable')) { 128 var enable = allBindings.get('enable'); 129 if (ko.isObservable(enable)) { 130 ko.computed({ 131 read: function () { 132 setEnabled(enable()); 133 }, 134 disposeWhenNodeIsRemoved: element 135 }).extend({ rateLimit: 100, notifyWhenChangesStop: true }); 136 } else { 137 setEnabled(enable); 138 } 139 } 140 141 if (allBindings.has('disable')) { 142 var disable = allBindings.get('disable'); 143 if (ko.isObservable(disable)) { 144 ko.computed({ 145 read: function () { 146 setEnabled(!disable()); 147 }, 148 disposeWhenNodeIsRemoved: element 149 }).extend({ rateLimit: 100, notifyWhenChangesStop: true }); 150 } else { 151 setEnabled(!disable); 152 } 153 } 154 155 ko.utils.domNodeDisposal.addDisposeCallback(element, function() { 156 $element.multiselect('destroy'); 157 }); 158 }, 159 160 update: function(element, valueAccessor, allBindings, viewModel, bindingContext) { 161 var $element = $(element); 162 var config = ko.toJS(valueAccessor()); 163 164 $element.multiselect('setOptions', config); 165 $element.multiselect('rebuild'); 166 } 167 }; 168 } 169 170 function forEach(array, callback) { 171 for (var index = 0; index < array.length; ++index) { 172 callback(array[index], index); 173 } 174 } 175 176 /** 177 * Constructor to create a new multiselect using the given select. 178 * 179 * @param {jQuery} select 180 * @param {Object} options 181 * @returns {Multiselect} 182 */ 183 function Multiselect(select, options) { 184 185 this.$select = $(select); 186 187 // Placeholder via data attributes 188 if (this.$select.attr("data-placeholder")) { 189 options.nonSelectedText = this.$select.data("placeholder"); 190 } 191 192 this.options = this.mergeOptions($.extend({}, options, this.$select.data())); 193 194 // Initialization. 195 // We have to clone to create a new reference. 196 this.originalOptions = this.$select.clone()[0].options; 197 this.query = ''; 198 this.searchTimeout = null; 199 this.lastToggledInput = null; 200 201 this.options.multiple = this.$select.attr('multiple') === "multiple"; 202 this.options.onChange = $.proxy(this.options.onChange, this); 203 this.options.onDropdownShow = $.proxy(this.options.onDropdownShow, this); 204 this.options.onDropdownHide = $.proxy(this.options.onDropdownHide, this); 205 this.options.onDropdownShown = $.proxy(this.options.onDropdownShown, this); 206 this.options.onDropdownHidden = $.proxy(this.options.onDropdownHidden, this); 207 this.options.onInitialized = $.proxy(this.options.onInitialized, this); 208 209 // Build select all if enabled. 210 this.buildContainer(); 211 this.buildButton(); 212 this.buildDropdown(); 213 this.buildSelectAll(); 214 this.buildDropdownOptions(); 215 this.buildFilter(); 216 217 this.updateButtonText(); 218 this.updateSelectAll(true); 219 220 if (this.options.disableIfEmpty && $('option', this.$select).length <= 0) { 221 this.disable(); 222 } 223 224 this.$select.hide().after(this.$container); 225 this.options.onInitialized(this.$select, this.$container); 226 } 227 228 Multiselect.prototype = { 229 230 defaults: { 231 /**
232 * Default text function will either print 'None selected' in case no 233 * option is selected or a list of the selected options up to a length 234 * of 3 selected options. 235 * 236 * @param {jQuery} options 237 * @param {jQuery} select 238 * @returns {String} 239 */ 240 buttonText: function(options, select) { 241 if (this.disabledText.length > 0 242 && (this.disableIfEmpty || select.prop('disabled')) 243 && options.length == 0) { 244 245 return this.disabledText; 246 } 247 else if (options.length === 0) { 248 return this.nonSelectedText; 249 } 250 else if (this.allSelectedText 251 && options.length === $('option', $(select)).length 252 && $('option', $(select)).length !== 1 253 && this.multiple) { 254 255 if (this.selectAllNumber) { 256 return this.allSelectedText + ' (' + options.length + ')'; 257 } 258 else { 259 return this.allSelectedText; 260 } 261 } 262 else if (options.length > this.numberDisplayed) { 263 return options.length + ' ' + this.nSelectedText; 264 } 265 else { 266 var selected = ''; 267 var delimiter = this.delimiterText; 268 269 options.each(function() { 270 var label = ($(this).attr('label') !== undefined) ? $(this).attr('label') : $(this).text(); 271 selected += label + delimiter; 272 }); 273 274 return selected.substr(0, selected.length - 2); 275 } 276 }, 277 /** 278 * Updates the title of the button similar to the buttonText function. 279 * 280 * @param {jQuery} options 281 * @param {jQuery} select 282 * @returns {@exp;selected@call;substr} 283 */ 284 buttonTitle: function(options, select) { 285 if (options.length === 0) { 286 return this.nonSelectedText; 287 } 288 else { 289 var selected = ''; 290 var delimiter = this.delimiterText; 291 292 options.each(function () { 293 var label = ($(this).attr('label') !== undefined) ? $(this).attr('label') : $(this).text(); 294 selected += label + delimiter; 295 }); 296 return selected.substr(0, selected.length - 2); 297 } 298 }, 299 /** 300 * Create a label. 301 * 302 * @param {jQuery} element 303 * @returns {String} 304 */ 305 optionLabel: function(element){ 306 return $(element).attr('label') || $(element).text(); 307 }, 308 /** 309 * Create a class. 310 * 311 * @param {jQuery} element 312 * @returns {String} 313 */ 314 optionClass: function(element) { 315 return $(element).attr('class') || ''; 316 }, 317 /** 318 * Triggered on change of the multiselect. 319 * 320 * Not triggered when selecting/deselecting options manually. 321 * 322 * @param {jQuery} option 323 * @param {Boolean} checked 324 */ 325 onChange : function(option, checked) { 326 327 }, 328 /** 329 * Triggered when the dropdown is shown. 330 * 331 * @param {jQuery} event 332 */ 333 onDropdownShow: function(event) { 334 335 }, 336 /** 337 * Triggered when the dropdown is hidden. 338 * 339 * @param {jQuery} event 340 */ 341 onDropdownHide: function(event) { 342 343 }, 344 /** 345 * Triggered after the dropdown is shown.
346 * 347 * @param {jQuery} event 348 */ 349 onDropdownShown: function(event) { 350 351 }, 352 /** 353 * Triggered after the dropdown is hidden. 354 * 355 * @param {jQuery} event 356 */ 357 onDropdownHidden: function(event) { 358 359 }, 360 /** 361 * Triggered on select all. 362 */ 363 onSelectAll: function(checked) { 364 365 }, 366 /** 367 * Triggered after initializing. 368 * 369 * @param {jQuery} $select 370 * @param {jQuery} $container 371 */ 372 onInitialized: function($select, $container) { 373 374 }, 375 enableHTML: false, 376 buttonClass: 'btn btn-default', 377 inheritClass: false, 378 buttonWidth: 'auto', 379 buttonContainer: '<div class="btn-group" />', 380 dropRight: false, 381 dropUp: false, 382 selectedClass: 'active', 383 // Maximum height of the dropdown menu. 384 // If maximum height is exceeded a scrollbar will be displayed. 385 maxHeight: false, 386 checkboxName: false, 387 includeSelectAllOption: false, 388 includeSelectAllIfMoreThan: 0, 389 selectAllText: ' Select all', 390 selectAllValue: 'multiselect-all', 391 selectAllName: false, 392 selectAllNumber: true, 393 selectAllJustVisible: true, 394 enableFiltering: false, 395 enableCaseInsensitiveFiltering: false, 396 enableFullValueFiltering: false, 397 enableClickableOptGroups: false, 398 enableCollapsibelOptGroups: false, 399 filterPlaceholder: 'Search', 400 // possible options: 'text', 'value', 'both' 401 filterBehavior: 'text', 402 includeFilterClearBtn: true, 403 preventInputChangeEvent: false, 404 nonSelectedText: 'None selected', 405 nSelectedText: 'selected', 406 allSelectedText: 'All selected', 407 numberDisplayed: 3, 408 disableIfEmpty: false, 409 disabledText: '', 410 delimiterText: ', ', 411 templates: { 412 button: '<button type="button" class="multiselect dropdown-toggle" data-toggle="dropdown"><span class="multiselect-selected-text"></span> <b class="caret"></b></button>', 413 ul: '<ul class="multiselect-container dropdown-menu"></ul>', 414 filter: '<li class="multiselect-item filter"><div class="input-group"><span class="input-group-addon"><i class="glyphicon glyphicon-search"></i></span><input class="form-control multiselect-search" type="text"></div></li>', 415 filterClearBtn: '<span class="input-group-btn"><button class="btn btn-default multiselect-clear-filter" type="button"><i class="glyphicon glyphicon-remove-circle"></i></button></span>', 416 li: '<li><a tabindex="0"><label></label></a></li>', 417 divider: '<li class="multiselect-item divider"></li>', 418 liGroup: '<li class="multiselect-item multiselect-group"><label></label></li>' 419 } 420 }, 421 422 constructor: Multiselect, 423 424 /** 425 * Builds the container of the multiselect. 426 */ 427 buildContainer: function() { 428 this.$container = $(this.options.buttonContainer); 429 this.$container.on('show.bs.dropdown', this.options.onDropdownShow); 430 this.$container.on('hide.bs.dropdown', this.options.onDropdownHide); 431 this.$container.on('shown.bs.dropdown', this.options.onDropdownShown); 432 this.$container.on('hidden.bs.dropdown', this.options.onDropdownHidden); 433 }, 434 435 /** 436 * Builds the button of the multiselect. 437 */ 438 buildButton: function() { 439 this.$button = $(this.options.templates.button).addClass(this.options.buttonClass); 440 if (this.$select.attr('class') && this.options.inheritClass) { 441 this.$button.addClass(this.$select.attr('class')); 442 } 443 // Adopt active state. 444 if (this.$select.prop('disabled')) { 445 this.disable(); 446 } 447 else { 448 this.enable(); 449 } 450 451 // Manually add button width if set. 452 if (this.options.buttonWidth && this.options.buttonWidth !== 'auto') { 453 this.$button.css({ 454 'width' : this.options.buttonWidth, 455 'overflow' : 'hidden', 456 'text-overflow' : 'ellipsis' 457 }); 458 this.$container.css({ 459 'width': this.options.buttonWidth 460 }); 461 } 462 463 // Keep the tab index from the select. 464 var tabindex = this.$select.attr('tabindex'); 465 if (tabindex) { 466 this.$button.attr('tabindex', tabindex); 467 } 468 469 this.$container.prepend(this.$button); 470 }, 471 472 /** 473 * Builds the ul representing the dropdown menu. 474 */ 475 buildDropdown: function() { 476 477 // Build ul. 478 this.$ul = $(this.options.templates.ul); 479 480 if (this.options.dropRight) { 481 this.$ul.addClass('pull-right'); 482 } 483 484 // Set max height of dropdown menu to activate auto scrollbar. 485 if (this.options.maxHeight) { 486 // TODO: Add a class for this option to move the css declarations. 487 this.$ul.css({ 488 'max-height': this.options.maxHeight + 'px', 489 'overflow-y': 'auto', 490 'overflow-x': 'hidden' 491 }); 492 } 493 494 if (this.options.dropUp) { 495 496 var height = Math.min(this.options.maxHeight, $('option[data-role!="divider"]', this.$select).length*26 + $('option[data-role="divider"]', this.$select).length*19 + (this.options.includeSelectAllOption ? 26 : 0) + (this.options.enableFiltering || this.options.enableCaseInsensitiveFiltering ? 44 : 0)); 497 var moveCalc = height + 34; 498 499 this.$ul.css({ 500 'max-height': height + 'px', 501 'overflow-y': 'auto', 502 'overflow-x': 'hidden', 503 'margin-top': "-" + moveCalc + 'px' 504 }); 505 } 506 507 this.$container.append(this.$ul); 508 }, 509 510 /** 511 * Build the dropdown options and binds all nessecary events. 512 * 513 * Uses createDivider and createOptionValue to create the necessary options. 514 */ 515 buildDropdownOptions: function() { 516 517 this.$select.children().each($.proxy(function(index, element) { 518 519 var $element = $(element);
vendor: 4,132 bytes, lines 520-626
520 // Support optgroups and options without a group simultaneously. 521 var tag = $element.prop('tagName') 522 .toLowerCase(); 523 524 if ($element.prop('value') === this.options.selectAllValue) { 525 return; 526 } 527 528 if (tag === 'optgroup') { 529 this.createOptgroup(element); 530 } 531 else if (tag === 'option') { 532 533 if ($element.data('role') === 'divider') { 534 this.createDivider(); 535 } 536 else { 537 this.createOptionValue(element); 538 } 539 540 } 541 542 // Other illegal tags will be ignored. 543 }, this)); 544 545 // Bind the change event on the dropdown elements. 546 $('li input', this.$ul).on('change', $.proxy(function(event) { 547 var $target = $(event.target); 548 549 var checked = $target.prop('checked') || false; 550 var isSelectAllOption = $target.val() === this.options.selectAllValue; 551 552 // Apply or unapply the configured selected class. 553 if (this.options.selectedClass) { 554 if (checked) { 555 $target.closest('li') 556 .addClass(this.options.selectedClass); 557 } 558 else { 559 $target.closest('li') 560 .removeClass(this.options.selectedClass); 561 } 562 } 563 564 // Get the corresponding option. 565 var value = $target.val(); 566 var $option = this.getOptionByValue(value); 567 568 var $optionsNotThis = $('option', this.$select).not($option); 569 var $checkboxesNotThis = $('input', this.$container).not($target); 570 571 if (isSelectAllOption) { 572 if (checked) { 573 this.selectAll(this.options.selectAllJustVisible); 574 } 575 else { 576 this.deselectAll(this.options.selectAllJustVisible); 577 } 578 } 579 else { 580 if (checked) { 581 $option.prop('selected', true); 582 583 if (this.options.multiple) { 584 // Simply select additional option. 585 $option.prop('selected', true); 586 } 587 else { 588 // Unselect all other options and corresponding checkboxes. 589 if (this.options.selectedClass) { 590 $($checkboxesNotThis).closest('li').removeClass(this.options.selectedClass); 591 } 592 593 $($checkboxesNotThis).prop('checked', false); 594 $optionsNotThis.prop('selected', false); 595 596 // It's a single selection, so close. 597 this.$button.click(); 598 } 599 600 if (this.options.selectedClass === "active") { 601 $optionsNotThis.closest("a").css("outline", ""); 602 } 603 } 604 else { 605 // Unselect option. 606 $option.prop('selected', false); 607 } 608 609 // To prevent select all from firing onChange: #575 610 this.options.onChange($option, checked); 611 } 612 613 this.$select.change(); 614 615 this.updateButtonText(); 616 this.updateSelectAll(); 617 618 if(this.options.preventInputChangeEvent) { 619 return false; 620 } 621 }, this)); 622 623 $('li a', this.$ul).on('mousedown', function(e) { 624 if (e.shiftKey) { 625 // Prevent selecting text by Shift+click 626 return false;
627 } 628 }); 629 630 $('li a', this.$ul).on('touchstart click', $.proxy(function(event) { 631 event.stopPropagation(); 632 633 var $target = $(event.target); 634 635 if (event.shiftKey && this.options.multiple) { 636 if($target.is("label")){ // Handles checkbox selection manually (see https://github.com/davidstutz/bootstrap-multiselect/issues/431) 637 event.preventDefault(); 638 $target = $target.find("input"); 639 $target.prop("checked", !$target.prop("checked")); 640 } 641 var checked = $target.prop('checked') || false; 642 643 if (this.lastToggledInput !== null && this.lastToggledInput !== $target) { // Make sure we actually have a range 644 var from = $target.closest("li").index(); 645 var to = this.lastToggledInput.closest("li").index(); 646 647 if (from > to) { // Swap the indices 648 var tmp = to; 649 to = from; 650 from = tmp; 651 } 652 653 // Make sure we grab all elements since slice excludes the last index 654 ++to; 655 656 // Change the checkboxes and underlying options 657 var range = this.$ul.find("li").slice(from, to).find("input"); 658 659 range.prop('checked', checked); 660 661 if (this.options.selectedClass) { 662 range.closest('li') 663 .toggleClass(this.options.selectedClass, checked); 664 } 665 666 for (var i = 0, j = range.length; i < j; i++) { 667 var $checkbox = $(range[i]); 668 669 var $option = this.getOptionByValue($checkbox.val()); 670 671 $option.prop('selected', checked); 672 } 673 } 674 675 // Trigger the select "change" event 676 $target.trigger("change"); 677 } 678 679 // Remembers last clicked option 680 if($target.is("input") && !$target.closest("li").is(".multiselect-item")){ 681 this.lastToggledInput = $target; 682 } 683 684 $target.blur(); 685 }, this)); 686 687 // Keyboard support. 688 this.$container.off('keydown.multiselect').on('keydown.multiselect', $.proxy(function(event) { 689 if ($('input[type="text"]', this.$container).is(':focus')) { 690 return; 691 } 692 693 if (event.keyCode === 9 && this.$container.hasClass('open')) { 694 this.$button.click(); 695 } 696 else { 697 var $items = $(this.$container).find("li:not(.divider):not(.disabled) a").filter(":visible"); 698 699 if (!$items.length) { 700 return; 701 } 702 703 var index = $items.index($items.filter(':focus')); 704 705 // Navigation up. 706 if (event.keyCode === 38 && index > 0) { 707 index--; 708 } 709 // Navigate down. 710 else if (event.keyCode === 40 && index < $items.length - 1) { 711 index++; 712 } 713 else if (!~index) { 714 index = 0; 715 } 716 717 var $current = $items.eq(index); 718 $current.focus(); 719 720 if (event.keyCode === 32 || event.keyCode === 13) { 721 var $checkbox = $current.find('input'); 722 723 $checkbox.prop("checked", !$checkbox.prop("checked")); 724 $checkbox.change(); 725 } 726 727 event.stopPropagation(); 728 event.preventDefault(); 729 } 730 }, this)); 731 732 if(this.options.enableClickableOptGroups && this.options.multiple) { 733 $('li.multiselect-group', this.$ul).on('click', $.proxy(function(event) { 734 event.stopPropagation(); 735 console.log('test'); 736 var group = $(event.target).parent(); 737 738 // Search all option in optgroup 739 var $options = group.nextUntil('li.multiselect-group'); 740 var $visibleOptions = $options.filter(":visible:not(.disabled)"); 741 742 // check or uncheck items 743 var allChecked = true; 744 var optionInputs = $visibleOptions.find('input'); 745 var values = []; 746 747 optionInputs.each(function() { 748 allChecked = allChecked && $(this).prop('checked'); 749 values.push($(this).val()); 750 }); 751 752 if (!allChecked) { 753 this.select(values, false); 754 } 755 else { 756 this.deselect(values, false); 757 } 758 759 this.options.onChange(optionInputs, !allChecked); 760 }, this)); 761 } 762 763 if (this.options.enableCollapsibleOptGroups && this.options.multiple) { 764 $("li.multiselect-group input", this.$ul).off(); 765 $("li.multiselect-group", this.$ul).siblings().not("li.multiselect-group, li.multiselect-all", this.$ul).each( function () { 766 $(this).toggleClass('hidden', true); 767 }); 768 769 $("li.multiselect-group", this.$ul).on("click", $.proxy(function(group) { 770 group.stopPropagation(); 771 }, this)); 772 773 $("li.multiselect-group > a > b", this.$ul).on("click", $.proxy(function(t) { 774 t.stopPropagation(); 775 var n = $(t.target).closest('li'); 776 var r = n.nextUntil("li.multiselect-group"); 777 var i = true; 778 779 r.each(function() { 780 i = i && $(this).hasClass('hidden'); 781 }); 782 783 r.toggleClass('hidden', !i); 784 }, this)); 785 786 $("li.multiselect-group > a > input", this.$ul).on("change", $.proxy(function(t) { 787 t.stopPropagation(); 788 var n = $(t.target).closest('li'); 789 var r = n.nextUntil("li.multiselect-group", ':not(.disabled)'); 790 var s = r.find("input"); 791 792 var i = true; 793 s.each(function() { 794 i = i && $(this).prop("checked"); 795 }); 796 797 s.prop("checked", !i).trigger("change"); 798 }, this)); 799 800 // Set the initial selection state of the groups. 801 $('li.multiselect-group', this.$ul).each(function() { 802 var r = $(this).nextUntil("li.multiselect-group", ':not(.disabled)'); 803 var s = r.find("input"); 804 805 var i = true; 806 s.each(function() { 807 i = i && $(this).prop("checked"); 808 }); 809 810 $(this).find('input').prop("checked", i); 811 }); 812 813 // Update the group checkbox based on new selections among the 814 // corresponding children. 815 $("li input", this.$ul).on("change", $.proxy(function(t) { 816 t.stopPropagation(); 817 var n = $(t.target).closest('li'); 818 var r1 = n.prevUntil("li.multiselect-group", ':not(.disabled)'); 819 var r2 = n.nextUntil("li.multiselect-group", ':not(.disabled)'); 820 var s1 = r1.find("input"); 821 var s2 = r2.find("input"); 822 823 var i = $(t.target).prop('checked'); 824 s1.each(function() { 825 i = i && $(this).prop("checked"); 826 }); 827 828 s2.each(function() { 829 i = i && $(this).prop("checked"); 830 }); 831 832 n.prevAll('.multiselect-group').find('input').prop('checked', i); 833 }, this)); 834 835 $("li.multiselect-all", this.$ul).css('background', '#f3f3f3').css('border-bottom', '1px solid #eaeaea'); 836 $("li.multiselect-group > a, li.multiselect-all > a > label.checkbox", this.$ul).css('padding', '3px 20px 3px 35px'); 837 $("li.multiselect-group > a > input", this.$ul).css('margin', '4px 0px 5px -20px'); 838 } 839 }, 840 841 /** 842 * Create an option using the given select option. 843 * 844 * @param {jQuery} element 845 */ 846 createOptionValue: function(element) { 847 var $element = $(element); 848 if ($element.is(':selected')) { 849 $element.prop('selected', true); 850 } 851
852 // Support the label attribute on options. 853 var label = this.options.optionLabel(element); 854 var classes = this.options.optionClass(element); 855 var value = $element.val(); 856 var inputType = this.options.multiple ? "checkbox" : "radio"; 857 858 var $li = $(this.options.templates.li); 859 var $label = $('label', $li); 860 $label.addClass(inputType); 861 $li.addClass(classes); 862 863 if (this.options.enableHTML) { 864 $label.html(" " + label); 865 } 866 else { 867 $label.text(" " + label); 868 } 869 870 var $checkbox = $('<input/>').attr('type', inputType); 871 872 if (this.options.checkboxName) { 873 $checkbox.attr('name', this.options.checkboxName); 874 } 875 $label.prepend($checkbox); 876 877 var selected = $element.prop('selected') || false; 878 $checkbox.val(value); 879 880 if (value === this.options.selectAllValue) { 881 $li.addClass("multiselect-item multiselect-all"); 882 $checkbox.parent().parent() 883 .addClass('multiselect-all'); 884 } 885 886 $label.attr('title', $element.attr('title')); 887 888 this.$ul.append($li); 889 890 if ($element.is(':disabled')) { 891 $checkbox.attr('disabled', 'disabled') 892 .prop('disabled', true) 893 .closest('a') 894 .attr("tabindex", "-1") 895 .closest('li') 896 .addClass('disabled'); 897 } 898 899 $checkbox.prop('checked', selected); 900 901 if (selected && this.options.selectedClass) { 902 $checkbox.closest('li') 903 .addClass(this.options.selectedClass); 904 } 905 }, 906 907 /** 908 * Creates a divider using the given select option. 909 * 910 * @param {jQuery} element 911 */ 912 createDivider: function(element) { 913 var $divider = $(this.options.templates.divider); 914 this.$ul.append($divider); 915 }, 916 917 /** 918 * Creates an optgroup. 919 * 920 * @param {jQuery} group 921 */ 922 createOptgroup: function(group) { 923 if (this.options.enableCollapsibleOptGroups && this.options.multiple) { 924 var label = $(group).attr("label"); 925 var value = $(group).attr("value"); 926 var r = $('<li class="multiselect-item multiselect-group"><a href="javascript:void(0);"><input type="checkbox" value="' + value + '"/><b> ' + label + '<b class="caret"></b></b></a></li>'); 927 928 if (this.options.enableClickableOptGroups) { 929 r.addClass("multiselect-group-clickable") 930 } 931 this.$ul.append(r); 932 if ($(group).is(":disabled")) { 933 r.addClass("disabled") 934 } 935 $("option", group).each($.proxy(function($, group) { 936 this.createOptionValue(group) 937 }, this)) 938 } 939 else { 940 var groupName = $(group).prop('label'); 941 942 // Add a header for the group. 943 var $li = $(this.options.templates.liGroup); 944 945 if (this.options.enableHTML) { 946 $('label', $li).html(groupName); 947 } 948 else { 949 $('label', $li).text(groupName); 950 } 951 952 if (this.options.enableClickableOptGroups) { 953 $li.addClass('multiselect-group-clickable'); 954 } 955 956 this.$ul.append($li); 957 958 if ($(group).is(':disabled')) { 959 $li.addClass('disabled'); 960 } 961 962 // Add the options of the group. 963 $('option', group).each($.proxy(function(index, element) { 964 this.createOptionValue(element); 965 }, this)); 966 } 967 }, 968 969 /** 970 * Build the select all. 971 * 972 * Checks if a select all has already been created. 973 */ 974 buildSelectAll: function() { 975 if (typeof this.options.selectAllValue === 'number') { 976 this.options.selectAllValue = this.options.selectAllValue.toString(); 977 } 978 979 var alreadyHasSelectAll = this.hasSelectAll(); 980 981 if (!alreadyHasSelectAll && this.options.includeSelectAllOption && this.options.multiple 982 && $('option', this.$select).length > this.options.includeSelectAllIfMoreThan) { 983 984 // Check whether to add a divider after the select all. 985 if (this.options.includeSelectAllDivider) { 986 this.$ul.prepend($(this.options.templates.divider)); 987 } 988 989 var $li = $(this.options.templates.li); 990 $('label', $li).addClass("checkbox"); 991 992 if (this.options.enableHTML) { 993 $('label', $li).html(" " + this.options.selectAllText); 994 } 995 else { 996 $('label', $li).text(" " + this.options.selectAllText); 997 } 998 999 if (this.options.selectAllName) { 1000 $('label', $li).prepend('<input type="checkbox" name="' + this.options.selectAllName + '" />'); 1001 } 1002 else { 1003 $('label', $li).prepend('<input type="checkbox" />'); 1004 } 1005 1006 var $checkbox = $('input', $li); 1007 $checkbox.val(this.options.selectAllValue); 1008 1009 $li.addClass("multiselect-item multiselect-all"); 1010 $checkbox.parent().parent() 1011 .addClass('multiselect-all'); 1012 1013 this.$ul.prepend($li); 1014 1015 $checkbox.prop('checked', false); 1016 } 1017 }, 1018 1019 /** 1020 * Builds the filter. 1021 */ 1022 buildFilter: function() { 1023 1024 // Build filter if filtering OR case insensitive filtering is enabled and the number of options exceeds (or equals) enableFilterLength. 1025 if (this.options.enableFiltering || this.options.enableCaseInsensitiveFiltering) { 1026 var enableFilterLength = Math.max(this.options.enableFiltering, this.options.enableCaseInsensitiveFiltering); 1027 1028 if (this.$select.find('option').length >= enableFilterLength) { 1029 1030 this.$filter = $(this.options.templates.filter); 1031 $('input', this.$filter).attr('placeholder', this.options.filterPlaceholder); 1032 1033 // Adds optional filter clear button 1034 if(this.options.includeFilterClearBtn){ 1035 var clearBtn = $(this.options.templates.filterClearBtn); 1036 clearBtn.on('click', $.proxy(function(event){ 1037 clearTimeout(this.searchTimeout); 1038 this.$filter.find('.multiselect-search').val(''); 1039 $('li', this.$ul).show().removeClass("filter-hidden"); 1040 this.updateSelectAll(); 1041 }, this)); 1042 this.$filter.find('.input-group').append(clearBtn); 1043 } 1044 1045 this.$ul.prepend(this.$filter); 1046 1047 this.$filter.val(this.query).on('click', function(event) { 1048 event.stopPropagation(); 1049 }).on('input keydown', $.proxy(function(event) { 1050 // Cancel enter key default behaviour 1051 if (event.which === 13) { 1052 event.preventDefault(); 1053 } 1054 1055 // This is useful to catch "keydown" events after the browser has updated the control. 1056 clearTimeout(this.searchTimeout); 1057 1058 this.searchTimeout = this.asyncFunction($.proxy(function() { 1059 1060 if (this.query !== event.target.value) { 1061 this.query = event.target.value; 1062 1063 var currentGroup, currentGroupVisible; 1064 $.each($('li', this.$ul), $.proxy(function(index, element) { 1065 var value = $('input', element).length > 0 ? $('input', element).val() : ""; 1066 var text = $('label', element).text(); 1067 1068 var filterCandidate = ''; 1069 if ((this.options.filterBehavior === 'text')) { 1070 filterCandidate = text; 1071 } 1072 else if ((this.options.filterBehavior === 'value')) { 1073 filterCandidate = value; 1074 } 1075 else if (this.options.filterBehavior === 'both') { 1076 filterCandidate = text + '\n' + value; 1077 } 1078 1079 if (value !== this.options.selectAllValue && text) { 1080 1081 // By default lets assume that element is not 1082 // interesting for this search. 1083 var showElement = false;
1084 1085 if (this.options.enableCaseInsensitiveFiltering) { 1086 filterCandidate = filterCandidate.toLowerCase(); 1087 this.query = this.query.toLowerCase(); 1088 } 1089 1090 if (this.options.enableFullValueFiltering && this.options.filterBehavior !== 'both') { 1091 var valueToMatch = filterCandidate.trim().substring(0, this.query.length); 1092 if (this.query.indexOf(valueToMatch) > -1) { 1093 showElement = true; 1094 } 1095 } 1096 else if (filterCandidate.indexOf(this.query) > -1) { 1097 showElement = true; 1098 } 1099 1100 // Toggle current element (group or group item) according to showElement boolean. 1101 $(element).toggle(showElement).toggleClass('filter-hidden', !showElement); 1102 1103 // Differentiate groups and group items. 1104 if ($(element).hasClass('multiselect-group')) { 1105 // Remember group status. 1106 currentGroup = element; 1107 currentGroupVisible = showElement; 1108 } 1109 else { 1110 // Show group name when at least one of its items is visible. 1111 if (showElement) { 1112 $(currentGroup).show().removeClass('filter-hidden'); 1113 } 1114 1115 // Show all group items when group name satisfies filter. 1116 if (!showElement && currentGroupVisible) { 1117 $(element).show().removeClass('filter-hidden'); 1118 } 1119 } 1120 } 1121 }, this)); 1122 } 1123 1124 this.updateSelectAll(); 1125 }, this), 300, this); 1126 }, this)); 1127 } 1128 } 1129 }, 1130 1131 /** 1132 * Unbinds the whole plugin. 1133 */ 1134 destroy: function() { 1135 this.$container.remove(); 1136 this.$select.show(); 1137 this.$select.data('multiselect', null); 1138 }, 1139 1140 /** 1141 * Refreshs the multiselect based on the selected options of the select. 1142 */ 1143 refresh: function () { 1144 var inputs = $.map($('li input', this.$ul), $); 1145 1146 $('option', this.$select).each($.proxy(function (index, element) { 1147 var $elem = $(element); 1148 var value = $elem.val(); 1149 var $input; 1150 for (var i = inputs.length; 0 < i--; /**/) { 1151 if (value !== ($input = inputs[i]).val()) 1152 continue; // wrong li 1153 1154 if ($elem.is(':selected')) { 1155 $input.prop('checked', true); 1156 1157 if (this.options.selectedClass) { 1158 $input.closest('li') 1159 .addClass(this.options.selectedClass); 1160 } 1161 } 1162 else { 1163 $input.prop('checked', false); 1164 1165 if (this.options.selectedClass) { 1166 $input.closest('li') 1167 .removeClass(this.options.selectedClass); 1168 } 1169 } 1170 1171 if ($elem.is(":disabled")) { 1172 $input.attr('disabled', 'disabled') 1173 .prop('disabled', true) 1174 .closest('li') 1175 .addClass('disabled'); 1176 } 1177 else { 1178 $input.prop('disabled', false) 1179 .closest('li') 1180 .removeClass('disabled'); 1181 } 1182 break; // assumes unique values 1183 } 1184 }, this)); 1185 1186 this.updateButtonText(); 1187 this.updateSelectAll(); 1188 }, 1189 1190 /** 1191 * Select all options of the given values. 1192 * 1193 * If triggerOnChange is set to true, the on change event is triggered if 1194 * and only if one value is passed. 1195 * 1196 * @param {Array} selectValues 1197 * @param {Boolean} triggerOnChange 1198 */ 1199 select: function(selectValues, triggerOnChange) { 1200 if(!$.isArray(selectValues)) { 1201 selectValues = [selectValues]; 1202 } 1203 1204 for (var i = 0; i < selectValues.length; i++) { 1205 var value = selectValues[i]; 1206 1207 if (value === null || value === undefined) { 1208 continue; 1209 } 1210 1211 var $option = this.getOptionByValue(value); 1212 var $checkbox = this.getInputByValue(value); 1213 1214 if($option === undefined || $checkbox === undefined) { 1215 continue; 1216 } 1217 1218 if (!this.options.multiple) { 1219 this.deselectAll(false); 1220 } 1221 1222 if (this.options.selectedClass) { 1223 $checkbox.closest('li') 1224 .addClass(this.options.selectedClass); 1225 } 1226 1227 $checkbox.prop('checked', true); 1228 $option.prop('selected', true); 1229 1230 if (triggerOnChange) { 1231 this.options.onChange($option, true); 1232 } 1233 } 1234 1235 this.updateButtonText(); 1236 this.updateSelectAll(); 1237 }, 1238 1239 /** 1240 * Clears all selected items. 1241 */ 1242 clearSelection: function () { 1243 this.deselectAll(false); 1244 this.updateButtonText(); 1245 this.updateSelectAll(); 1246 }, 1247 1248 /** 1249 * Deselects all options of the given values. 1250 * 1251 * If triggerOnChange is set to true, the on change event is triggered, if 1252 * and only if one value is passed. 1253 * 1254 * @param {Array} deselectValues 1255 * @param {Boolean} triggerOnChange 1256 */ 1257 deselect: function(deselectValues, triggerOnChange) { 1258 if(!$.isArray(deselectValues)) { 1259 deselectValues = [deselectValues]; 1260 } 1261 1262 for (var i = 0; i < deselectValues.length; i++) { 1263 var value = deselectValues[i]; 1264 1265 if (value === null || value === undefined) { 1266 continue; 1267 } 1268 1269 var $option = this.getOptionByValue(value); 1270 var $checkbox = this.getInputByValue(value); 1271 1272 if($option === undefined || $checkbox === undefined) { 1273 continue; 1274 } 1275 1276 if (this.options.selectedClass) { 1277 $checkbox.closest('li') 1278 .removeClass(this.options.selectedClass); 1279 } 1280 1281 $checkbox.prop('checked', false); 1282 $option.prop('selected', false); 1283 1284 if (triggerOnChange) { 1285 this.options.onChange($option, false); 1286 } 1287 } 1288 1289 this.updateButtonText(); 1290 this.updateSelectAll(); 1291 }, 1292 1293 /** 1294 * Selects all enabled & visible options. 1295 * 1296 * If justVisible is true or not specified, only visible options are selected. 1297 * 1298 * @param {Boolean} justVisible 1299 * @param {Boolean} triggerOnSelectAll 1300 */ 1301 selectAll: function (justVisible, triggerOnSelectAll) { 1302 justVisible = (this.options.enableCollapsibleOptGroups && this.options.multiple) ? false : justVisible; 1303 1304 var justVisible = typeof justVisible === 'undefined' ? true : justVisible; 1305 var allCheckboxes = $("li input[type='checkbox']:enabled", this.$ul); 1306 var visibleCheckboxes = allCheckboxes.filter(":visible"); 1307 var allCheckboxesCount = allCheckboxes.length; 1308 var visibleCheckboxesCount = visibleCheckboxes.length; 1309 1310 if(justVisible) { 1311 visibleCheckboxes.prop('checked', true); 1312 $("li:not(.divider):not(.disabled)", this.$ul).filter(":visible").addClass(this.options.selectedClass); 1313 } 1314 else { 1315 allCheckboxes.prop('checked', true); 1316 $("li:not(.divider):not(.disabled)", this.$ul).addClass(this.options.selectedClass); 1317 } 1318 1319 if (allCheckboxesCount === visibleCheckboxesCount || justVisible === false) { 1320 $("option:not([data-role='divider']):enabled", this.$select).prop('selected', true); 1321 } 1322 else { 1323 var values = visibleCheckboxes.map(function() { 1324 return $(this).val(); 1325 }).get(); 1326 1327 $("option:enabled", this.$select).filter(function(index) { 1328 return $.inArray($(this).val(), values) !== -1; 1329 }).prop('selected', true); 1330 } 1331 1332 if (triggerOnSelectAll) { 1333 this.options.onSelectAll(); 1334 } 1335 }, 1336 1337 /** 1338 * Deselects all options. 1339 * 1340 * If justVisible is true or not specified, only visible options are deselected. 1341 * 1342 * @param {Boolean} justVisible 1343 */ 1344 deselectAll: function (justVisible) { 1345 justVisible = (this.options.enableCollapsibleOptGroups && this.options.multiple) ? false : justVisible; 1346 justVisible = typeof justVisible === 'undefined' ? true : justVisible; 1347 1348 if(justVisible) { 1349 var visibleCheckboxes = $("li input[type='checkbox']:not(:disabled)", this.$ul).filter(":visible"); 1350 visibleCheckboxes.prop('checked', false); 1351 1352 var values = visibleCheckboxes.map(function() { 1353 return $(this).val(); 1354 }).get(); 1355 1356 $("option:enabled", this.$select).filter(function(index) { 1357 return $.inArray($(this).val(), values) !== -1; 1358 }).prop('selected', false); 1359 1360 if (this.options.selectedClass) { 1361 $("li:not(.divider):not(.disabled)", this.$ul).filter(":visible").removeClass(this.options.selectedClass); 1362 } 1363 } 1364 else { 1365 $("li input[type='checkbox']:enabled", this.$ul).prop('checked', false); 1366 $("option:enabled", this.$select).prop('selected', false); 1367 1368 if (this.options.selectedClass) { 1369 $("li:not(.divider):not(.disabled)", this.$ul).removeClass(this.options.selectedClass); 1370 } 1371 } 1372 }, 1373 1374 /** 1375 * Rebuild the plugin. 1376 * 1377 * Rebuilds the dropdown, the filter and the select all option. 1378 */ 1379 rebuild: function() { 1380 this.$ul.html(''); 1381 1382 // Important to distinguish between radios and checkboxes. 1383 this.options.multiple = this.$select.attr('multiple') === "multiple"; 1384 1385 this.buildSelectAll(); 1386 this.buildDropdownOptions(); 1387 this.buildFilter(); 1388 1389 this.updateButtonText(); 1390 this.updateSelectAll(true); 1391 1392 if (this.options.disableIfEmpty && $('option', this.$select).length <= 0) { 1393 this.disable(); 1394 } 1395 else { 1396 this.enable(); 1397 } 1398 1399 if (this.options.dropRight) { 1400 this.$ul.addClass('pull-right'); 1401 } 1402 }, 1403 1404 /** 1405 * The provided data will be used to build the dropdown. 1406 */ 1407 dataprovider: function(dataprovider) { 1408 1409 var groupCounter = 0; 1410 var $select = this.$select.empty(); 1411 1412 $.each(dataprovider, function (index, option) { 1413 var $tag; 1414 1415 if ($.isArray(option.children)) { // create optiongroup tag 1416 groupCounter++; 1417 1418 $tag = $('<optgroup/>').attr({ 1419 label: option.label || 'Group ' + groupCounter, 1420 disabled: !!option.disabled 1421 }); 1422
1423 forEach(option.children, function(subOption) { // add children option tags 1424 $tag.append($('<option/>').attr({ 1425 value: subOption.value, 1426 label: subOption.label || subOption.value, 1427 title: subOption.title, 1428 selected: !!subOption.selected, 1429 disabled: !!subOption.disabled 1430 })); 1431 }); 1432 } 1433 else { 1434 $tag = $('<option/>').attr({ 1435 value: option.value, 1436 label: option.label || option.value, 1437 title: option.title, 1438 class: option.class, 1439 selected: !!option.selected, 1440 disabled: !!option.disabled 1441 }); 1442 $tag.text(option.label || option.value); 1443 } 1444 1445 $select.append($tag); 1446 }); 1447 1448 this.rebuild(); 1449 }, 1450 1451 /** 1452 * Enable the multiselect. 1453 */ 1454 enable: function() { 1455 this.$select.prop('disabled', false); 1456 this.$button.prop('disabled', false) 1457 .removeClass('disabled'); 1458 }, 1459 1460 /** 1461 * Disable the multiselect. 1462 */ 1463 disable: function() { 1464 this.$select.prop('disabled', true); 1465 this.$button.prop('disabled', true) 1466 .addClass('disabled'); 1467 }, 1468 1469 /** 1470 * Set the options. 1471 * 1472 * @param {Array} options 1473 */ 1474 setOptions: function(options) { 1475 this.options = this.mergeOptions(options); 1476 }, 1477 1478 /** 1479 * Merges the given options with the default options. 1480 * 1481 * @param {Array} options 1482 * @returns {Array} 1483 */ 1484 mergeOptions: function(options) { 1485 return $.extend(true, {}, this.defaults, this.options, options); 1486 }, 1487 1488 /** 1489 * Checks whether a select all checkbox is present. 1490 * 1491 * @returns {Boolean} 1492 */ 1493 hasSelectAll: function() { 1494 return $('li.multiselect-all', this.$ul).length > 0; 1495 }, 1496 1497 /** 1498 * Updates the select all checkbox based on the currently displayed and selected checkboxes. 1499 */ 1500 updateSelectAll: function(notTriggerOnSelectAll) { 1501 if (this.hasSelectAll()) { 1502 var allBoxes = $("li:not(.multiselect-item):not(.filter-hidden) input:enabled", this.$ul); 1503 var allBoxesLength = allBoxes.length; 1504 var checkedBoxesLength = allBoxes.filter(":checked").length; 1505 var selectAllLi = $("li.multiselect-all", this.$ul); 1506 var selectAllInput = selectAllLi.find("input"); 1507 1508 if (checkedBoxesLength > 0 && checkedBoxesLength === allBoxesLength) { 1509 selectAllInput.prop("checked", true); 1510 selectAllLi.addClass(this.options.selectedClass); 1511 this.options.onSelectAll(true); 1512 } 1513 else { 1514 selectAllInput.prop("checked", false); 1515 selectAllLi.removeClass(this.options.selectedClass); 1516 if (checkedBoxesLength === 0) { 1517 if (!notTriggerOnSelectAll) { 1518 this.options.onSelectAll(false); 1519 } 1520 } 1521 } 1522 } 1523 }, 1524 1525 /** 1526 * Update the button text and its title based on the currently selected options. 1527 */ 1528 updateButtonText: function() { 1529 var options = this.getSelected(); 1530 1531 // First update the displayed button text. 1532 if (this.options.enableHTML) { 1533 $('.multiselect .multiselect-selected-text', this.$container).html(this.options.buttonText(options, this.$select)); 1534 } 1535 else { 1536 $('.multiselect .multiselect-selected-text', this.$container).text(this.options.buttonText(options, this.$select)); 1537 } 1538 1539 // Now update the title attribute of the button. 1540 $('.multiselect', this.$container).attr('title', this.options.buttonTitle(options, this.$select)); 1541 }, 1542 1543 /** 1544 * Get all selected options. 1545 * 1546 * @returns {jQUery} 1547 */ 1548 getSelected: function() { 1549 return $('option', this.$select).filter(":selected"); 1550 }, 1551 1552 /** 1553 * Gets a select option by its value. 1554 * 1555 * @param {String} value 1556 * @returns {jQuery} 1557 */ 1558 getOptionByValue: function (value) { 1559 1560 var options = $('option', this.$select); 1561 var valueToCompare = value.toString(); 1562 1563 for (var i = 0; i < options.length; i = i + 1) { 1564 var option = options[i]; 1565 if (option.value === valueToCompare) { 1566 return $(option); 1567 } 1568 } 1569 }, 1570 1571 /** 1572 * Get the input (radio/checkbox) by its value. 1573 * 1574 * @param {String} value 1575 * @returns {jQuery} 1576 */ 1577 getInputByValue: function (value) { 1578 1579 var checkboxes = $('li input', this.$ul); 1580 var valueToCompare = value.toString(); 1581 1582 for (var i = 0; i < checkboxes.length; i = i + 1) { 1583 var checkbox = checkboxes[i]; 1584 if (checkbox.value === valueToCompare) { 1585 return $(checkbox); 1586 } 1587 } 1588 }, 1589 1590 /** 1591 * Used for knockout integration. 1592 */ 1593 updateOriginalOptions: function() { 1594 this.originalOptions = this.$select.clone()[0].options; 1595 }, 1596 1597 asyncFunction: function(callback, timeout, self) { 1598 var args = Array.prototype.slice.call(arguments, 3); 1599 return setTimeout(function() { 1600 callback.apply(self || window, args); 1601 }, timeout); 1602 }, 1603 1604 setAllSelectedText: function(allSelectedText) { 1605 this.options.allSelectedText = allSelectedText; 1606 this.updateButtonText(); 1607 } 1608 }; 1609 1610 $.fn.multiselect = function(option, parameter, extraOptions) { 1611 return this.each(function() { 1612 var data = $(this).data('multiselect'); 1613 var options = typeof option === 'object' && option; 1614 1615 // Initialize the multiselect. 1616 if (!data) { 1617 data = new Multiselect(this, options); 1618 $(this).data('multiselect', data); 1619 } 1620 1621 // Call multiselect method. 1622 if (typeof option === 'string') { 1623 data[option](parameter, extraOptions); 1624 1625 if (option === 'destroy') { 1626 $(this).data('multiselect', false); 1627 } 1628 } 1629 }); 1630 }; 1631 1632 $.fn.multiselect.Constructor = Multiselect; 1633 1634 $(function() { 1635 $("select[data-role=multiselect]").multiselect(); 1636 }); 1637 1638}(window.jQuery); 1639 1640;/* 1641 Bootstrap - File Input 1642 ====================== 1643 1644 This is meant to convert all file input tags into a set of elements that displays consistently in all browsers. 1645 1646 Converts all 1647 <input type="file"> 1648 into Bootstrap buttons 1649 <a class="btn">Browse</a> 1650 1651*/ 1652(function($) { 1653 1654$.fn.bootstrapFileInput = function() { 1655 1656 this.each(function(i,elem){ 1657 1658 var $elem = $(elem); 1659 1660 // Maybe some fields don't need to be standardized. 1661 if (typeof $elem.attr('data-bfi-disabled') != 'undefined') { 1662 return; 1663 } 1664 1665 // Set the word to be displayed on the button 1666 var buttonWord = 'Browse'; 1667 1668 if (typeof $elem.attr('title') != 'undefined') { 1669 buttonWord = $elem.attr('title'); 1670 } 1671 1672 var className = ''; 1673 1674 if (!!$elem.attr('class')) { 1675 className = ' ' + $elem.attr('class'); 1676 } 1677 1678 // Now we're going to wrap that input field with a Bootstrap button. 1679 // The input will actually still be there, it will just be float above and transparent (done with the CSS). 1680 $elem.wrap('<a class="file-input-wrapper btn btn-default ' + className + '"></a>
1680').parent().prepend($('<span></span>').html(buttonWord)); 1681 }) 1682 1683 // After we have found all of the file inputs let's apply a listener for tracking the mouse movement. 1684 // This is important because the in order to give the illusion that this is a button in FF we actually need to move the button from the file input under the cursor. Ugh. 1685 .promise().done( function(){ 1686 1687 // As the cursor moves over our new Bootstrap button we need to adjust the position of the invisible file input Browse button to be under the cursor. 1688 // This gives us the pointer cursor that FF denies us 1689 $('.file-input-wrapper').mousemove(function(cursor) { 1690 1691 var input, wrapper, 1692 wrapperX, wrapperY, 1693 inputWidth, inputHeight, 1694 cursorX, cursorY; 1695 1696 // This wrapper element (the button surround this file input) 1697 wrapper = $(this); 1698 // The invisible file input element 1699 input = wrapper.find("input"); 1700 // The left-most position of the wrapper 1701 wrapperX = wrapper.offset().left; 1702 // The top-most position of the wrapper 1703 wrapperY = wrapper.offset().top; 1704 // The with of the browsers input field 1705 inputWidth= input.width(); 1706 // The height of the browsers input field 1707 inputHeight= input.height(); 1708 //The position of the cursor in the wrapper 1709 cursorX = cursor.pageX; 1710 cursorY = cursor.pageY; 1711 1712 //The positions we are to move the invisible file input 1713 // The 20 at the end is an arbitrary number of pixels that we can shift the input such that cursor is not pointing at the end of the Browse button but somewhere nearer the middle 1714 moveInputX = cursorX - wrapperX - inputWidth + 20; 1715 // Slides the invisible input Browse button to be positioned middle under the cursor 1716 moveInputY = cursorY- wrapperY - (inputHeight/2); 1717 1718 // Apply the positioning styles to actually move the invisible file input 1719 input.css({ 1720 left:moveInputX, 1721 top:moveInputY 1722 }); 1723 }); 1724 1725 $('body').on('change', '.file-input-wrapper input[type=file]', function(){ 1726 1727 var fileName; 1728 fileName = $(this).val(); 1729 1730 // Remove any previous file names 1731 $(this).parent().next('.file-input-name').remove(); 1732 if (!!$(this).prop('files') && $(this).prop('files').length > 1) { 1733 fileName = $(this)[0].files.length+' files'; 1734 //$(this).parent().after('<span class="file-input-name">'+$(this)[0].files.length+' files</span>'); 1735 } 1736 else { 1737 // var fakepath = 'C:\\fakepath\\'; 1738 // fileName = $(this).val().replace('C:\\fakepath\\',''); 1739 fileName = fileName.substring(fileName.lastIndexOf('\\')+1,fileName.length); 1740 } 1741 1742 var selectedFileNamePlacement = $(this).data('filename-placement') || 'outside'; 1743 if (selectedFileNamePlacement === 'inside') { 1744 // Print the fileName inside 1745 $(this).siblings('span').html(fileName); 1746 $(this).attr('title', fileName); 1747 } else if (selectedFileNamePlacement === 'outside') { 1748 // Print the fileName aside (right after the the button) 1749 $(this).parent().after('<span class="file-input-name">'+fileName+'</span>'); 1750 } else { 1751 console.log('Error in bootstrap-file-input plugin : unknown placement [' + selectedFileNamePlacement + '] for selected filename'); 1752 } 1753 }); 1754 1755 }); 1756 1757}; 1758 1759// Add the styles before the first stylesheet 1760// This ensures they can be easily overridden with developer styles 1761var cssHtml = '<style>'+ 1762 '.file-input-wrapper { overflow: hidden; position: relative; cursor: pointer; z-index: 1; }'+ 1763 '.file-input-wrapper input[type=file], .file-input-wrapper input[type=file]:focus, .file-input-wrapper input[type=file]:hover { position: absolute; top: 0; left: 0; cursor: pointer; opacity: 0; filter: alpha(opacity=0); z-index: 99; outline: 0; }'+ 1764 '.file-input-name { margin-left: 8px; }'+ 1765 '</style>'; 1766$('link[rel=stylesheet]').eq(0).before(cssHtml); 1767 1768})(jQuery); 1769 1770;/* =========================================================== 1771 * Bootstrap: inputmask.js v3.1.0 1772 * http://jasny.github.io/bootstrap/javascript/#inputmask 1773 * 1774 * Based on Masked Input plugin by Josh Bush (digitalbush.com) 1775 * ===========================================================
1776 * Copyright 2012-2014 Arnold Daniels 1777 * 1778 * Licensed under the Apache License, Version 2.0 (the "License") 1779 * you may not use this file except in compliance with the License. 1780 * You may obtain a copy of the License at 1781 * 1782 * http://www.apache.org/licenses/LICENSE-2.0 1783 * 1784 * Unless required by applicable law or agreed to in writing, software 1785 * distributed under the License is distributed on an "AS IS" BASIS, 1786 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1787 * See the License for the specific language governing permissions and 1788 * limitations under the License. 1789 * ========================================================== */ 1790 1791+function ($) { "use strict"; 1792 1793 var isIphone = (window.orientation !== undefined) 1794 var isAndroid = navigator.userAgent.toLowerCase().indexOf("android") > -1 1795 var isIE = window.navigator.appName == 'Microsoft Internet Explorer' 1796 1797 // INPUTMASK PUBLIC CLASS DEFINITION 1798 // ================================= 1799 1800 var Inputmask = function (element, options) { 1801 if (isAndroid) return // No support because caret positioning doesn't work on Android 1802 1803 this.$element = $(element) 1804 this.options = $.extend({}, Inputmask.DEFAULTS, options) 1805 this.mask = String(this.options.mask) 1806 1807 this.init() 1808 this.listen() 1809 1810 this.checkVal() //Perform initial check for existing values 1811 } 1812 1813 Inputmask.DEFAULTS = { 1814 mask: "", 1815 placeholder: "_", 1816 definitions: { 1817 '9': "[0-9]", 1818 'a': "[A-Za-z]", 1819 'w': "[A-Za-z0-9]", 1820 '*': "." 1821 } 1822 } 1823 1824 Inputmask.prototype.init = function() { 1825 var defs = this.options.definitions 1826 var len = this.mask.length 1827 1828 this.tests = [] 1829 this.partialPosition = this.mask.length 1830 this.firstNonMaskPos = null 1831 1832 $.each(this.mask.split(""), $.proxy(function(i, c) { 1833 if (c == '?') { 1834 len-- 1835 this.partialPosition = i 1836 } else if (defs[c]) { 1837 this.tests.push(new RegExp(defs[c])) 1838 if (this.firstNonMaskPos === null) 1839 this.firstNonMaskPos = this.tests.length - 1 1840 } else { 1841 this.tests.push(null) 1842 } 1843 }, this)) 1844 1845 this.buffer = $.map(this.mask.split(""), $.proxy(function(c, i) { 1846 if (c != '?') return defs[c] ? this.options.placeholder : c 1847 }, this)) 1848 1849 this.focusText = this.$element.val() 1850 1851 this.$element.data("rawMaskFn", $.proxy(function() { 1852 return $.map(this.buffer, function(c, i) { 1853 return this.tests[i] && c != this.options.placeholder ? c : null 1854 }).join('') 1855 }, this)) 1856 } 1857 1858 Inputmask.prototype.listen = function() { 1859 if (this.$element.attr("readonly")) return 1860 1861 var pasteEventName = (isIE ? 'paste' : 'input') + ".mask" 1862 1863 this.$element 1864 .on("unmask.bs.inputmask", $.proxy(this.unmask, this)) 1865 1866 .on("focus.bs.inputmask", $.proxy(this.focusEvent, this)) 1867 .on("blur.bs.inputmask", $.proxy(this.blurEvent, this)) 1868 1869 .on("keydown.bs.inputmask", $.proxy(this.keydownEvent, this)) 1870 .on("keypress.bs.inputmask", $.proxy(this.keypressEvent, this)) 1871 1872 .on(pasteEventName, $.proxy(this.pasteEvent, this)) 1873 } 1874 1875 //Helper Function for Caret positioning 1876 Inputmask.prototype.caret = function(begin, end) { 1877 if (this.$element.length === 0) return 1878 if (typeof begin == 'number') { 1879 end = (typeof end == 'number') ? end : begin 1880 return this.$element.each(function() { 1881 if (this.setSelectionRange) { 1882 this.setSelectionRange(begin, end) 1883 } else if (this.createTextRange) { 1884 var range = this.createTextRange() 1885 range.collapse(true) 1886 range.moveEnd('character', end) 1887 range.moveStart('character', begin) 1888 range.select() 1889 } 1890 }) 1891 } else { 1892 if (this.$element[0].setSelectionRange) { 1893 begin = this.$element[0].selectionStart 1894 end = this.$element[0].selectionEnd 1895 } else if (document.selection && document.selection.createRange) { 1896 var range = document.selection.createRange() 1897 begin = 0 - range.duplicate().moveStart('character', -100000) 1898 end = begin + range.text.length 1899 } 1900 return { 1901 begin: begin, 1902 end: end 1903 } 1904 } 1905 } 1906 1907 Inputmask.prototype.seekNext = function(pos) { 1908 var len = this.mask.length 1909 while (++pos <= len && !this.tests[pos]); 1910 1911 return pos 1912 } 1913 1914 Inputmask.prototype.seekPrev = function(pos) { 1915 while (--pos >= 0 && !this.tests[pos]); 1916 1917 return pos 1918 } 1919 1920 Inputmask.prototype.shiftL = function(begin,end) { 1921 var len = this.mask.length 1922 1923 if (begin < 0) return 1924 1925 for (var i = begin, j = this.seekNext(end); i < len; i++) { 1926 if (this.tests[i]) { 1927 if (j < len && this.tests[i].test(this.buffer[j])) { 1928 this.buffer[i] = this.buffer[j] 1929 this.buffer[j] = this.options.placeholder 1930 } else 1931 break 1932 j = this.seekNext(j) 1933 } 1934 } 1935 this.writeBuffer() 1936 this.caret(Math.max(this.firstNonMaskPos, begin)) 1937 } 1938 1939 Inputmask.prototype.shiftR = function(pos) { 1940 var len = this.mask.length 1941 1942 for (var i = pos, c = this.options.placeholder; i < len; i++) { 1943 if (this.tests[i]) { 1944 var j = this.seekNext(i) 1945 var t = this.buffer[i] 1946 this.buffer[i] = c 1947 if (j < len && this.tests[j].test(t)) 1948 c = t 1949 else 1950 break 1951 } 1952 } 1953 }, 1954 1955 Inputmask.prototype.unmask = function() { 1956 this.$element 1957 .unbind(".mask") 1958 .removeData("inputmask") 1959 } 1960
1961 Inputmask.prototype.focusEvent = function() { 1962 this.focusText = this.$element.val() 1963 var len = this.mask.length 1964 var pos = this.checkVal() 1965 this.writeBuffer() 1966 1967 var that = this 1968 var moveCaret = function() { 1969 if (pos == len) 1970 that.caret(0, pos) 1971 else 1972 that.caret(pos) 1973 } 1974 1975 moveCaret() 1976 setTimeout(moveCaret, 50) 1977 } 1978 1979 Inputmask.prototype.blurEvent = function() { 1980 this.checkVal() 1981 if (this.$element.val() !== this.focusText) 1982 this.$element.trigger('change') 1983 } 1984 1985 Inputmask.prototype.keydownEvent = function(e) { 1986 var k = e.which 1987 1988 //backspace, delete, and escape get special treatment 1989 if (k == 8 || k == 46 || (isIphone && k == 127)) { 1990 var pos = this.caret(), 1991 begin = pos.begin, 1992 end = pos.end 1993 1994 if (end - begin === 0) { 1995 begin = k != 46 ? this.seekPrev(begin) : (end = this.seekNext(begin - 1)) 1996 end = k == 46 ? this.seekNext(end) : end 1997 } 1998 this.clearBuffer(begin, end) 1999 this.shiftL(begin, end - 1) 2000 2001 return false 2002 } else if (k == 27) {//escape 2003 this.$element.val(this.focusText) 2004 this.caret(0, this.checkVal()) 2005 return false 2006 } 2007 } 2008 2009 Inputmask.prototype.keypressEvent = function(e) { 2010 var len = this.mask.length 2011 2012 var k = e.which, 2013 pos = this.caret() 2014 2015 if (e.ctrlKey || e.altKey || e.metaKey || k < 32) {//Ignore 2016 return true 2017 } else if (k) { 2018 if (pos.end - pos.begin !== 0) { 2019 this.clearBuffer(pos.begin, pos.end) 2020 this.shiftL(pos.begin, pos.end - 1) 2021 } 2022 2023 var p = this.seekNext(pos.begin - 1) 2024 if (p < len) { 2025 var c = String.fromCharCode(k) 2026 if (this.tests[p].test(c)) { 2027 this.shiftR(p) 2028 this.buffer[p] = c 2029 this.writeBuffer() 2030 var next = this.seekNext(p) 2031 this.caret(next) 2032 } 2033 } 2034 return false 2035 } 2036 } 2037 2038 Inputmask.prototype.pasteEvent = function() { 2039 var that = this 2040 2041 setTimeout(function() { 2042 that.caret(that.checkVal(true)) 2043 }, 0) 2044 } 2045 2046 Inputmask.prototype.clearBuffer = function(start, end) { 2047 var len = this.mask.length 2048 2049 for (var i = start; i < end && i < len; i++) { 2050 if (this.tests[i]) 2051 this.buffer[i] = this.options.placeholder 2052 } 2053 } 2054 2055 Inputmask.prototype.writeBuffer = function() { 2056 return this.$element.val(this.buffer.join('')).val() 2057 } 2058 2059 Inputmask.prototype.checkVal = function(allow) { 2060 var len = this.mask.length 2061 //try to place characters where they belong 2062 var test = this.$element.val() 2063 var lastMatch = -1 2064 2065 for (var i = 0, pos = 0; i < len; i++) { 2066 if (this.tests[i]) { 2067 this.buffer[i] = this.options.placeholder 2068 while (pos++ < test.length) { 2069 var c = test.charAt(pos - 1) 2070 if (this.tests[i].test(c)) { 2071 this.buffer[i] = c 2072 lastMatch = i 2073 break 2074 } 2075 } 2076 if (pos > test.length) 2077 break 2078 } else if (this.buffer[i] == test.charAt(pos) && i != this.partialPosition) { 2079 pos++ 2080 lastMatch = i 2081 } 2082 } 2083 if (!allow && lastMatch + 1 < this.partialPosition) { 2084 this.$element.val("") 2085 this.clearBuffer(0, len) 2086 } else if (allow || lastMatch + 1 >= this.partialPosition) { 2087 this.writeBuffer() 2088 if (!allow) this.$element.val(this.$element.val().substring(0, lastMatch + 1)) 2089 } 2090 return (this.partialPosition ? i : this.firstNonMaskPos) 2091 } 2092 2093 2094 // INPUTMASK PLUGIN DEFINITION 2095 // =========================== 2096 2097 var old = $.fn.inputmask 2098 2099 $.fn.inputmask = function (options) { 2100 return this.each(function () { 2101 var $this = $(this) 2102 var data = $this.data('bs.inputmask') 2103 2104 if (!data) $this.data('bs.inputmask', (data = new Inputmask(this, options))) 2105 }) 2106 } 2107 2108 $.fn.inputmask.Constructor = Inputmask 2109 2110 2111 // INPUTMASK NO CONFLICT 2112 // ==================== 2113 2114 $.fn.inputmask.noConflict = function () { 2115 $.fn.inputmask = old 2116 return this 2117 } 2118 2119 2120 // INPUTMASK DATA-API 2121 // ================== 2122 2123 $(document).on('focus.bs.inputmask.data-api', '[data-mask]', function (e) { 2124 var $this = $(this) 2125 if ($this.data('bs.inputmask')) return 2126 $this.inputmask($this.data()) 2127 }) 2128 2129}(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.