1/* jshint laxcomma: true */ 2var salvattore = (function (global, document, undefined) { 3"use strict"; 4 5var self = {}, 6 grids = [], 7 mediaRules = [], 8 mediaQueries = [], 9 add_to_dataset = function(element, key, value) { 10 // uses dataset function or a fallback for <ie10 11 if (element.dataset) { 12 element.dataset[key] = value; 13 } else { 14 element.setAttribute("data-" + key, value); 15 } 16 return; 17 }; 18 19self.obtainGridSettings = function obtainGridSettings(element) { 20 // returns the number of columns and the classes a column should have, 21 // from computing the style of the ::before pseudo-element of the grid. 22 23 var computedStyle = global.getComputedStyle(element, ":before") 24 , content = computedStyle.getPropertyValue("content").slice(1, -1) 25 , matchResult = content.match(/^\s*(\d+)(?:\s?\.(.+))?\s*$/) 26 , numberOfColumns = 1 27 , columnClasses = [] 28 ; 29 30 if (matchResult) { 31 numberOfColumns = matchResult[1]; 32 columnClasses = matchResult[2]; 33 columnClasses = columnClasses? columnClasses.split(".") : ["column"]; 34 } else { 35 matchResult = content.match(/^\s*\.(.+)\s+(\d+)\s*$/); 36 if (matchResult) { 37 columnClasses = matchResult[1]; 38 numberOfColumns = matchResult[2]; 39 if (numberOfColumns) { 40 numberOfColumns = numberOfColumns.split("."); 41 } 42 } 43 } 44 45 return { 46 numberOfColumns: numberOfColumns, 47 columnClasses: columnClasses 48 }; 49}; 50 51 52self.addColumns = function addColumns(grid, items) { 53 // from the settings obtained, it creates columns with 54 // the configured classes and adds to them a list of items. 55 56 var settings = self.obtainGridSettings(grid) 57 , numberOfColumns = settings.numberOfColumns 58 , columnClasses = settings.columnClasses 59 , columnsItems = new Array(+numberOfColumns) 60 , columnsFragment = document.createDocumentFragment() 61 , i = numberOfColumns 62 , selector 63 ; 64 65 while (i-- !== 0) { 66 selector = "[data-columns] > *:nth-child(" + numberOfColumns + "n-" + i + ")"; 67 columnsItems.push(items.querySelectorAll(selector)); 68 } 69 70 columnsItems.forEach(function append_to_grid_fragment(rows) { 71 var column = document.createElement("div") 72 , rowsFragment = document.createDocumentFragment() 73 ; 74 75 column.className = columnClasses.join(" "); 76 77 Array.prototype.forEach.call(rows, function append_to_column(row) { 78 rowsFragment.appendChild(row); 79 }); 80 column.appendChild(rowsFragment); 81 columnsFragment.appendChild(column); 82 }); 83 84 grid.appendChild(columnsFragment); 85 add_to_dataset(grid, 'columns', numberOfColumns); 86}; 87 88 89self.removeColumns = function removeColumns(grid) { 90 // removes all the columns from a grid, and returns a list 91 // of items sorted by the ordering of columns. 92 93 var range = document.createRange(); 94 range.selectNodeContents(grid); 95 96 var columns = Array.prototype.filter.call(range.extractContents().childNodes, function filter_elements(node) { 97 return node instanceof global.HTMLElement; 98 }); 99 100 var numberOfColumns = columns.length 101 , numberOfRowsInFirstColumn = columns[0].childNodes.length 102 , sortedRows = new Array(numberOfRowsInFirstColumn * numberOfColumns) 103 ; 104 105 Array.prototype.forEach.call(columns, function iterate_columns(column, columnIndex) { 106 Array.prototype.forEach.call(column.children, function iterate_rows(row, rowIndex) { 107 sortedRows[rowIndex * numberOfColumns + columnIndex] = row; 108 }); 109 }); 110 111 var container = document.createElement("div"); 112 add_to_dataset(container, 'columns', 0); 113 114 sortedRows.filter(function filter_non_null(child) { 115 return !!child; 116 }).forEach(function append_row(child) { 117 container.appendChild(child); 118 }); 119 120 return container; 121}; 122 123 124self.recreateColumns = function recreateColumns(grid) { 125 // removes all the columns from the grid, and adds them again, 126 // it is used when the number of columns change. 127 128 global.requestAnimationFrame(function render_after_css_mediaQueryChange() { 129 self.addColumns(grid, self.removeColumns(grid)); 130 var columnsChange = new CustomEvent("columnsChange"); 131 grid.dispatchEvent(columnsChange); 132 }); 133}; 134 135 136self.mediaQueryChange = function mediaQueryChange(mql) { 137 // recreates the columns when a media query matches the current state 138 // of the browser. 139 140 if (mql.matches) { 141 Array.prototype.forEach.call(grids, self.recreateColumns); 142 } 143}; 144 145 146self.getCSSRules = function getCSSRules(stylesheet) { 147 // returns a list of css rules from a stylesheet 148 149 var cssRules; 150 try { 151 cssRules = stylesheet.sheet.cssRules || stylesheet.sheet.rules; 152 } catch (e) { 153 return []; 154 } 155 156 return cssRules || []; 157}; 158 159 160self.getStylesheets = function getStylesheets() { 161 // returns a list of all the styles in the document (that are accessible). 162 163 return Array.prototype.concat.call( 164 Array.prototype.slice.call(document.querySelectorAll("style[type='text/css']")), 165 Array.prototype.slice.call(document.querySelectorAll("link[rel='stylesheet']")) 166 ); 167}; 168 169 170self.mediaRuleHasColumnsSelector = function mediaRuleHasColumnsSelector(rules) { 171 // checks if a media query css rule has in its contents a selector that 172 // styles the grid. 173 174 var i, rule; 175 176 try { 177 i = rules.length; 178 }
179 catch (e) { 180 i = 0; 181 } 182 183 while (i--) { 184 rule = rules[i]; 185 if (rule.selectorText && rule.selectorText.match(/\[data-columns\](.*)::?before$/)) { 186 return true; 187 } 188 } 189 190 return false; 191}; 192 193 194self.scanMediaQueries = function scanMediaQueries() { 195 // scans all the stylesheets for selectors that style grids, 196 // if the matchMedia API is supported. 197 198 var newMediaRules = []; 199 200 if (!global.matchMedia) { 201 return; 202 } 203 204 self.getStylesheets().forEach(function extract_rules(stylesheet) { 205 Array.prototype.forEach.call(self.getCSSRules(stylesheet), function filter_by_column_selector(rule) { 206 if (rule.media && rule.cssRules && self.mediaRuleHasColumnsSelector(rule.cssRules)) { 207 newMediaRules.push(rule); 208 } 209 }); 210 }); 211 212 // remove matchMedia listeners from the old rules 213 var oldRules = mediaRules.filter(function (el) { 214 return newMediaRules.indexOf(el) === -1; 215 }); 216 mediaQueries.filter(function (el) { 217 return oldRules.indexOf(el.rule) !== -1; 218 }).forEach(function (el) { 219 el.mql.removeListener(self.mediaQueryChange); 220 }); 221 mediaQueries = mediaQueries.filter(function (el) { 222 return oldRules.indexOf(el.rule) === -1; 223 }); 224 225 // add matchMedia listeners to the new rules 226 newMediaRules.filter(function (el) { 227 return mediaRules.indexOf(el) == -1; 228 }).forEach(function (rule) { 229 var mql = global.matchMedia(rule.media.mediaText); 230 mql.addListener(self.mediaQueryChange); 231 mediaQueries.push({rule: rule, mql:mql}); 232 }); 233 234 // swap mediaRules with the new set 235 mediaRules.length = 0; 236 mediaRules = newMediaRules; 237}; 238 239 240self.rescanMediaQueries = function rescanMediaQueries() { 241 self.scanMediaQueries(); 242 Array.prototype.forEach.call(grids, self.recreateColumns); 243}; 244 245 246self.nextElementColumnIndex = function nextElementColumnIndex(grid, fragments) { 247 // returns the index of the column where the given element must be added. 248 249 var children = grid.children 250 , m = children.length 251 , lowestRowCount = 0 252 , child 253 , currentRowCount 254 , i 255 , index = 0 256 ; 257 for (i = 0; i < m; i++) { 258 child = children[i]; 259 currentRowCount = child.children.length + (fragments[i].children || fragments[i].childNodes).length; 260 if(lowestRowCount === 0) { 261 lowestRowCount = currentRowCount; 262 } 263 if(currentRowCount < lowestRowCount) { 264 index = i; 265 lowestRowCount = currentRowCount; 266 } 267 } 268 269 return index; 270}; 271 272 273self.createFragmentsList = function createFragmentsList(quantity) { 274 // returns a list of fragments 275 276 var fragments = new Array(quantity) 277 , i = 0 278 ; 279 280 while (i !== quantity) { 281 fragments[i] = document.createDocumentFragment(); 282 i++; 283 } 284 285 return fragments; 286}; 287 288 289self.appendElements = function appendElements(grid, elements) { 290 // adds a list of elements to the end of a grid 291 292 var columns = grid.children 293 , numberOfColumns = columns.length 294 , fragments = self.createFragmentsList(numberOfColumns) 295 ; 296 297 Array.prototype.forEach.call(elements, function append_to_next_fragment(element) { 298 var columnIndex = self.nextElementColumnIndex(grid, fragments); 299 fragments[columnIndex].appendChild(element); 300 }); 301 302 Array.prototype.forEach.call(columns, function insert_column(column, index) { 303 column.appendChild(fragments[index]); 304 }); 305}; 306 307 308self.prependElements = function prependElements(grid, elements) { 309 // adds a list of elements to the start of a grid 310 311 var columns = grid.children 312 , numberOfColumns = columns.length 313 , fragments = self.createFragmentsList(numberOfColumns) 314 , columnIndex = numberOfColumns - 1 315 ; 316 317 elements.forEach(function append_to_next_fragment(element) { 318 var fragment = fragments[columnIndex]; 319 fragment.insertBefore(element, fragment.firstChild); 320 if (columnIndex === 0) { 321 columnIndex = numberOfColumns - 1; 322 } else { 323 columnIndex--; 324 } 325 }); 326 327 Array.prototype.forEach.call(columns, function insert_column(column, index) { 328 column.insertBefore(fragments[index], column.firstChild); 329 }); 330 331 // populates a fragment with n columns till the right 332 var fragment = document.createDocumentFragment() 333 , numberOfColumnsToExtract = elements.length % numberOfColumns 334 ; 335 336 while (numberOfColumnsToExtract-- !== 0) { 337 fragment.appendChild(grid.lastChild); 338 } 339 340 // adds the fragment to the left 341 grid.insertBefore(fragment, grid.firstChild); 342}; 343 344 345self.registerGrid = function registerGrid (grid) { 346 if (global.getComputedStyle(grid).display === "none") { 347 return; 348 } 349 350 // retrieve the list of items from the grid itself 351 var range = document.createRange(); 352 range.selectNodeContents(grid); 353 354 var items = document.createElement("div"); 355 items.appendChild(range.extractContents()); 356 357 358 add_to_dataset(items, 'columns', 0); 359 self.addColumns(grid, items); 360 grids.push(grid); 361}; 362 363
364self.init = function init() { 365 // adds required CSS rule to hide 'content' based 366 // configuration. 367 368 var css = document.createElement("style"); 369 css.innerHTML = "[data-columns]::before{visibility:hidden;position:absolute;font-size:1px;}"; 370 document.head.appendChild(css); 371 372 // scans all the grids in the document and generates 373 // columns from their configuration. 374 375 var gridElements = document.querySelectorAll("[data-columns]"); 376 Array.prototype.forEach.call(gridElements, self.registerGrid); 377 self.scanMediaQueries(); 378}; 379 380self.init(); 381 382return { 383 appendElements: self.appendElements, 384 prependElements: self.prependElements, 385 registerGrid: self.registerGrid, 386 recreateColumns: self.recreateColumns, 387 rescanMediaQueries: self.rescanMediaQueries, 388 389 // maintains backwards compatibility with underscore style method names 390 append_elements: self.appendElements, 391 prepend_elements: self.prependElements, 392 register_grid: self.registerGrid, 393 recreate_columns: self.recreateColumns, 394 rescan_media_queries: self.rescanMediaQueries 395}; 396 397})(window, window.document);
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.