text: /*!
    jQuery Wookmark plugin
    @name jquery.wookmark.js
    @author Christoph Ono (chri@sto.ph or @gbks)
    @author Sebastian Helzle (sebastian@helzle.net or @sebobo)
    @version 1.4.8
    @date 07/08/2013
    @category jQuery plugin
    @copyright (c) 2009-2014 Christoph Ono (www.wookmark.com)
    @license Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
  */
  (function (factory) {
    if (typeof define === 'function' && define.amd)
      define(['jquery'], factory);
    else
      factory(jQuery);
  }(function ($) {
    var Wookmark, defaultOptions, __bind;
  
    __bind = function(fn, me) {
      return function() {
        return fn.apply(me, arguments);
      };
    };
  
    // Wookmark default options
    defaultOptions = {
      align: 'center',
      autoResize: false,
      comparator: null,
      container: $('body'),
      direction: undefined,
      ignoreInactiveItems: true,
      itemWidth: 0,
      fillEmptySpace: false,
      flexibleWidth: 0,
      offset: 2,
      outerOffset: 0,
      onLayoutChanged: undefined,
      possibleFilters: [],
      resizeDelay: 50,
      verticalOffset: undefined
    };
  
    // Function for executing css writes to dom on the next animation frame if supported
    var executeNextFrame = window.requestAnimationFrame || function(callback) {callback();},
        $window = $(window);
  
    function bulkUpdateCSS(data) {
      executeNextFrame(function() {
        var i, item;
        for (i = 0; i < data.length; i++) {
          item = data[i];
          item.obj.css(item.css);
        }
      });
    }
  
    function cleanFilterName(filterName) {
      return $.trim(filterName).toLowerCase();
    }
  
    // Main wookmark plugin class
    Wookmark = (function() {
  
      function Wookmark(handler, options) {
        // Instance variables.
        this.handler = handler;
        this.columns = this.containerWidth = this.resizeTimer = null;
        this.activeItemCount = 0;
        this.itemHeightsDirty = true;
        this.placeholders = [];
  
        $.extend(true, this, defaultOptions, options);
  
        this.verticalOffset = this.verticalOffset || this.offset;
  
        // Bind instance methods
        this.update = __bind(this.update, this);
        this.onResize = __bind(this.onResize, this);
        this.onRefresh = __bind(this.onRefresh, this);
        this.getItemWidth = __bind(this.getItemWidth, this);
        this.layout = __bind(this.layout, this);
        this.layoutFull = __bind(this.layoutFull, this);
        this.layoutColumns = __bind(this.layoutColumns, this);
        this.filter = __bind(this.filter, this);
        this.clear = __bind(this.clear, this);
        this.getActiveItems = __bind(this.getActiveItems, this);
        this.refreshPlaceholders = __bind(this.refreshPlaceholders, this);
        this.sortElements = __bind(this.sortElements, this);
        this.updateFilterClasses = __bind(this.updateFilterClasses, this);
  
        // Initial update of the filter classes
        this.updateFilterClasses();
  
        // Listen to resize event if requested.
        if (this.autoResize)
          $window.bind('resize.wookmark', this.onResize);
  
        this.container.bind('refreshWookmark', this.onRefresh);
      }
  
      Wookmark.prototype.updateFilterClasses = function() {
        // Collect filter data
        var i = 0, j = 0, k = 0, filterClasses = {}, itemFilterClasses,
            $item, filterClass, possibleFilters = this.possibleFilters, possibleFilter;
  
        for (; i < this.handler.length; i++) {
          $item = this.handler.eq(i);
  
          // Read filter classes and globally store each filter class as object and the fitting items in the array
          itemFilterClasses = $item.data('filterClass');
          if (typeof itemFilterClasses == 'object' && itemFilterClasses.length > 0) {
            for (j = 0; j < itemFilterClasses.length; j++) {
              filterClass = cleanFilterName(itemFilterClasses[j]);
  
              if (typeof(filterClasses[filterClass]) === 'undefined') {
                filterClasses[filterClass] = [];
              }
              filterClasses[filterClass].push($item[0]);
            }
          }
        }
  
        for (; k < possibleFilters.length; k++) {
          possibleFilter = cleanFilterName(possibleFilters[k]);
          if (!(possibleFilter in filterClasses)) {
            filterClasses[possibleFilter] = [];
          }
        }
  
        this.filterClasses = filterClasses;
      };
  
      // Method for updating the plugins options
      Wookmark.prototype.update = function(options) {
        this.itemHeightsDirty = true;
        $.extend(true, this, options);
      };
  
      // This timer ensures that layout is not continuously called as window is being dragged.
      Wookmark.prototype.onResize = function() {
        clearTimeout(this.resizeTimer);
        this.itemHeightsDirty = this.flexibleWidth !== 0;
        this.resizeTimer = setTimeout(this.layout, this.resizeDelay);
      };
  
      // Marks the items heights as dirty and does a relayout
      Wookmark.prototype.onRefresh = function() {
        this.itemHeightsDirty = true;
        this.layout();
      };
  
      /**
       * Filters the active items with the given string filters.
       * @param filters array of string
       * @param mode 'or' or 'and'
       */
      Wookmark.prototype.filter = function(filters, mode, dryRun) {
        var activeFilters = [], activeFiltersLength, activeItems = $(),
            i, j, k, filter;
  
        filters = filters || [];
        mode = mode || 'or';
        dryRun = dryRun || false;
  
        if (filters.length) {
          // Collect active filters
          for (i = 0; i < filters.length; i++) {
            filter = cleanFilterName(filters[i]);
            if (filter in this.filterClasses) {
              activeFilters.push(this.filterClasses[filter]);
            }
          }
  
          // Get items for active filters with the selected mode
          activeFiltersLength = activeFilters.length;
          if (mode == 'or' || activeFiltersLength == 1) {
            // Set all items in all active filters active
            for (i = 0; i < activeFiltersLength; i++) {
              activeItems = activeItems.add(activeFilters[i]);
            }
          } else if (mode == 'and') {
            var shortestFilter = activeFilters[0],
                itemValid = true, foundInFilter,
                currentItem, currentFilter;
  
            // Find shortest filter class
            for (i = 1; i < activeFiltersLength; i++) {
              if (activeFilters[i].length < shortestFilter.length) {
                shortestFilter = activeFilters[i];
              }
            }
  
            // Iterate over shortest filter and find elements in other filter classes
            shortestFilter = shortestFilter || [];
            for (i = 0; i < shortestFilter.length; i++) {
              currentItem = shortestFilter[i];
              itemValid = true;
  
              for (j = 0; j < activeFilters.length && itemValid; j++) {
                currentFilter = activeFilters[j];
                if (shortestFilter == currentFilter) continue;
  
                // Search for current item in each active filter class
                for (k = 0, foundInFilter = false; k < currentFilter.length && !foundInFilter; k++) {
                  foundInFilter = currentFilter[k] == currentItem;
                }
                itemValid &= foundInFilter;
              }
              if (itemValid)
                activeItems.push(shortestFilter[i]);
            }
          }
          // Hide inactive items
          if (!dryRun)
            this.handler.not(activeItems).addClass('inactive');
        } else {
          // Show all items if no filter is selected
          activeItems = this.handler;
        }
  
        // Show active items
        if (!dryRun) {
          activeItems.removeClass('inactive');
          // Unset columns and refresh grid for a full layout
          this.columns = null;
          this.layout();
        }
        return activeItems;
      };
  
      /**
       * Creates or updates existing placeholders to create columns of even height
       */
      Wookmark.prototype.refreshPlaceholders = function(columnWidth, sideOffset) {
        var i = this.placeholders.length,
            $placeholder, $lastColumnItem,
            columnsLength = this.columns.length, column,
            height, top, innerOffset,
            containerHeight = this.container.innerHeight();
  
        for (; i < columnsLength; i++) {
          $placeholder = $('<div class="wookmark-placeholder"/>').appendTo(this.container);
          this.placeholders.push($placeholder);
        }
  
        innerOffset = this.offset + parseInt(this.placeholders[0].css('borderLeftWidth'), 10) * 2;
  
        for (i = 0; i < this.placeholders.length; i++) {
          $placeholder = this.placeholders[i];
          column = this.columns[i];
  
          if (i >= columnsLength || !column[column.length - 1]) {
            $placeholder.css('display', 'none');
          } else {
            $lastColumnItem = column[column.length - 1];
            if (!$lastColumnItem) continue;
            top = $lastColumnItem.data('wookmark-top') + $lastColumnItem.data('wookmark-height') + this.verticalOffset;
            height = containerHeight - top - innerOffset;
  
            $placeholder.css({
              position: 'absolute',
              display: height > 0 ? 'block' : 'none',
              left: i * columnWidth + sideOffset,
              top: top,
              width: columnWidth - innerOffset,
              height: height
            });
          }
        }
      };
  
      // Method the get active items which are not disabled and visible
      Wookmark.prototype.getActiveItems = function() {
        return this.ignoreInactiveItems ? this.handler.not('.inactive') : this.handler;
      };
  
      // Method to get the standard item width
      Wookmark.prototype.getItemWidth = function() {
        var itemWidth = this.itemWidth,
            innerWidth = this.container.width() - 2 * this.outerOffset,
            firstElement = this.handler.eq(0),
            flexibleWidth = this.flexibleWidth;
  
        if (this.itemWidth === undefined || this.itemWidth === 0 && !this.flexibleWidth) {
          itemWidth = firstElement.outerWidth();
        }
        else if (typeof this.itemWidth == 'string' && this.itemWidth.indexOf('%') >= 0) {
          itemWidth = parseFloat(this.itemWidth) / 100 * innerWidth;
        }
  
        // Calculate flexible item width if option is set
        if (flexibleWidth) {
          if (typeof flexibleWidth == 'string' && flexibleWidth.indexOf('%') >= 0) {
            flexibleWidth = parseFloat(flexibleWidth) / 100 * innerWidth;
          }
  
          // Find highest column count
          var paddedInnerWidth = (innerWidth + this.offset),
              flexibleColumns = ~~(0.5 + paddedInnerWidth / (flexibleWidth + this.offset)),
              fixedColumns = ~~(paddedInnerWidth / (itemWidth + this.offset)),
              columns = Math.max(flexibleColumns, fixedColumns),
              columnWidth = Math.min(flexibleWidth, ~~((innerWidth - (columns - 1) * this.offset) / columns));
  
          itemWidth = Math.max(itemWidth, columnWidth);
  
          // Stretch items to fill calculated width
          this.handler.css('width', itemWidth);
        }
  
        return itemWidth;
      };
  
      // Main layout method.
      Wookmark.prototype.layout = function(force) {
        // Do nothing if container isn't visible
        if (!this.container.is(':visible')) return;
  
        // Calculate basic layout parameters.
        var columnWidth = this.getItemWidth() + this.offset,
            containerWidth = this.container.width(),
            innerWidth = containerWidth - 2 * this.outerOffset,
            columns = ~~((innerWidth + this.offset) / columnWidth),
            offset = 0, maxHeight = 0, i = 0,
            activeItems = this.getActiveItems(),
            activeItemsLength = activeItems.length,
            $item;
  
        // Cache item height
        if (this.itemHeightsDirty || !this.container.data('itemHeightsInitialized')) {
          for (; i < activeItemsLength; i++) {
            $item = activeItems.eq(i);
            $item.data('wookmark-height', $item.outerHeight());
          }
          this.itemHeightsDirty = false;
          this.container.data('itemHeightsInitialized', true);
        }
  
        // Use less columns if there are to few items
        columns = Math.max(1, Math.min(columns, activeItemsLength));
  
        // Calculate the offset based on the alignment of columns to the parent container
        offset = this.outerOffset;
        if (this.align == 'center') {
          offset += ~~(0.5 + (innerWidth - (columns * columnWidth - this.offset)) >> 1);
        }
  
        // Get direction for positioning
        this.direction = this.direction || (this.align == 'right' ? 'right' : 'left');
  
        // If container and column count hasn't changed, we can only update the columns.
        if (!force && this.columns !== null && this.columns.length == columns && this.activeItemCount == activeItemsLength) {
          maxHeight = this.layoutColumns(columnWidth, offset);
        } else {
          maxHeight = this.layoutFull(columnWidth, columns, offset);
        }
        this.activeItemCount = activeItemsLength;
  
        // Set container height to height of the grid.
        this.container.css('height', maxHeight);
  
        // Update placeholders
        if (this.fillEmptySpace) {
          this.refreshPlaceholders(columnWidth, offset);
        }
  
        if (this.onLayoutChanged !== undefined && typeof this.onLayoutChanged === 'function') {
          this.onLayoutChanged();
        }
      };
  
      /**
       * Sort elements with configurable comparator
       */
      Wookmark.prototype.sortElements = function(elements) {
        return typeof(this.comparator) === 'function' ? elements.sort(this.comparator) : elements;
      };
  
      /**
       * Perform a full layout update.
       */
      Wookmark.prototype.layoutFull = function(columnWidth, columns, offset) {
        var $item, i = 0, k = 0,
            activeItems = $.makeArray(this.getActiveItems()),
            length = activeItems.length,
            shortest = null, shortestIndex = null,
            sideOffset, heights = [], itemBulkCSS = [],
            leftAligned = this.align == 'left' ? true : false;
  
        this.columns = [];
  
        // Sort elements before layouting
        activeItems = this.sortElements(activeItems);
  
        // Prepare arrays to store height of columns and items.
        while (heights.length < columns) {
          heights.push(this.outerOffset);
          this.columns.push([]);
        }
  
        // Loop over items.
        for (; i < length; i++ ) {
          $item = $(activeItems[i]);
  
          // Find the shortest column.
          shortest = heights[0];
          shortestIndex = 0;
          for (k = 0; k < columns; k++) {
            if (heights[k] < shortest) {
              shortest = heights[k];
              shortestIndex = k;
            }
          }
          $item.data('wookmark-top', shortest);
  
          // stick to left side if alignment is left and this is the first column
          sideOffset = offset;
          if (shortestIndex > 0 || !leftAligned)
            sideOffset += shortestIndex * columnWidth;
  
          // Position the item.
          (itemBulkCSS[i] = {
            obj: $item,
            css: {
              position: 'absolute',
              top: shortest
            }
          }).css[this.direction] = sideOffset;
  
          // Update column height and store item in shortest column
          heights[shortestIndex] += $item.data('wookmark-height') + this.verticalOffset;
          this.columns[shortestIndex].push($item);
        }
  
        bulkUpdateCSS(itemBulkCSS);
  
        // Return longest column
        return Math.max.apply(Math, heights);
      };
  
      /**
       * This layout method only updates the vertical position of the
       * existing column assignments.
       */
      Wookmark.prototype.layoutColumns = function(columnWidth, offset) {
        var heights = [], itemBulkCSS = [],
            i = 0, k = 0, j = 0, currentHeight,
            column, $item, itemData, sideOffset;
  
        for (; i < this.columns.length; i++) {
          heights.push(this.outerOffset);
          column = this.columns[i];
          sideOffset = i * columnWidth + offset;
          currentHeight = heights[i];
  
          for (k = 0; k < column.length; k++, j++) {
            $item = column[k].data('wookmark-top', currentHeight);
            (itemBulkCSS[j] = {
              obj: $item,
              css: {
                top: currentHeight
              }
            }).css[this.direction] = sideOffset;
  
            currentHeight += $item.data('wookmark-height') + this.verticalOffset;
          }
          heights[i] = currentHeight;
        }
  
        bulkUpdateCSS(itemBulkCSS);
  
        // Return longest column
        return Math.max.apply(Math, heights);
      };
  
      /**
       * Clear event listeners and time outs and the instance itself
       */
      Wookmark.prototype.clear = function() {
        clearTimeout(this.resizeTimer);
        $window.unbind('resize.wookmark', this.onResize);
        this.container.unbind('refreshWookmark', this.onRefresh);
        this.handler.wookmarkInstance = null;
      };
  
      return Wookmark;
    })();
  
    $.fn.wookmark = function(options) {
      // Create a wookmark instance if not available
      if (!this.wookmarkInstance) {
        this.wookmarkInstance = new Wookmark(this, options || {});
      } else {
        this.wookmarkInstance.update(options || {});
      }
  
      // Apply layout
      this.wookmarkInstance.layout(true);
  
      // Display items (if hidden) and return jQuery object to maintain chainability
      return this.show();
    };
  }));
  