1;(function (root, factory) { 2 // AMD. 3 if (typeof define === "function" && define.amd) 4 define(["jquery"], factory); 5 6 // Node, CommonJS-like. 7 else if (typeof exports === "object") 8 factory(require("jquery")); 9 10 // Browser globals. 11 else 12 factory(root.jQuery); 13}(this, function ($) { 14 // Strict mode. 15 "use strict"; 16 17 // @todo 18 // - markup: datalist > option 19 // - markup: select > optgroup > option 20 21 /** 22 * Initialize GridPicker. 23 * 24 * @param {HTMLSelectElement} element select element node 25 * @param {Object} options see GridPicker.prototype._defaults 26 * @return {Void} 27 */ 28 var GridPicker = function (element, options) { 29 if (!(this instanceof GridPicker)) 30 throw "GridPicker: GridPicker is a constructor."; 31 if (!(element instanceof HTMLSelectElement)) 32 throw "GridPicker: element argument must be of type HTMLSelectElement."; 33 34 this._element = element; 35 this._options = options; 36 37 this._init(); 38 } 39 40 /** 41 * GridPicker prototype. 42 * 43 * @type {Object} 44 */ 45 GridPicker.prototype = { 46 /** 47 * Default options. 48 * 49 * @type {Object} 50 */ 51 _defaults: { 52 /** 53 * Render method. 54 * 55 * If not defined (Null) the default render 56 * method will be used. The first argument 57 * in function is HTMLOptionElement, and the 58 * result should be any HTMLElement. 59 * 60 * Example usage: 61 * 62 * $(selector).gridPicker({ 63 * render: function(element) { 64 * return $("<a />") 65 * .attr("href", "#") 66 * .attr("title", label) 67 * .text(label) 68 * .get(0); 69 * } 70 * } 71 * 72 * @type {Function|Null} 73 */ 74 render: null, 75 76 /** 77 * Can item be selected. 78 * 79 * If not defined (Null) the default canSelect 80 * method will be used. It can be True, False 81 * or Function. The first argument in function 82 * is HTMLOptionElement, and the result should 83 * be True|False. 84 * 85 * Example usage: 86 * 87 * $(selector).gridPicker({ 88 * canSelect: function(element) { 89 * return !$(element).is(":disabled"); 90 * } 91 * } 92 * 93 * @type {Function|Boolean|Null} 94 */ 95 canSelect: null, 96 97 /** 98 * Can item be unselected. 99 * 100 * If not defined (Null) the default canUnselect 101 * method will be used. It can be True, False 102 * or Function. The first argument in function 103 * is HTMLOptionElement, and the result should 104 * be True|False. 105 * 106 * Example usage: 107 * 108 * $(selector).gridPicker({ 109 * canUnselect: function(element) { 110 * return typeof this._$ui.element.attr("multiple") !== "undefined"; 111 * } 112 * } 113 * 114 * @type {Function|Boolean|Null} 115 */ 116 canUnselect: null, 117 }, 118 119 /** 120 * Constructor. 121 * 122 * @return {Void} 123 */ 124 _init: function () { 125 // Already instanced. 126 if ($(this._element).data("jquery-grid-picker")) 127 return; 128 129 // Init all. 130 this._initElement(); 131 this._initWidget(); 132 this._initOptions(); 133 this._initUi(); 134 this._initObserver(); 135 136 // Render items. 137 this.refresh(); 138 }, 139 140 /** 141 * Initialize element. 142 * 143 * @return {Void} 144 */ 145 _initElement: function () { 146 this._element = $(this._element) 147 .addClass("jquery-grid-picker") 148 .off(".jquerygridpicker") 149 .on("change.jquerygridpicker", this._handleElementChange.bind(this)) 150 .data("jquery-grid-picker", this) 151 .get(0); 152 }, 153 154 /** 155 * Initialize widget. 156 * 157 * @return {Void} 158 */ 159 _initWidget: function () { 160 this._widget = $("<ul />") 161 .addClass("jquery-grid-picker-widget") 162 .on("click.jquerygridpicker", ".jquery-grid-picker-item", this._handleItem
162Click.bind(this)) 163 .insertAfter(this.element) 164 .get(0); 165 }, 166 167 /** 168 * Initialize options. 169 * 170 * @return {Void} 171 */ 172 _initOptions: function () { 173 this._options = $.extend({}, this._defaults, this._options); 174 175 // Delete invalid option keys. 176 for (var key in this._options) { 177 if (!this._defaults.hasOwnProperty(key)) 178 delete this._options[key]; 179 } 180 }, 181 182 /** 183 * Initialize ui. 184 * 185 * @return {Void} 186 */ 187 _initUi: function () { 188 this._$ui = { 189 element: $(this.element), 190 widget: $(this.widget), 191 items: $(null), 192 }; 193 }, 194 195 /** 196 * Initialize mutation observer. 197 * 198 * @return {Void} 199 */ 200 _initObserver: function () { 201 this._observer = null; 202 if (typeof MutationObserver === "undefined") 203 return; 204 205 this._observer = new MutationObserver(this._handleMutation.bind(this)); 206 this._observer.observe(this.element, { 207 attributes: true, 208 childList: true, 209 subtree: true, 210 characterData: true, 211 }); 212 }, 213 214 /** 215 * Destructor. 216 * 217 * @return {Void} 218 */ 219 destroy: function () { 220 if (this._observer) 221 this._observer.disconnect(); 222 223 this._$ui.element 224 .off(".jquerygridpicker") 225 .removeClass("jquery-grid-picker") 226 .removeData("jquery-grid-picker"); 227 this._$ui.widget 228 .remove(); 229 230 this._$ui = null; 231 this._observer = null; 232 this._options = null; 233 this._widget = null; 234 this._element = null; 235 }, 236 237 /** 238 * Element property getter. 239 * 240 * @return {HTMLSelectElement} 241 */ 242 get element() { 243 return this._element; 244 }, 245 246 /** 247 * Widget property getter. 248 * 249 * @return {HTMLElement} 250 */ 251 get widget() { 252 return this._widget; 253 }, 254 255 /** 256 * Get option. 257 * 258 * @param {String} key 259 * @return {Mixed} 260 */ 261 getOption: function (key) { 262 return this._options[key]; 263 }, 264 265 /** 266 * Set option. 267 * 268 * @param {String} key 269 * @param {Mixed} value 270 * @return {Void} 271 */ 272 setOption: function (key, value) { 273 if (this._defaults.hasOwnProperty(key)) 274 this._options[key] = value; 275 }, 276 277 /** 278 * Element value. 279 * 280 * @return {String} 281 */ 282 value: function () { 283 return this._$ui.element.val(); 284 }, 285 286 /** 287 * Reset element value to null. 288 * 289 * @return {Void} 290 */ 291 reset: function () { 292 this._$ui.element.val(null); 293 }, 294 295 /** 296 * Refresh (render) option list. 297 * 298 * @return {Void} 299 */ 300 refresh: function () { 301 this._$ui.widget.empty(); 302 this._$ui.items = $(null); 303 304 this._$ui.element 305 .find("option") 306 .each(function (index, element) { 307 this._syncItem(element); 308 }.bind(this)); 309 310 this._handleElementChange(); 311 }, 312 313 /** 314 * Sync select option and widget list item. 315 * 316 * @param {HTMLOptionElement} element 317 * @return {Void} 318 */ 319 _syncItem: function (element) { 320 // Clear old element widget item (if exists). 321 var $element = $(element), 322 attached = !!$element.closest(this.element).length, 323 value = $element.val(), 324 item = this._$ui.items.filter('[data-jquery-grid-picker-value="' + this._selectorEscape(value) + '"]').get(0); 325 this._$ui.items = this._$ui.items.not(item); 326 $(item).remove(); 327 328 // Element is detached, nothing else to do here. 329 if (!attached) 330 return; 331 332 // Render item content from render option. 333 var content = null, 334 render = this.getOption("render"); 335 if (typeof render === "function") 336 content = render.call(this, element); 337 else 338 content = this._renderItemContent(element); 339 if (!content) 340 return; 341 342 var index = $element.index(), 343 selected = $element.prop("selected"), 344 hidden = $element.css("display") === "none", 345 disabled = $element.is(":disabled"); 346 347 // Render new element widget item. 348 item = this._renderItemWrapper(element); 349 $(item) 350 .addClass("jquery-grid-picker-item") 351 .addClass("jquery-grid-picker-item-" + (selected ? "selected" : "temp")) 352 .addClass("jquery-grid-picker-item-" + (hidden ? "hidden" : "temp")) 353 .addClass("jquery-grid-picker-item-" + (disabled ? "disabled" : "temp")) 354 .removeClass("jquery-grid-picker-item-temp") 355 .attr("data-jquery-grid-picker-value", value) 356 .removeAttr("data-jquery-grid-picker-item-temp"); 357 $(content) 358 .addClass("jquery-grid-picker-item-content") 359 .appendTo(item); 360 361 // ...and append it to widget. 362 if (!index) 363 this._$ui.widget.prepend(item); 364 else 365 this._$ui.widget.find(".jquery-grid-picker-item:nth-child(" + index + ")").after(item); 366 367 // Refresh ui. 368 this._$ui.items = this._$ui.items 369 .add(item); 370 }, 371 372 /**
373 * Render list item wrapper. 374 * 375 * @param {HTMLOptionElement} element 376 * @return {HTMLElement} 377 */ 378 _renderItemWrapper: function (element) { 379 return $("<li />").get(0); 380 }, 381 382 /** 383 * Render list item (default) content: 384 * this is a render fallback if no render 385 * option is specified. 386 * 387 * @param {HTMLOptionElement} element 388 * @return {HTMLElement} 389 */ 390 _renderItemContent: function (element) { 391 var $element = $(element), 392 label = $element.attr("label") || $element.text(); 393 return $("<a />") 394 .attr("href", "#") 395 .attr("title", label) 396 .attr("data-tippy-content", label) 397 .attr("draggable", "false") 398 .text(label) 399 .get(0); 400 }, 401 402 /** 403 * Can select item. 404 * 405 * @param {HTMLOptionElement} element 406 * @return {Boolean} 407 */ 408 _canSelect: function (element) { 409 var option = this.getOption("canSelect"); 410 if (typeof option === "boolean") 411 return option; 412 else if (typeof option === "function") 413 return !!option.call(this, element); 414 415 return !$(element).is(":disabled"); 416 }, 417 418 /** 419 * Can unselect item. 420 * 421 * @param {HTMLOptionElement} element 422 * @return {Boolean} 423 */ 424 _canUnselect: function (element) { 425 var option = this.getOption("canUnselect"); 426 if (typeof option === "boolean") 427 return option; 428 else if (typeof option === "function") 429 return !!option.call(this, element); 430 431 return this.element.hasAttribute("multiple"); 432 }, 433 434 /** 435 * Escape string so we can use it as css selector. 436 * 437 * @param {String} str 438 * @return {String} 439 */ 440 _selectorEscape: function (str) { 441 return str.toString() 442 .replace(/"/g, "\\\""); 443 }, 444 445 /** 446 * Mutation observer callback: 447 * sync select option and widget list item. 448 * 449 * @param {Array} e 450 * @return {Void} 451 */ 452 _handleMutation: function (e) { 453 // Element list that needs sync. It would be easier 454 // to use jQuery object here, but jQuery's list of 455 // elements is sorted. We need to apply sync method 456 // on elements as provided in MutationRecord list. 457 var elements = [], 458 addToElements = function (element) { 459 $(element) 460 .closest("option") 461 .each(function (index, value) { 462 if (elements.indexOf(value) === -1) 463 elements.push(value); 464 }); 465 }; 466 467 // Iterate mutation record list and find elements 468 // that need sync. 469 e.forEach(function (record) { 470 if (record.type === "childList") { 471 addToElements(record.removedNodes); 472 addToElements(record.addedNodes); 473 } else if ((record.type === "attributes" && record.target !== this.element) || (record.type === "characterData")) 474 addToElements(record.target); 475 }.bind(this)); 476 477 // Sync mutated elements. 478 elements.forEach(function (element) { 479 this._syncItem(element); 480 }.bind(this)); 481 }, 482 483 /** 484 * Element change event handler. 485 * 486 * @param {Event} e 487 * @return {Void} 488 */ 489 _handleElementChange: function (e) { 490 var value = this.value(), 491 filter = null; 492 493 if (typeof value === "string") 494 filter = '[data-jquery-grid-picker-value="' + this._selectorEscape(value) + '"]'; 495 else if (value instanceof Array) 496 filter = value 497 .map(function (item) { 498 return '[data-jquery-grid-picker-value="' + this._selectorEscape(item) + '"]'; 499 }.bind(this)) 500 .join(","); 501 502 this._$ui.items 503 .removeClass("jquery-grid-picker-item-selected") 504 .filter(filter) 505 .addClass("jquery-grid-picker-item-selected"); 506 }, 507 508 /** 509 * Item click event handler. 510 * 511 * @param {Event} e 512 * @return {Void} 513 */
514 _handleItemClick: function (e) { 515 e.preventDefault(); 516 517 var target = e.currentTarget, 518 value = $(target).attr("data-jquery-grid-picker-value") || "", 519 $option = this._$ui.element.find('option[value="' + this._selectorEscape(value) + '"]'), 520 option = $option.get(0), 521 isSelected = $option.is(":selected"); 522 523 // Unselect selected. 524 if (isSelected && this._canUnselect(option)) { 525 // Non multiple select elements can not have all the 526 // options unselected (it will default to first option), 527 // so we will reset it's value. 528 var length = this._$ui.element.find("option:selected").length; 529 if (length - 1 === 0) 530 this.reset(); 531 else 532 $option.prop("selected", false); 533 } 534 535 // Select unselected. 536 else if (!isSelected && this._canSelect(option)) 537 $option.prop("selected", true); 538 539 // Select/unselect fail, return. 540 else 541 return; 542 543 // Trigger change and let change event handler 544 // do the rest. 545 this._$ui.element 546 .trigger("change"); 547 }, 548 }; 549 550 // Re-assign constuctor. 551 GridPicker.prototype.constructor = GridPicker; 552 553 /** 554 * jQuery gridPicker plugin: 555 * display HTMLSelectElement as grid with clickable 556 * (select/unselect) items. 557 * 558 * Instance new GridPicker on element(s) 559 * >> $(selector).gridPicker(options); 560 * 561 * ...execute GridPicker method 562 * >> $(selector) 563 * .gridPicker("setOption", "canUnselect", true) 564 * .gridPicker("reset"); 565 * 566 * ...or access GridPicker property 567 * >> var widget = $(selector).gridPicker("widget"); 568 * 569 * @param {Mixed} options 570 * @return {Mixed} 571 */ 572 $.fn.gridPicker = function (options) { 573 var classObject = GridPicker, 574 className = "GridPicker", 575 store = "jquery-grid-picker", 576 args = Array.prototype.slice.call(arguments, 1), 577 $this = $(this); 578 579 // Iterate all 580 $this.each(function () { 581 // Get instance (or create new one). 582 var instance = $(this).data(store); 583 if (!instance) 584 instance = new classObject(this, typeof options === "object" ? options : {}); 585 586 // Access properties. 587 if (typeof options === "string") { 588 var exists = options in instance, 589 isPrivate = options.substr(0, 1) === "_", 590 type = typeof instance[options]; 591 592 // Property is function, execute it. 593 if (exists && !isPrivate && type === "function" && instance[options] !== Object.prototype[options] && options !== "constructor") { 594 var result = instance[options].apply(instance, args); 595 596 // Function returned result (non undefined), store 597 // result, exit loop. 598 if (typeof result !== "undefined") { 599 $this = result; 600 return false; 601 } 602 } 603 604 // Property as getter (get, store result, exit loop). 605 else if (exists && !isPrivate && type !== "function" && !args.length) { 606 $this = instance[options]; 607 return false; 608 } 609 610 // Property as setter (set, continue). 611 else if (exists && !isPrivate && type !== "function") { 612 instance[options] = args[0]; 613 } 614 615 // Invalid option argument. 616 else { 617 throw className + ": " + options + " is not a valid " + className + " property."; 618 } 619 } 620 }); 621 622 // ...finally. 623 return $this; 624 }; 625}));
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.