PageRenderTime 54ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/files/salvattore/1.0.7/salvattore.js

https://gitlab.com/Mirros/jsdelivr
JavaScript | 515 lines | 360 code | 110 blank | 45 comment | 53 complexity | afdc8dde966c05f35a64a2547a223b2d MD5 | raw file
  1. (function(root, factory) {
  2. if(typeof exports === 'object') {
  3. module.exports = factory();
  4. }
  5. else if(typeof define === 'function' && define.amd) {
  6. define('salvattore', [], factory);
  7. }
  8. else {
  9. root.salvattore = factory();
  10. }
  11. }(this, function() {
  12. /*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas, David Knight. Dual MIT/BSD license */
  13. window.matchMedia || (window.matchMedia = function() {
  14. "use strict";
  15. // For browsers that support matchMedium api such as IE 9 and webkit
  16. var styleMedia = (window.styleMedia || window.media);
  17. // For those that don't support matchMedium
  18. if (!styleMedia) {
  19. var style = document.createElement('style'),
  20. script = document.getElementsByTagName('script')[0],
  21. info = null;
  22. style.type = 'text/css';
  23. style.id = 'matchmediajs-test';
  24. script.parentNode.insertBefore(style, script);
  25. // 'style.currentStyle' is used by IE <= 8 and 'window.getComputedStyle' for all other browsers
  26. info = ('getComputedStyle' in window) && window.getComputedStyle(style, null) || style.currentStyle;
  27. styleMedia = {
  28. matchMedium: function(media) {
  29. var text = '@media ' + media + '{ #matchmediajs-test { width: 1px; } }';
  30. // 'style.styleSheet' is used by IE <= 8 and 'style.textContent' for all other browsers
  31. if (style.styleSheet) {
  32. style.styleSheet.cssText = text;
  33. } else {
  34. style.textContent = text;
  35. }
  36. // Test if media query is true or false
  37. return info.width === '1px';
  38. }
  39. };
  40. }
  41. return function(media) {
  42. return {
  43. matches: styleMedia.matchMedium(media || 'all'),
  44. media: media || 'all'
  45. };
  46. };
  47. }());
  48. ;/*! matchMedia() polyfill addListener/removeListener extension. Author & copyright (c) 2012: Scott Jehl. Dual MIT/BSD license */
  49. (function(){
  50. // Bail out for browsers that have addListener support
  51. if (window.matchMedia && window.matchMedia('all').addListener) {
  52. return false;
  53. }
  54. var localMatchMedia = window.matchMedia,
  55. hasMediaQueries = localMatchMedia('only all').matches,
  56. isListening = false,
  57. timeoutID = 0, // setTimeout for debouncing 'handleChange'
  58. queries = [], // Contains each 'mql' and associated 'listeners' if 'addListener' is used
  59. handleChange = function(evt) {
  60. // Debounce
  61. clearTimeout(timeoutID);
  62. timeoutID = setTimeout(function() {
  63. for (var i = 0, il = queries.length; i < il; i++) {
  64. var mql = queries[i].mql,
  65. listeners = queries[i].listeners || [],
  66. matches = localMatchMedia(mql.media).matches;
  67. // Update mql.matches value and call listeners
  68. // Fire listeners only if transitioning to or from matched state
  69. if (matches !== mql.matches) {
  70. mql.matches = matches;
  71. for (var j = 0, jl = listeners.length; j < jl; j++) {
  72. listeners[j].call(window, mql);
  73. }
  74. }
  75. }
  76. }, 30);
  77. };
  78. window.matchMedia = function(media) {
  79. var mql = localMatchMedia(media),
  80. listeners = [],
  81. index = 0;
  82. mql.addListener = function(listener) {
  83. // Changes would not occur to css media type so return now (Affects IE <= 8)
  84. if (!hasMediaQueries) {
  85. return;
  86. }
  87. // Set up 'resize' listener for browsers that support CSS3 media queries (Not for IE <= 8)
  88. // There should only ever be 1 resize listener running for performance
  89. if (!isListening) {
  90. isListening = true;
  91. window.addEventListener('resize', handleChange, true);
  92. }
  93. // Push object only if it has not been pushed already
  94. if (index === 0) {
  95. index = queries.push({
  96. mql : mql,
  97. listeners : listeners
  98. });
  99. }
  100. listeners.push(listener);
  101. };
  102. mql.removeListener = function(listener) {
  103. for (var i = 0, il = listeners.length; i < il; i++){
  104. if (listeners[i] === listener){
  105. listeners.splice(i, 1);
  106. }
  107. }
  108. };
  109. return mql;
  110. };
  111. }());
  112. ;// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
  113. // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
  114. // requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel
  115. // MIT license
  116. (function() {
  117. var lastTime = 0;
  118. var vendors = ['ms', 'moz', 'webkit', 'o'];
  119. for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
  120. window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
  121. window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame']
  122. || window[vendors[x]+'CancelRequestAnimationFrame'];
  123. }
  124. if (!window.requestAnimationFrame)
  125. window.requestAnimationFrame = function(callback, element) {
  126. var currTime = new Date().getTime();
  127. var timeToCall = Math.max(0, 16 - (currTime - lastTime));
  128. var id = window.setTimeout(function() { callback(currTime + timeToCall); },
  129. timeToCall);
  130. lastTime = currTime + timeToCall;
  131. return id;
  132. };
  133. if (!window.cancelAnimationFrame)
  134. window.cancelAnimationFrame = function(id) {
  135. clearTimeout(id);
  136. };
  137. }());
  138. ;var salvattore = (function (global, document, undefined) {
  139. "use strict";
  140. var self = {},
  141. grids = [],
  142. add_to_dataset = function(element, key, value) {
  143. // uses dataset function or a fallback for <ie10
  144. if (element.dataset) {
  145. element.dataset[key] = value;
  146. } else {
  147. element.setAttribute("data-" + key, value);
  148. }
  149. return;
  150. };
  151. self.obtain_grid_settings = function obtain_grid_settings(element) {
  152. // returns the number of columns and the classes a column should have,
  153. // from computing the style of the ::before pseudo-element of the grid.
  154. var computedStyle = global.getComputedStyle(element, ":before")
  155. , content = computedStyle.getPropertyValue("content").slice(1, -1)
  156. , matchResult = content.match(/^\s*(\d+)(?:\s?\.(.+))?\s*$/)
  157. , numberOfColumns
  158. , columnClasses
  159. ;
  160. if (matchResult) {
  161. numberOfColumns = matchResult[1];
  162. columnClasses = matchResult[2];
  163. columnClasses = columnClasses? columnClasses.split(".") : ["column"];
  164. } else {
  165. matchResult = content.match(/^\s*\.(.+)\s+(\d+)\s*$/);
  166. columnClasses = matchResult[1];
  167. numberOfColumns = matchResult[2];
  168. if (numberOfColumns) {
  169. numberOfColumns = numberOfColumns.split(".");
  170. }
  171. }
  172. return {
  173. numberOfColumns: numberOfColumns,
  174. columnClasses: columnClasses
  175. };
  176. };
  177. self.add_columns = function add_columns(grid, items) {
  178. // from the settings obtained, it creates columns with
  179. // the configured classes and adds to them a list of items.
  180. var settings = self.obtain_grid_settings(grid)
  181. , numberOfColumns = settings.numberOfColumns
  182. , columnClasses = settings.columnClasses
  183. , columnsItems = new Array(+numberOfColumns)
  184. , columnsFragment = document.createDocumentFragment()
  185. , i = numberOfColumns
  186. , selector
  187. ;
  188. while (i-- !== 0) {
  189. selector = "[data-columns] > *:nth-child(" + numberOfColumns + "n-" + i + ")";
  190. columnsItems.push(items.querySelectorAll(selector));
  191. }
  192. columnsItems.forEach(function append_to_grid_fragment(rows) {
  193. var column = document.createElement("div")
  194. , rowsFragment = document.createDocumentFragment()
  195. ;
  196. column.className = columnClasses.join(" ");
  197. Array.prototype.forEach.call(rows, function append_to_column(row) {
  198. rowsFragment.appendChild(row);
  199. });
  200. column.appendChild(rowsFragment);
  201. columnsFragment.appendChild(column);
  202. });
  203. grid.appendChild(columnsFragment);
  204. add_to_dataset(grid, 'columns', numberOfColumns);
  205. };
  206. self.remove_columns = function remove_columns(grid) {
  207. // removes all the columns from a grid, and returns a list
  208. // of items sorted by the ordering of columns.
  209. var range = document.createRange();
  210. range.selectNodeContents(grid);
  211. var columns = Array.prototype.filter.call(range.extractContents().childNodes, function filter_elements(node) {
  212. return node instanceof global.HTMLElement;
  213. });
  214. var numberOfColumns = columns.length
  215. , numberOfRowsInFirstColumn = columns[0].childNodes.length
  216. , sortedRows = new Array(numberOfRowsInFirstColumn * numberOfColumns)
  217. ;
  218. Array.prototype.forEach.call(columns, function iterate_columns(column, columnIndex) {
  219. Array.prototype.forEach.call(column.children, function iterate_rows(row, rowIndex) {
  220. sortedRows[rowIndex * numberOfColumns + columnIndex] = row;
  221. });
  222. });
  223. var container = document.createElement("div");
  224. add_to_dataset(container, 'columns', 0);
  225. sortedRows.filter(function filter_non_null(child) {
  226. return !!child;
  227. }).forEach(function append_row(child) {
  228. container.appendChild(child);
  229. });
  230. return container;
  231. };
  232. self.recreate_columns = function recreate_columns(grid) {
  233. // removes all the columns from the grid, and adds them again,
  234. // it is used when the number of columns change.
  235. global.requestAnimationFrame(function render_after_css_media_query_change() {
  236. self.add_columns(grid, self.remove_columns(grid));
  237. });
  238. };
  239. self.media_query_change = function media_query_change(mql) {
  240. // recreates the columns when a media query matches the current state
  241. // of the browser.
  242. if (mql.matches) {
  243. Array.prototype.forEach.call(grids, self.recreate_columns);
  244. }
  245. };
  246. self.get_css_rules = function get_css_rules(stylesheet) {
  247. // returns a list of css rules from a stylesheet
  248. var cssRules;
  249. try {
  250. cssRules = stylesheet.sheet.cssRules || stylesheet.sheet.rules;
  251. } catch (e) {
  252. return [];
  253. }
  254. return cssRules || [];
  255. };
  256. self.get_stylesheets = function get_stylesheets() {
  257. // returns a list of all the styles in the document (that are accessible).
  258. return Array.prototype.concat.call(
  259. Array.prototype.slice.call(document.querySelectorAll("style[type='text/css']")),
  260. Array.prototype.slice.call(document.querySelectorAll("link[rel='stylesheet']"))
  261. );
  262. };
  263. self.media_rule_has_columns_selector = function media_rule_has_columns_selector(rules) {
  264. // checks if a media query css rule has in its contents a selector that
  265. // styles the grid.
  266. var i = rules.length
  267. , rule
  268. ;
  269. while (i--) {
  270. rule = rules[i];
  271. if (rule.selectorText && rule.selectorText.match(/\[data-columns\](.*)::?before$/)) {
  272. return true;
  273. }
  274. }
  275. return false;
  276. };
  277. self.scan_media_queries = function scan_media_queries() {
  278. // scans all the stylesheets for selectors that style grids,
  279. // if the matchMedia API is supported.
  280. var mediaQueries = [];
  281. if (!global.matchMedia) {
  282. return;
  283. }
  284. self.get_stylesheets().forEach(function extract_rules(stylesheet) {
  285. Array.prototype.forEach.call(self.get_css_rules(stylesheet), function filter_by_column_selector(rule) {
  286. if (rule.media && self.media_rule_has_columns_selector(rule.cssRules)) {
  287. mediaQueries.push(global.matchMedia(rule.media.mediaText));
  288. }
  289. });
  290. });
  291. mediaQueries.forEach(function listen_to_changes(mql) {
  292. mql.addListener(self.media_query_change);
  293. });
  294. };
  295. self.next_element_column_index = function next_element_column_index(grid, fragments) {
  296. // returns the index of the column where the given element must be added.
  297. var children = grid.children
  298. , m = children.length
  299. , lowestRowCount = 0
  300. , child
  301. , currentRowCount
  302. , i
  303. , index = 0
  304. ;
  305. for (i = 0; i < m; i++) {
  306. child = children[i];
  307. currentRowCount = child.children.length + fragments[i].children.length;
  308. if(lowestRowCount === 0) {
  309. lowestRowCount = currentRowCount;
  310. }
  311. if(currentRowCount < lowestRowCount) {
  312. index = i;
  313. lowestRowCount = currentRowCount;
  314. }
  315. }
  316. return index;
  317. };
  318. self.create_list_of_fragments = function create_list_of_fragments(quantity) {
  319. // returns a list of fragments
  320. var fragments = new Array(quantity)
  321. , i = 0
  322. ;
  323. while (i !== quantity) {
  324. fragments[i] = document.createDocumentFragment();
  325. i++;
  326. }
  327. return fragments;
  328. };
  329. self.append_elements = function append_elements(grid, elements) {
  330. // adds a list of elements to the end of a grid
  331. var columns = grid.children
  332. , numberOfColumns = columns.length
  333. , fragments = self.create_list_of_fragments(numberOfColumns)
  334. ;
  335. elements.forEach(function append_to_next_fragment(element) {
  336. var columnIndex = self.next_element_column_index(grid, fragments);
  337. fragments[columnIndex].appendChild(element);
  338. });
  339. Array.prototype.forEach.call(columns, function insert_column(column, index) {
  340. column.appendChild(fragments[index]);
  341. });
  342. };
  343. self.prepend_elements = function prepend_elements(grid, elements) {
  344. // adds a list of elements to the start of a grid
  345. var columns = grid.children
  346. , numberOfColumns = columns.length
  347. , fragments = self.create_list_of_fragments(numberOfColumns)
  348. , columnIndex = numberOfColumns - 1
  349. ;
  350. elements.forEach(function append_to_next_fragment(element) {
  351. var fragment = fragments[columnIndex];
  352. fragment.insertBefore(element, fragment.firstChild);
  353. if (columnIndex === 0) {
  354. columnIndex = numberOfColumns - 1;
  355. } else {
  356. columnIndex--;
  357. }
  358. });
  359. Array.prototype.forEach.call(columns, function insert_column(column, index) {
  360. column.insertBefore(fragments[index], column.firstChild);
  361. });
  362. // populates a fragment with n columns till the right
  363. var fragment = document.createDocumentFragment()
  364. , numberOfColumnsToExtract = elements.length % numberOfColumns
  365. ;
  366. while (numberOfColumnsToExtract-- !== 0) {
  367. fragment.appendChild(grid.lastChild);
  368. }
  369. // adds the fragment to the left
  370. grid.insertBefore(fragment, grid.firstChild);
  371. };
  372. self.register_grid = function register_grid (grid) {
  373. if (global.getComputedStyle(grid).display === "none") {
  374. return;
  375. }
  376. // retrieve the list of items from the grid itself
  377. var range = document.createRange();
  378. range.selectNodeContents(grid);
  379. var items = document.createElement("div");
  380. items.appendChild(range.extractContents());
  381. add_to_dataset(items, 'columns', 0);
  382. self.add_columns(grid, items);
  383. grids.push(grid);
  384. };
  385. self.init = function init() {
  386. // adds required CSS rule to hide 'content' based
  387. // configuration.
  388. var css = document.createElement("style");
  389. css.innerHTML = "[data-columns]::before{visibility:hidden;position:absolute;font-size:1px;}";
  390. document.head.appendChild(css);
  391. // scans all the grids in the document and generates
  392. // columns from their configuration.
  393. var gridElements = document.querySelectorAll("[data-columns]");
  394. Array.prototype.forEach.call(gridElements, self.register_grid);
  395. self.scan_media_queries();
  396. };
  397. self.init();
  398. return {
  399. append_elements: self.append_elements,
  400. prepend_elements: self.prepend_elements,
  401. register_grid: self.register_grid
  402. };
  403. })(window, window.document);
  404. return salvattore;
  405. }));