PageSourceSearch

https://www.tusiar.com/cjs/js/wookmark.js

js tusiar.com collected 2026-10-03 02:41:00 UTC 24,533 bytes, 783 lines download raw bytes

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.