1/*! 2 Wookmark plugin 3 @name wookmark.js 4 @author Christoph Ono ([email protected] or @gbks) 5 @author Sebastian Helzle ([email protected] or @sebobo) 6 @version 2.1.2 7 @date 05/05/2016 8 @category jQuery plugin 9 @copyright (c) 2009-2016 Christoph Ono (www.wookmark.com) 10 @license Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license. 11*/ 12/*global define, window, jQuery*/ 13/*jslint plusplus: true, bitwise: true */ 14 15(function ($) { 16(function (factory) { 17 if (typeof define === 'function' && define.amd) { 18 define(factory); 19 } else { 20 factory(); 21 } 22}(function () { 23 24 // Wookmark default options 25 // ------------------------ 26 var defaultOptions = { 27 align: 'center', 28 autoResize: true, 29 comparator: null, 30 direction: undefined, 31 ignoreInactiveItems: true, 32 inactiveClass: 'wookmark-inactive', 33 itemSelector: undefined, 34 itemWidth: 0, 35 fillEmptySpace: false, 36 flexibleWidth: 0, 37 offset: 5, 38 outerOffset: 0, 39 onLayoutChanged: undefined, 40 placeholderClass: 'wookmark-placeholder', 41 possibleFilters: [], 42 resizeDelay: 50, 43 verticalOffset: undefined 44 }; 45 46 // Helper functions 47 // ---------------- 48 49 // Bind function to set the context for the Wookmark instance function 50 function __bind(fn, me) { 51 return function () { 52 return fn.apply(me, arguments); 53 }; 54 } 55 56 // Function for executing css writes to dom on the next animation frame if supported 57 var executeNextFrame = window.requestAnimationFrame || function (callback) { callback(); }; 58 59 // Update multiple css values on an object 60 function setCSS(el, properties) { 61 var key; 62 for (key in properties) { 63 if (properties.hasOwnProperty(key)) { 64 el.style[key] = properties[key]; 65 } 66 } 67 } 68 69 // Update the css properties of multiple elements at the same time 70 // befor the browsers next animation frame. 71 // The parameter `data` has to be an array containing objects, each 72 // with the element and the desired css properties. 73 function bulkUpdateCSS(data, callback) { 74 executeNextFrame(function () { 75 var i, item; 76 for (i = 0; i < data.length; i++) { 77 item = data[i]; 78 setCSS(item.el, item.css); 79 } 80 // Run optional callback 81 if (typeof callback === 'function') { 82 executeNextFrame(callback); 83 } 84 }); 85 } 86 87 // Remove whitespace around filter names 88 function cleanFilterName(filterName) { 89 return filterName.replace(/^\s+|\s+$/g, '').toLowerCase(); 90 } 91 92 // Remove listener from an element (IE8 compatible) 93 function removeEventListener(el, eventName, handler) { 94 if (window.jQuery) { 95 $(el).off(eventName, handler); 96 } else if (el.removeEventListener) { 97 el.removeEventListener(eventName, handler); 98 } else { 99 el.detachEvent('on' + eventName, handler); 100 } 101 } 102 103 // Add listener to an element (IE8 compatible) 104 function addEventListener(el, eventName, handler) { 105 removeEventListener(el, eventName, handler); 106 107 if (window.jQuery) { 108 $(el).on(eventName, handler); 109 } else if (el.addEventListener) { 110 el.addEventListener(eventName, handler); 111 } else { 112 el.attachEvent('on' + eventName, function () { 113 handler.call(el); 114 }); 115 } 116 } 117 118 // Checks if element `el` is not visible in the browser 119 function isHidden(el) { 120 return el.offsetParent === null; 121 } 122 123 // Returns the elements height without margin 124 function getHeight(el) { 125 return el.offsetHeight; 126 } 127 128 // Returns the elements width without margin 129 function getWidth(el) { 130 return el.offsetWidth; 131 } 132 133 // Return true if element has class 134 function hasClass(el, className) { 135 if (el.classList) { 136 return el.classList.contains(className); 137 } 138 return new RegExp('(^| )' + className + '( |$)', 'gi').test(el.className); 139 } 140 141 // Add class to element (IE8+) 142 function addClass(el, className) { 143 if (el.classList) { 144 el.classList.add(className); 145 } else { 146 el.className += ' ' + className; 147 } 148 } 149 150 // Remove class from element (IE8+) 151 function removeClass(el, className) { 152 if (el.classList) { 153 el.classList.remove(className); 154 } else { 155 el.className = el.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' '); 156 } 157 } 158 159 // Get value of specified data attribute 160 function getData(el, attr, isInt, prefix) { 161 if (prefix === undefined) { 162 prefix = 'wookmark-'; 163 } 164 var val = el.getAttribute('data-' + prefix + attr); 165 if (isInt === true) { 166 return parseInt(val, 10); 167 } 168 return val; 169 } 170 171 // Set value of specified data attribute 172 function setData(el, attr, val, prefix) { 173 if (prefix === undefined) { 174 prefix = 'wookmark-'; 175 } 176 el.setAttribute('data-' + prefix + attr, val); 177 } 178 179 // Remove duplicates from given array 180 function removeDuplicates(items) { 181 var temp = {}, result = [], x, i = items.length; 182 while (i--) { 183 x = getData(items[i], 'id', true); 184 if (!temp.hasOwnProperty(x)) { 185 temp[x] = 1; 186 result.push(items[i]); 187 } 188 } 189 return result; 190 } 191 192 // Get the computed style from an element (IE 8 compatible) 193 function getStyle(el, prop) { 194 return window.getComputedStyle !== undefined ? window.getComputedStyle(el, null).getPropertyValue(prop) : el.currentStyle[prop]; 195 } 196 197 198 // IE 8 compatible indexOf 199 function indexOf(items, item) { 200 var len = items.length, i; 201 for (i = 0; i < len; i++) { 202 if (items[i] === item) { 203 return i; 204 } 205 } 206 return -1; 207 } 208 209 // Main wookmark plugin class 210 // -------------------------- 211 function Wookmark(container, options) { 212 options = options || {}; 213 214 if (typeof container === 'string') { 215 container = document.querySelector(container); 216 } 217 218 // Instance variables. 219 this.container = container; 220 this.columns = this.resizeTimer = null;
221 this.activeItemCount = 0; 222 this.placeholders = []; 223 this.itemHeightsInitialized = false; 224 this.itemHeightsDirty = false; 225 this.elementTag = 'div'; 226 227 // Bind instance methods 228 this.initItems = __bind(this.initItems, this); 229 this.updateOptions = __bind(this.updateOptions, this); 230 this.onResize = __bind(this.onResize, this); 231 this.onRefresh = __bind(this.onRefresh, this); 232 this.getItemWidth = __bind(this.getItemWidth, this); 233 this.layout = __bind(this.layout, this); 234 this.layoutFull = __bind(this.layoutFull, this); 235 this.layoutColumns = __bind(this.layoutColumns, this); 236 this.filter = __bind(this.filter, this); 237 this.clear = __bind(this.clear, this); 238 this.getActiveItems = __bind(this.getActiveItems, this); 239 this.refreshPlaceholders = __bind(this.refreshPlaceholders, this); 240 this.sortElements = __bind(this.sortElements, this); 241 this.updateFilterClasses = __bind(this.updateFilterClasses, this); 242 243 // Initialize children of the container 244 this.initItems(); 245 246 // Initial update and layout 247 this.container.style.display = 'block'; 248 this.updateOptions(options); 249 250 // Collect filter classes after items have been initialized 251 this.updateFilterClasses(); 252 253 // Listen to resize event of the browser if enabled 254 if (this.autoResize) { 255 addEventListener(window, 'resize', this.onResize); 256 } 257 258 // Listen to external refresh event 259 addEventListener(this.container, 'refreshWookmark', this.onRefresh); 260 } 261 262 // Get all valid children of the container object and store them 263 Wookmark.prototype.initItems = function () { 264 // By select all children of the container if no selector is specified 265 if (this.itemSelector === undefined) { 266 var items = [], child, children = this.container.children, 267 i = children.length; 268 while (i--) { 269 child = children[i]; 270 // Skip comment nodes on IE8 271 if (child.nodeType !== 8) { 272 // Show item 273 child.style.display = ''; 274 setData(child, 'id', i); 275 items.unshift(child); 276 } 277 } 278 this.items = items; 279 } else { 280 this.items = this.container.querySelectorAll(this.itemSelector); 281 } 282 283 if (this.items.length) { 284 this.elementTag = this.items[0].tagName; 285 } 286 this.itemHeightsDirty = true; 287 }; 288 289 // Reload all filter classes from all items and cache them 290 Wookmark.prototype.updateFilterClasses = function () { 291 // Collect filter data 292 var i = this.items.length, j, filterClasses = {}, itemFilterClasses, 293 item, filterClass, possibleFilters = this.possibleFilters, 294 k = possibleFilters.length, possibleFilter; 295 296 while (i--) { 297 item = this.items[i]; 298 299 // Read filter classes and globally store each filter class as object and the fitting items in the array 300 itemFilterClasses = JSON.parse(getData(item, 'filter-class', false, '')); 301 if (itemFilterClasses && typeof itemFilterClasses === 'object') { 302 j = itemFilterClasses.length; 303 while (j--) { 304 filterClass = cleanFilterName(itemFilterClasses[j]); 305 if (!filterClasses.hasOwnProperty(filterClass)) { 306 filterClasses[filterClass] = []; 307 } 308 filterClasses[filterClass].push(item); 309 } 310 } 311 } 312 313 while (k--) { 314 possibleFilter = cleanFilterName(possibleFilters[k]); 315 if (!filterClasses.hasOwnProperty(possibleFilter)) { 316 filterClasses[possibleFilter] = []; 317 } 318 } 319 320 this.filterClasses = filterClasses; 321 }; 322 323 // Method for updating the plugins options 324 Wookmark.prototype.updateOptions = function (options) { 325 var key; 326 this.itemHeightsDirty = true; 327 options = options || {}; 328 329 // Overwrite non existing instance variables with the ones from options or the defaults 330 for (key in defaultOptions) { 331 if (defaultOptions.hasOwnProperty(key)) { 332 if (options.hasOwnProperty(key)) { 333 this[key] = options[key]; 334 } else if (!this.hasOwnProperty(key)) { 335 this[key] = defaultOptions[key]; 336 } 337 } 338 } 339 340 // Vertical offset uses a fallback to offset 341 this.verticalOffset = this.verticalOffset || this.offset; 342 343 // Update layout so changes apply 344 this.layout(true); 345 }; 346 347 // This timer ensures that layout is not continuously called as window is being dragged. 348 Wookmark.prototype.onResize = function () { 349 clearTimeout(this.resizeTimer);
350 this.itemHeightsDirty = this.flexibleWidth !== 0; 351 this.resizeTimer = setTimeout(this.layout, this.resizeDelay); 352 }; 353 354 // Marks the items heights as dirty and does a relayout 355 Wookmark.prototype.onRefresh = function () { 356 this.itemHeightsDirty = true; 357 this.layout(); 358 }; 359 360 // Filters the active items with the given string filters. 361 // @param filters array of string 362 // @param mode 'or' or 'and' 363 Wookmark.prototype.filter = function (filters, mode, dryRun) { 364 var activeFilters = [], activeFiltersLength, activeItems = [], 365 i, j, k, filter; 366 367 filters = filters || []; 368 mode = mode || 'or'; 369 dryRun = dryRun || false; 370 371 if (filters.length) { 372 // Collect active filters 373 for (i = 0; i < filters.length; i++) { 374 filter = cleanFilterName(filters[i]); 375 if (this.filterClasses.hasOwnProperty(filter)) { 376 activeFilters.push(this.filterClasses[filter]); 377 } 378 } 379 380 // Get items for active filters with the selected mode 381 i = activeFiltersLength = activeFilters.length; 382 if (mode === 'or' || activeFiltersLength === 1) { 383 // Set all items in all active filters active 384 while (i--) { 385 activeItems = activeItems.concat(activeFilters[i]); 386 } 387 } else if (mode === 'and') { 388 var shortestFilter = activeFilters[0], itemValid = true, 389 foundInFilter, currentItem, currentFilter; 390 391 // Find shortest filter class 392 while (i--) { 393 if (activeFilters[i].length < shortestFilter.length) { 394 shortestFilter = activeFilters[i]; 395 } 396 } 397 398 // Iterate over shortest filter and find elements in other filter classes 399 shortestFilter = shortestFilter || []; 400 i = shortestFilter.length; 401 while (i--) { 402 currentItem = shortestFilter[i]; 403 j = activeFiltersLength; 404 itemValid = true; 405 406 while (j-- && itemValid) { 407 currentFilter = activeFilters[j]; 408 if (shortestFilter !== currentFilter) { 409 // Search for current item in each active filter class 410 foundInFilter = false; 411 k = currentFilter.length; 412 while (k-- && !foundInFilter) { 413 foundInFilter = currentFilter[k] === currentItem; 414 } 415 itemValid &= foundInFilter; 416 } 417 } 418 419 if (itemValid) { 420 activeItems = activeItems.concat(shortestFilter[i]); 421 } 422 } 423 } 424 425 // Remove duplicates from active items 426 if (activeFiltersLength > 1) { 427 activeItems = removeDuplicates(activeItems); 428 } 429 430 // Hide inactive items 431 if (!dryRun) { 432 i = this.items.length; 433 while (i--) { 434 if (indexOf(activeItems, this.items[i]) === -1) { 435 addClass(this.items[i], this.inactiveClass); 436 } 437 } 438 } 439 } else { 440 // Show all items if no filter is selected 441 activeItems = this.items; 442 } 443 444 // Show active items 445 if (!dryRun) { 446 i = activeItems.length; 447 while (i--) { 448 removeClass(activeItems[i], this.inactiveClass); 449 } 450 // Unset columns and refresh grid for a full layout 451 this.columns = null; 452 this.layout(); 453 } 454 return activeItems; 455 }; 456 457 // Creates or updates existing placeholders to create columns of even height 458 Wookmark.prototype.refreshPlaceholders = function (columnWidth, sideOffset) { 459 var i, 460 containerHeight = getHeight(this.container), 461 columnsLength = this.columns.length, 462 column, 463 height, 464 innerOffset, 465 lastColumnItem, 466 placeholdersHtml = '', 467 placeholder, 468 top; 469 470 // Add more placeholders if necessary 471 if (this.placeholders.length < columnsLength) { 472 for (i = 0; i < columnsLength - this.placeholders.length; i++) { 473 placeholdersHtml += '<' + this.elementTag + ' class="' + this.placeholderClass + '"/>'; 474 } 475 this.container.insertAdjacentHTML('beforeend', placeholdersHtml); 476 this.placeholders = this.container.querySelectorAll('.' + this.placeholderClass); 477 } 478 479 innerOffset = (this.offset + parseInt(getStyle(this.placeholders[0], 'border-left-width'), 10) * 2) || 0; 480 innerOffset += (parseInt(getStyle(this.placeholders[0], 'padding-left'), 10) * 2) || 0; 481 482 // Update each placeholder 483 for (i = 0; i < this.placeholders.length; i++) { 484 placeholder = this.placeholders[i]; 485 column = this.columns[i]; 486 487 if (i >= columnsLength || column.length === 0) { 488 placeholder.style.display = 'none'; 489 } else { 490 lastColumnItem = column[column.length - 1]; 491 top = getData(lastColumnItem, 'top', true) + getData(lastColumnItem, 'height', true) + this.verticalOffset; 492 height = Math.max(0, containerHeight - top - innerOffset); 493 494 setCSS(placeholder, { 495 position: 'absolute', 496 display: height > 0 ? 'block' : 'none', 497 left: (i * columnWidth + sideOffset) + 'px', 498 top: top + 'px', 499 width: (columnWidth - innerOffset) + 'px', 500 height: height + 'px' 501 }); 502 } 503 } 504 }; 505 506 // Method the get active items which are not disabled and visible 507 Wookmark.prototype.getActiveItems = function () { 508 var inactiveClass = this.inactiveClass, 509 i, 510 result = [], 511 item, 512 items = this.items; 513 514 if (this.ignoreInactiveItems) { 515 for (i = 0; i < items.length; i++) { 516 item = items[i]; 517 if (!hasClass(item, inactiveClass)) { 518 result.push(item); 519 } 520 } 521 } else { 522 return items; 523 } 524 return result; 525 }; 526 527 // Method to get the standard item width 528 Wookmark.prototype.getItemWidth = function () { 529 var itemWidth = this.itemWidth, 530 innerWidth = getWidth(this.container) - 2 * this.outerOffset, 531 flexibleWidth = this.flexibleWidth; 532 533 if (typeof itemWidth === 'function') { 534 itemWidth = this.itemWidth(); 535 } 536 537 if (this.items.length > 0 && (itemWidth === undefined || (itemWidth === 0 && !this.flexibleWidth))) { 538 itemWidth = getWidth(this.items[0]); 539 } else if (typeof itemWidth === 'string' && itemWidth.indexOf('%') >= 0) { 540 itemWidth = parseFloat(itemWidth) / 100 * innerWidth; 541 } 542 543 // Calculate flexible item width if option is set 544 if (flexibleWidth) { 545 if (typeof flexibleWidth === 'function') { 546 flexibleWidth = flexibleWidth(); 547 } 548 549 if (typeof flexibleWidth === 'string' && flexibleWidth.indexOf('%') >= 0) { 550 flexibleWidth = parseFloat(flexibleWidth) / 100 * innerWidth; 551 } 552 553 // Find highest column count 554 var paddedInnerWidth = (innerWidth + this.offset), 555 flexibleColumns = Math.floor(0.5 + paddedInnerWidth / (flexibleWidth + this.offset)), 556 fixedColumns = Math.floor(paddedInnerWidth / (itemWidth + this.offset)), 557 columns = Math.max(flexibleColumns, fixedColumns), 558 columnWidth = Math.min(flexibleWidth, Math.floor((innerWidth - (columns - 1) * this.offset) / columns)); 559 560 itemWidth = Math.max(itemWidth, columnWidth); 561 } 562 563 return itemWidth; 564 }; 565 566 // Main layout method. 567 Wookmark.prototype.layout = function (force, callback) {
568 // Do nothing if container isn't visible 569 if (!force && isHidden(this.container)) { return; } 570 571 // Calculate basic layout parameters. 572 var calculatedItemWidth = this.getItemWidth(), 573 columnWidth = calculatedItemWidth + this.offset, 574 containerWidth = getWidth(this.container), 575 innerWidth = containerWidth - 2 * this.outerOffset, 576 columns = Math.floor((innerWidth + this.offset) / columnWidth), 577 offset, 578 maxHeight = 0, 579 activeItems = this.getActiveItems(), 580 activeItemsLength = activeItems.length, 581 item; 582 583 // Cache item heights 584 if (force || this.itemHeightsDirty || !this.itemHeightsInitialized) { 585 for (var i = 0; i < activeItemsLength; i++) { 586 item = activeItems[i]; 587 588 if (this.flexibleWidth) { 589 // Stretch items to fill calculated width 590 item.style.width = calculatedItemWidth + 'px'; 591 } 592 setData(item, 'height', item.offsetHeight); 593 } 594 this.itemHeightsDirty = false; 595 this.itemHeightsInitialized = true; 596 } 597 598 // Use less columns if there are to few items 599 columns = Math.max(1, Math.min(columns, activeItemsLength)); 600 601 // Calculate the offset based on the alignment of columns to the parent container 602 offset = this.outerOffset; 603 if (this.align === 'center') { 604 offset += Math.floor(0.5 + (innerWidth - (columns * columnWidth - this.offset)) >> 1); 605 } 606 607 // Get direction for positioning 608 this.direction = this.direction || (this.align === 'right' ? 'right' : 'left'); 609 610 // If container and column count hasn't changed, we can only update the columns. 611 if (!force && this.columns !== null && this.columns.length === columns && this.activeItemCount === activeItemsLength) { 612 maxHeight = this.layoutColumns(columnWidth, offset); 613 } else { 614 maxHeight = this.layoutFull(columnWidth, columns, offset); 615 } 616 this.activeItemCount = activeItemsLength; 617 618 // Set container height to height of the grid. 619 this.container.style.height = maxHeight + 'px'; 620 621 // Update placeholders 622 if (this.fillEmptySpace) { 623 this.refreshPlaceholders(columnWidth, offset); 624 } 625 626 if (this.onLayoutChanged !== undefined && typeof this.onLayoutChanged === 'function') { 627 this.onLayoutChanged(); 628 } 629 630 // Run optional callback 631 if (typeof callback === 'function') { 632 callback(); 633 } 634 }; 635 636 // Sort elements with configurable comparator 637 Wookmark.prototype.sortElements = function (elements) { 638 return typeof this.comparator === 'function' ? elements.sort(this.comparator) : elements; 639 }; 640 641 // Perform a full layout update. 642 Wookmark.prototype.layoutFull = function (columnWidth, columns, offset) { 643 var item, k = 0, i = 0, activeItems, activeItemCount, shortest = null, shortestIndex = null, 644 sideOffset, heights = [], itemBulkCSS = [], leftAligned = this.align === 'left', self = this; 645 646 this.columns = []; 647 648 // Sort elements before layouting 649 activeItems = this.sortElements(this.getActiveItems()); 650 activeItemCount = activeItems.length; 651 652 // Prepare arrays to store height of columns and items. 653 while (heights.length < columns) { 654 heights.push(this.outerOffset); 655 this.columns.push([]); 656 } 657 658 // Loop over items. 659 while (i < activeItemCount) { 660 item = activeItems[i]; 661 662 // Find the shortest column. 663 shortest = heights[0]; 664 shortestIndex = 0; 665 for (k = 0; k < columns; k++) { 666 if (heights[k] < shortest) { 667 shortest = heights[k]; 668 shortestIndex = k; 669 } 670 } 671 setData(item, 'top', shortest); 672 673 // stick to left side if alignment is left and this is the first column 674 sideOffset = offset; 675 if (shortestIndex > 0 || !leftAligned) { 676 sideOffset += shortestIndex * columnWidth; 677 } 678 679 // Position the item. 680 itemBulkCSS[i] = { 681 el: item, 682 css: { 683 position: 'absolute', 684 top: shortest + 'px' 685 } 686 }; 687 itemBulkCSS[i].css[this.direction] = sideOffset + 'px'; 688 689 // Update column height and store item in shortest column 690 heights[shortestIndex] += getData(item, 'height', true) + this.verticalOffset; 691 this.columns[shortestIndex].push(item); 692 i++; 693 } 694 695 // Update all css in the next frame and mark container as initalised 696 bulkUpdateCSS(itemBulkCSS, function () { 697 // Initialisation done 698 if (!hasClass(self.container, 'wookmark-initialised')) { 699 addClass(self.container, 'wookmark-initialised'); 700 } 701 }); 702 703 // Return longest column 704 return Math.max.apply(Math, heights); 705 }; 706 707 // This layout method only updates the vertical position of the 708 // existing column assignments. 709 Wookmark.prototype.layoutColumns = function (columnWidth, offset) { 710 var heights = [], itemBulkCSS = [], k = 0, j = 0, 711 i = this.columns.length, currentHeight, 712 column, item, sideOffset; 713 714 while (i--) { 715 currentHeight = this.outerOffset; 716 heights.push(currentHeight); 717 column = this.columns[i]; 718 sideOffset = i * columnWidth + offset; 719 720 for (k = 0; k < column.length; k++, j++) { 721 item = column[k]; 722 setData(item, 'top', currentHeight); 723 itemBulkCSS[j] = { 724 el: item, 725 css: { 726 top: currentHeight + 'px' 727 } 728 }; 729 itemBulkCSS[j].css[this.direction] = sideOffset + 'px'; 730 731 currentHeight += getData(item, 'height', true) + this.verticalOffset; 732 } 733 heights[i] = currentHeight; 734 } 735 736 bulkUpdateCSS(itemBulkCSS); 737 738 // Return longest column 739 return Math.max.apply(Math, heights); 740 }; 741 742 // Clear event listeners and time outs and the instance itself 743 Wookmark.prototype.clear = function () { 744 clearTimeout(this.resizeTimer); 745 var i = this.placeholders.length; 746 while (i--) { 747 this.container.removeChild(this.placeholders[i]); 748 } 749 removeEventListener(window, 'resize', this.onResize); 750 removeEventListener(this.container, 'refreshWookmark', this.onRefresh); 751 }; 752 753 // Register as jQuery plugin if jQuery is loaded 754 if (window.jQuery !== undefined) { 755 jQuery.fn.wookmark = function (options) { 756 var i = this.length; 757 758 // Use first element if container is an jQuery object 759 if (options !== undefined && options.container instanceof jQuery) { 760 options.container = options.container[0]; 761 } 762 763 // Call plugin multiple times if there are multiple elements selected 764 if (i > 1) { 765 while (i--) { 766 $(this).eq(i).wookmark(options); 767 } 768 } else if (i === 1) { 769 // Create a wookmark instance or update an existing one 770 if (!this.wookmarkInstance) { 771 this.wookmarkInstance = new Wookmark(this[0], options || {}); 772 } else { 773 this.wookmarkInstance.updateOptions(options || {}); 774 } 775 } 776 return this; 777 }; 778 } 779 780 window.Wookmark = Wookmark; 781 return Wookmark; 782})); 783})(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.