PageRenderTime 56ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/files/jquery.wookmark/1.4.5/jquery.wookmark.js

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