PageRenderTime 873ms CodeModel.GetById 43ms RepoModel.GetById 5ms app.codeStats 0ms

/files/jquery.wookmark/1.4.0/jquery.wookmark.js

https://gitlab.com/Mirros/jsdelivr
JavaScript | 452 lines | 301 code | 73 blank | 78 comment | 84 complexity | 5de074dcdd939773ba2020fe3730ae19 MD5 | raw file
  1. /*!
  2. jQuery Wookmark plugin
  3. @name jquery.wookmark.js
  4. @author Christoph Ono (chri@sto.ph or @gbks)
  5. @author Sebastian Helzle (sebastian@helzle.net or @sebobo)
  6. @version 1.4.0
  7. @date 8/1/2013
  8. @category jQuery plugin
  9. @copyright (c) 2009-2013 Christoph Ono (www.wookmark.com)
  10. @license Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
  11. */
  12. (function (factory) {
  13. if (typeof define === 'function' && define.amd)
  14. define(['jquery'], factory);
  15. else
  16. factory(jQuery);
  17. }(function ($) {
  18. var Wookmark, defaultOptions, __bind;
  19. __bind = function(fn, me) {
  20. return function() {
  21. return fn.apply(me, arguments);
  22. };
  23. };
  24. // Wookmark default options
  25. defaultOptions = {
  26. align: 'center',
  27. autoResize: false,
  28. comparator: null,
  29. container: $('body'),
  30. ignoreInactiveItems: true,
  31. itemWidth: 0,
  32. fillEmptySpace: false,
  33. flexibleWidth: 0,
  34. offset: 2,
  35. onLayoutChanged: undefined,
  36. resizeDelay: 50
  37. };
  38. Wookmark = (function() {
  39. function Wookmark(handler, options) {
  40. // Instance variables.
  41. this.handler = handler;
  42. this.columns = this.containerWidth = this.resizeTimer = null;
  43. this.activeItemCount = 0;
  44. this.direction = 'left';
  45. this.itemHeightsDirty = true;
  46. this.placeholders = [];
  47. $.extend(true, this, defaultOptions, options);
  48. // Bind instance methods
  49. this.update = __bind(this.update, this);
  50. this.onResize = __bind(this.onResize, this);
  51. this.onRefresh = __bind(this.onRefresh, this);
  52. this.getItemWidth = __bind(this.getItemWidth, this);
  53. this.layout = __bind(this.layout, this);
  54. this.layoutFull = __bind(this.layoutFull, this);
  55. this.layoutColumns = __bind(this.layoutColumns, this);
  56. this.filter = __bind(this.filter, this);
  57. this.clear = __bind(this.clear, this);
  58. this.getActiveItems = __bind(this.getActiveItems, this);
  59. this.refreshPlaceholders = __bind(this.refreshPlaceholders, this);
  60. this.sortElements = __bind(this.sortElements, this);
  61. // Collect filter data
  62. var i = 0, j = 0, filterClasses = {}, itemFilterClasses, $item, filterClass;
  63. for (; i < handler.length; i++) {
  64. $item = handler.eq(i);
  65. // Read filter classes
  66. itemFilterClasses = $item.data('filterClass');
  67. // Globally store each filter class as object and the fitting items in the array
  68. if (typeof itemFilterClasses == 'object' && itemFilterClasses.length > 0) {
  69. for (j = 0; j < itemFilterClasses.length; j++) {
  70. filterClass = $.trim(itemFilterClasses[j]).toLowerCase();
  71. if (!(filterClass in filterClasses)) {
  72. filterClasses[filterClass] = [];
  73. }
  74. filterClasses[filterClass].push($item[0]);
  75. }
  76. }
  77. }
  78. this.filterClasses = filterClasses;
  79. // Listen to resize event if requested.
  80. if (this.autoResize)
  81. $(window).bind('resize.wookmark', this.onResize);
  82. this.container.bind('refreshWookmark', this.onRefresh);
  83. }
  84. // Method for updating the plugins options
  85. Wookmark.prototype.update = function(options) {
  86. this.itemHeightsDirty = true;
  87. $.extend(true, this, options);
  88. };
  89. // This timer ensures that layout is not continuously called as window is being dragged.
  90. Wookmark.prototype.onResize = function() {
  91. clearTimeout(this.resizeTimer);
  92. this.itemHeightsDirty = this.flexibleWidth !== 0;
  93. this.resizeTimer = setTimeout(this.layout, this.resizeDelay);
  94. };
  95. // Marks the items heights as dirty and does a relayout
  96. Wookmark.prototype.onRefresh = function() {
  97. this.itemHeightsDirty = true;
  98. this.layout();
  99. };
  100. /**
  101. * Filters the active items with the given string filters.
  102. * @param filters array of string
  103. * @param mode 'or' or 'and'
  104. */
  105. Wookmark.prototype.filter = function(filters, mode) {
  106. var activeFilters = [], activeFiltersLength, activeItems = $(),
  107. i, j, k, filter;
  108. filters = filters || [];
  109. mode = mode || 'or';
  110. if (filters.length) {
  111. // Collect active filters
  112. for (i = 0; i < filters.length; i++) {
  113. filter = $.trim(filters[i].toLowerCase());
  114. if (filter in this.filterClasses) {
  115. activeFilters.push(this.filterClasses[filter]);
  116. }
  117. }
  118. // Get items for active filters with the selected mode
  119. activeFiltersLength = activeFilters.length;
  120. if (mode == 'or' || activeFiltersLength == 1) {
  121. // Set all items in all active filters active
  122. for (i = 0; i < activeFiltersLength; i++) {
  123. activeItems = activeItems.add(activeFilters[i]);
  124. }
  125. } else if (mode == 'and') {
  126. var shortestFilter = activeFilters[0],
  127. itemValid = true, foundInFilter,
  128. currentItem, currentFilter;
  129. // Find shortest filter class
  130. for (i = 1; i < activeFiltersLength; i++) {
  131. if (activeFilters[i].length < shortestFilter.length) {
  132. shortestFilter = activeFilters[i];
  133. }
  134. }
  135. // Iterate over shortest filter and find elements in other filter classes
  136. for (i = 0; i < shortestFilter.length; i++) {
  137. currentItem = shortestFilter[i];
  138. itemValid = true;
  139. for (j = 0; j < activeFilters.length && itemValid; j++) {
  140. currentFilter = activeFilters[j];
  141. if (shortestFilter == currentFilter) continue;
  142. // Search for current item in each active filter class
  143. for (k = 0, foundInFilter = false; k < currentFilter.length && !foundInFilter; k++) {
  144. foundInFilter = currentFilter[k] == currentItem;
  145. }
  146. itemValid &= foundInFilter;
  147. }
  148. if (itemValid)
  149. activeItems.push(shortestFilter[i]);
  150. }
  151. }
  152. // Hide inactive items
  153. this.handler.not(activeItems).addClass('inactive');
  154. } else {
  155. // Show all items if no filter is selected
  156. activeItems = this.handler;
  157. }
  158. // Show active items
  159. activeItems.removeClass('inactive');
  160. // Unset columns and refresh grid for a full layout
  161. this.columns = null;
  162. this.layout();
  163. };
  164. /**
  165. * Creates or updates existing placeholders to create columns of even height
  166. */
  167. Wookmark.prototype.refreshPlaceholders = function(columnWidth, sideOffset) {
  168. var i = this.placeholders.length,
  169. $placeholder, $lastColumnItem,
  170. columnsLength = this.columns.length, column,
  171. height, top, innerOffset,
  172. containerHeight = this.container.outerHeight();
  173. for (; i < columnsLength; i++) {
  174. $placeholder = $('<div class="wookmark-placeholder"/>').appendTo(this.container);
  175. this.placeholders.push($placeholder);
  176. }
  177. innerOffset = this.offset + parseInt(this.placeholders[0].css('borderWidth'), 10) * 2;
  178. for (i = 0; i < this.placeholders.length; i++) {
  179. $placeholder = this.placeholders[i];
  180. column = this.columns[i];
  181. if (i >= columnsLength || !column[column.length - 1]) {
  182. $placeholder.css('display', 'none');
  183. } else {
  184. $lastColumnItem = column[column.length - 1];
  185. if (!$lastColumnItem) continue;
  186. top = $lastColumnItem.data('wookmark-top') + $lastColumnItem.data('wookmark-height') + this.offset;
  187. height = containerHeight - top - innerOffset;
  188. $placeholder.css({
  189. position: 'absolute',
  190. display: height > 0 ? 'block' : 'none',
  191. left: i * columnWidth + sideOffset,
  192. top: top,
  193. width: columnWidth - innerOffset,
  194. height: height
  195. });
  196. }
  197. }
  198. };
  199. // Method the get active items which are not disabled and visible
  200. Wookmark.prototype.getActiveItems = function() {
  201. if (this.ignoreInactiveItems)
  202. return this.handler.not('.inactive');
  203. return this.handler;
  204. };
  205. // Method to get the standard item width
  206. Wookmark.prototype.getItemWidth = function() {
  207. var itemWidth = this.itemWidth,
  208. containerWidth = this.container.width(),
  209. firstElement = this.handler.eq(0),
  210. flexibleWidth = this.flexibleWidth;
  211. if (this.itemWidth === undefined || this.itemWidth === 0 && !this.flexibleWidth) {
  212. itemWidth = firstElement.outerWidth();
  213. }
  214. else if (typeof this.itemWidth == 'string' && this.itemWidth.indexOf('%') >= 0) {
  215. itemWidth = parseFloat(this.itemWidth) / 100 * containerWidth;
  216. }
  217. // Calculate flexible item width if option is set
  218. if (flexibleWidth) {
  219. if (typeof flexibleWidth == 'string' && flexibleWidth.indexOf('%') >= 0) {
  220. flexibleWidth = parseFloat(flexibleWidth) / 100 * containerWidth -
  221. firstElement.outerWidth() + firstElement.innerWidth();
  222. }
  223. var columns = ~~(1 + containerWidth / (flexibleWidth + this.offset)),
  224. columnWidth = (containerWidth - (columns - 1) * this.offset) / columns;
  225. itemWidth = Math.max(itemWidth, ~~(columnWidth));
  226. // Stretch items to fill calculated width
  227. this.handler.css('width', itemWidth);
  228. }
  229. return itemWidth;
  230. };
  231. // Main layout method.
  232. Wookmark.prototype.layout = function(force) {
  233. // Do nothing if container isn't visible
  234. if (!this.container.is(':visible')) return;
  235. // Calculate basic layout parameters.
  236. var columnWidth = this.getItemWidth() + this.offset,
  237. containerWidth = this.container.width(),
  238. columns = ~~(containerWidth / columnWidth),
  239. offset = 0, maxHeight = 0, i = 0,
  240. activeItems = this.getActiveItems(),
  241. activeItemsLength = activeItems.length,
  242. $item;
  243. // Cache item height
  244. if (this.itemHeightsDirty) {
  245. for (; i < activeItemsLength; i++) {
  246. $item = activeItems.eq(i);
  247. $item.data('wookmark-height', $item.outerHeight());
  248. }
  249. this.itemHeightsDirty = false;
  250. }
  251. // Use less columns if there are to few items
  252. columns = Math.max(1, Math.min(columns, activeItemsLength));
  253. // Calculate the offset based on the alignment of columns to the parent container
  254. if (this.align == 'left' || this.align == 'right') {
  255. offset = ~~((columns / columnWidth + this.offset) >> 1);
  256. } else {
  257. offset = ~~(0.5 + (containerWidth - (columns * columnWidth - this.offset)) >> 1);
  258. }
  259. // Get direction for positioning
  260. this.direction = this.align == 'right' ? 'right' : 'left';
  261. // If container and column count hasn't changed, we can only update the columns.
  262. if (!force && this.columns !== null && this.columns.length == columns && this.activeItemCount == activeItemsLength) {
  263. maxHeight = this.layoutColumns(columnWidth, offset);
  264. } else {
  265. maxHeight = this.layoutFull(columnWidth, columns, offset);
  266. }
  267. this.activeItemCount = activeItemsLength;
  268. // Set container height to height of the grid.
  269. this.container.css('height', maxHeight);
  270. // Update placeholders
  271. if (this.fillEmptySpace) {
  272. this.refreshPlaceholders(columnWidth, offset);
  273. }
  274. if (this.onLayoutChanged !== undefined && typeof this.onLayoutChanged === 'function') {
  275. this.onLayoutChanged();
  276. }
  277. };
  278. /**
  279. * Sort elements with configurable comparator
  280. */
  281. Wookmark.prototype.sortElements = function(elements) {
  282. return typeof(this.comparator) === "function" ? elements.sort(this.comparator) : elements;
  283. }
  284. /**
  285. * Perform a full layout update.
  286. */
  287. Wookmark.prototype.layoutFull = function(columnWidth, columns, offset) {
  288. var $item, i = 0, k = 0,
  289. activeItems = $.makeArray(this.getActiveItems()),
  290. length = activeItems.length,
  291. shortest = null, shortestIndex = null,
  292. itemCSS = {position: 'absolute'},
  293. sideOffset, heights = [],
  294. leftAligned = this.align == 'left' ? true : false;
  295. this.columns = [];
  296. // Sort elements before layouting
  297. activeItems = this.sortElements(activeItems);
  298. // Prepare arrays to store height of columns and items.
  299. while (heights.length < columns) {
  300. heights.push(0);
  301. this.columns.push([]);
  302. }
  303. // Loop over items.
  304. for (; i < length; i++ ) {
  305. $item = $(activeItems[i]);
  306. // Find the shortest column.
  307. shortest = heights[0];
  308. shortestIndex = 0;
  309. for (k = 0; k < columns; k++) {
  310. if (heights[k] < shortest) {
  311. shortest = heights[k];
  312. shortestIndex = k;
  313. }
  314. }
  315. // stick to left side if alignment is left and this is the first column
  316. sideOffset = offset;
  317. if (shortestIndex > 0 || !leftAligned)
  318. sideOffset += shortestIndex * columnWidth;
  319. // Position the item.
  320. itemCSS[this.direction] = sideOffset;
  321. itemCSS.top = shortest;
  322. $item.css(itemCSS).data('wookmark-top', shortest);
  323. // Update column height and store item in shortest column
  324. heights[shortestIndex] += $item.data('wookmark-height') + this.offset;
  325. this.columns[shortestIndex].push($item);
  326. }
  327. // Return longest column
  328. return Math.max.apply(Math, heights);
  329. };
  330. /**
  331. * This layout method only updates the vertical position of the
  332. * existing column assignments.
  333. */
  334. Wookmark.prototype.layoutColumns = function(columnWidth, offset) {
  335. var heights = [],
  336. i = 0, k = 0, currentHeight,
  337. column, $item, itemCSS, sideOffset;
  338. for (; i < this.columns.length; i++) {
  339. heights.push(0);
  340. column = this.columns[i];
  341. sideOffset = i * columnWidth + offset;
  342. currentHeight = heights[i];
  343. for (k = 0; k < column.length; k++) {
  344. $item = column[k];
  345. itemCSS = {
  346. top: currentHeight
  347. };
  348. itemCSS[this.direction] = sideOffset;
  349. $item.css(itemCSS).data('wookmark-top', currentHeight);
  350. currentHeight += $item.data('wookmark-height') + this.offset;
  351. }
  352. heights[i] = currentHeight;
  353. }
  354. // Return longest column
  355. return Math.max.apply(Math, heights);
  356. };
  357. /**
  358. * Clear event listeners and time outs.
  359. */
  360. Wookmark.prototype.clear = function() {
  361. clearTimeout(this.resizeTimer);
  362. $(window).unbind('resize.wookmark', this.onResize);
  363. this.container.unbind('refreshWookmark', this.onRefresh);
  364. };
  365. return Wookmark;
  366. })();
  367. $.fn.wookmark = function(options) {
  368. // Create a wookmark instance if not available
  369. if (!this.wookmarkInstance) {
  370. this.wookmarkInstance = new Wookmark(this, options || {});
  371. } else {
  372. this.wookmarkInstance.update(options || {});
  373. }
  374. // Apply layout
  375. this.wookmarkInstance.layout(true);
  376. // Display items (if hidden) and return jQuery object to maintain chainability
  377. return this.show();
  378. };
  379. }));