MINI Sh3ll
/* jshint laxcomma: true */
var salvattore = (function (global, document, undefined) {
"use strict";
var self = {},
grids = [],
mediaRules = [],
mediaQueries = [],
add_to_dataset = function(element, key, value) {
// uses dataset function or a fallback for <ie10
if (element.dataset) {
element.dataset[key] = value;
} else {
element.setAttribute("data-" + key, value);
}
return;
};
self.obtainGridSettings = function obtainGridSettings(element) {
// returns the number of columns and the classes a column should have,
// from computing the style of the ::before pseudo-element of the grid.
var computedStyle = global.getComputedStyle(element, ":before")
, content = computedStyle.getPropertyValue("content").slice(1, -1)
, matchResult = content.match(/^\s*(\d+)(?:\s?\.(.+))?\s*$/)
, numberOfColumns = 1
, columnClasses = []
;
if (matchResult) {
numberOfColumns = matchResult[1];
columnClasses = matchResult[2];
columnClasses = columnClasses? columnClasses.split(".") : ["column"];
} else {
matchResult = content.match(/^\s*\.(.+)\s+(\d+)\s*$/);
if (matchResult) {
columnClasses = matchResult[1];
numberOfColumns = matchResult[2];
if (numberOfColumns) {
numberOfColumns = numberOfColumns.split(".");
}
}
}
return {
numberOfColumns: numberOfColumns,
columnClasses: columnClasses
};
};
self.addColumns = function addColumns(grid, items) {
// from the settings obtained, it creates columns with
// the configured classes and adds to them a list of items.
var settings = self.obtainGridSettings(grid)
, numberOfColumns = settings.numberOfColumns
, columnClasses = settings.columnClasses
, columnsItems = new Array(+numberOfColumns)
, columnsFragment = document.createDocumentFragment()
, i = numberOfColumns
, selector
;
while (i-- !== 0) {
selector = "[data-columns] > *:nth-child(" + numberOfColumns + "n-" + i + ")";
columnsItems.push(items.querySelectorAll(selector));
}
columnsItems.forEach(function append_to_grid_fragment(rows) {
var column = document.createElement("div")
, rowsFragment = document.createDocumentFragment()
;
column.className = columnClasses.join(" ");
Array.prototype.forEach.call(rows, function append_to_column(row) {
rowsFragment.appendChild(row);
});
column.appendChild(rowsFragment);
columnsFragment.appendChild(column);
});
grid.appendChild(columnsFragment);
add_to_dataset(grid, 'columns', numberOfColumns);
};
self.removeColumns = function removeColumns(grid) {
// removes all the columns from a grid, and returns a list
// of items sorted by the ordering of columns.
var range = document.createRange();
range.selectNodeContents(grid);
var columns = Array.prototype.filter.call(range.extractContents().childNodes, function filter_elements(node) {
return node instanceof global.HTMLElement;
});
var numberOfColumns = columns.length
, numberOfRowsInFirstColumn = columns[0].childNodes.length
, sortedRows = new Array(numberOfRowsInFirstColumn * numberOfColumns)
;
Array.prototype.forEach.call(columns, function iterate_columns(column, columnIndex) {
Array.prototype.forEach.call(column.children, function iterate_rows(row, rowIndex) {
sortedRows[rowIndex * numberOfColumns + columnIndex] = row;
});
});
var container = document.createElement("div");
add_to_dataset(container, 'columns', 0);
sortedRows.filter(function filter_non_null(child) {
return !!child;
}).forEach(function append_row(child) {
container.appendChild(child);
});
return container;
};
self.recreateColumns = function recreateColumns(grid) {
// removes all the columns from the grid, and adds them again,
// it is used when the number of columns change.
global.requestAnimationFrame(function render_after_css_mediaQueryChange() {
self.addColumns(grid, self.removeColumns(grid));
var columnsChange = new CustomEvent("columnsChange");
grid.dispatchEvent(columnsChange);
});
};
self.mediaQueryChange = function mediaQueryChange(mql) {
// recreates the columns when a media query matches the current state
// of the browser.
if (mql.matches) {
Array.prototype.forEach.call(grids, self.recreateColumns);
}
};
self.getCSSRules = function getCSSRules(stylesheet) {
// returns a list of css rules from a stylesheet
var cssRules;
try {
cssRules = stylesheet.sheet.cssRules || stylesheet.sheet.rules;
} catch (e) {
return [];
}
return cssRules || [];
};
self.getStylesheets = function getStylesheets() {
// returns a list of all the styles in the document (that are accessible).
var inlineStyleBlocks = Array.prototype.slice.call(document.querySelectorAll("style"));
inlineStyleBlocks.forEach(function(stylesheet, idx) {
if (stylesheet.type !== 'text/css' && stylesheet.type !== '') {
inlineStyleBlocks.splice(idx, 1);
}
});
return Array.prototype.concat.call(
inlineStyleBlocks,
Array.prototype.slice.call(document.querySelectorAll("link[rel='stylesheet']"))
);
};
self.mediaRuleHasColumnsSelector = function mediaRuleHasColumnsSelector(rules) {
// checks if a media query css rule has in its contents a selector that
// styles the grid.
var i, rule;
try {
i = rules.length;
}
catch (e) {
i = 0;
}
while (i--) {
rule = rules[i];
if (rule.selectorText && rule.selectorText.match(/\[data-columns\](.*)::?before$/)) {
return true;
}
}
return false;
};
self.scanMediaQueries = function scanMediaQueries() {
// scans all the stylesheets for selectors that style grids,
// if the matchMedia API is supported.
var newMediaRules = [];
if (!global.matchMedia) {
return;
}
self.getStylesheets().forEach(function extract_rules(stylesheet) {
Array.prototype.forEach.call(self.getCSSRules(stylesheet), function filter_by_column_selector(rule) {
// rule.media throws an 'not implemented error' in ie9 for import rules occasionally
try {
if (rule.media && rule.cssRules && self.mediaRuleHasColumnsSelector(rule.cssRules)) {
newMediaRules.push(rule);
}
} catch (e) {}
});
});
// remove matchMedia listeners from the old rules
var oldRules = mediaRules.filter(function (el) {
return newMediaRules.indexOf(el) === -1;
});
mediaQueries.filter(function (el) {
return oldRules.indexOf(el.rule) !== -1;
}).forEach(function (el) {
el.mql.removeListener(self.mediaQueryChange);
});
mediaQueries = mediaQueries.filter(function (el) {
return oldRules.indexOf(el.rule) === -1;
});
// add matchMedia listeners to the new rules
newMediaRules.filter(function (el) {
return mediaRules.indexOf(el) == -1;
}).forEach(function (rule) {
var mql = global.matchMedia(rule.media.mediaText);
mql.addListener(self.mediaQueryChange);
mediaQueries.push({rule: rule, mql:mql});
});
// swap mediaRules with the new set
mediaRules.length = 0;
mediaRules = newMediaRules;
};
self.rescanMediaQueries = function rescanMediaQueries() {
self.scanMediaQueries();
Array.prototype.forEach.call(grids, self.recreateColumns);
};
self.nextElementColumnIndex = function nextElementColumnIndex(grid, fragments) {
// returns the index of the column where the given element must be added.
var children = grid.children
, m = children.length
, lowestRowCount = 0
, child
, currentRowCount
, i
, index = 0
;
for (i = 0; i < m; i++) {
child = children[i];
currentRowCount = child.children.length + (fragments[i].children || fragments[i].childNodes).length;
if(lowestRowCount === 0) {
lowestRowCount = currentRowCount;
}
if(currentRowCount < lowestRowCount) {
index = i;
lowestRowCount = currentRowCount;
}
}
return index;
};
self.createFragmentsList = function createFragmentsList(quantity) {
// returns a list of fragments
var fragments = new Array(quantity)
, i = 0
;
while (i !== quantity) {
fragments[i] = document.createDocumentFragment();
i++;
}
return fragments;
};
self.appendElements = function appendElements(grid, elements) {
// adds a list of elements to the end of a grid
var columns = grid.children
, numberOfColumns = columns.length
, fragments = self.createFragmentsList(numberOfColumns)
;
Array.prototype.forEach.call(elements, function append_to_next_fragment(element) {
var columnIndex = self.nextElementColumnIndex(grid, fragments);
fragments[columnIndex].appendChild(element);
});
Array.prototype.forEach.call(columns, function insert_column(column, index) {
column.appendChild(fragments[index]);
});
};
self.prependElements = function prependElements(grid, elements) {
// adds a list of elements to the start of a grid
var columns = grid.children
, numberOfColumns = columns.length
, fragments = self.createFragmentsList(numberOfColumns)
, columnIndex = numberOfColumns - 1
;
elements.forEach(function append_to_next_fragment(element) {
var fragment = fragments[columnIndex];
fragment.insertBefore(element, fragment.firstChild);
if (columnIndex === 0) {
columnIndex = numberOfColumns - 1;
} else {
columnIndex--;
}
});
Array.prototype.forEach.call(columns, function insert_column(column, index) {
column.insertBefore(fragments[index], column.firstChild);
});
// populates a fragment with n columns till the right
var fragment = document.createDocumentFragment()
, numberOfColumnsToExtract = elements.length % numberOfColumns
;
while (numberOfColumnsToExtract-- !== 0) {
fragment.appendChild(grid.lastChild);
}
// adds the fragment to the left
grid.insertBefore(fragment, grid.firstChild);
};
self.registerGrid = function registerGrid (grid) {
if (global.getComputedStyle(grid).display === "none") {
return;
}
// retrieve the list of items from the grid itself
var range = document.createRange();
range.selectNodeContents(grid);
var items = document.createElement("div");
items.appendChild(range.extractContents());
add_to_dataset(items, 'columns', 0);
self.addColumns(grid, items);
grids.push(grid);
};
self.init = function init() {
// adds required CSS rule to hide 'content' based
// configuration.
var css = document.createElement("style");
css.innerHTML = "[data-columns]::before{display:block;visibility:hidden;position:absolute;font-size:1px;}";
document.head.appendChild(css);
// scans all the grids in the document and generates
// columns from their configuration.
var gridElements = document.querySelectorAll("[data-columns]");
Array.prototype.forEach.call(gridElements, self.registerGrid);
self.scanMediaQueries();
};
self.init();
return {
appendElements: self.appendElements,
prependElements: self.prependElements,
registerGrid: self.registerGrid,
recreateColumns: self.recreateColumns,
rescanMediaQueries: self.rescanMediaQueries,
init: self.init,
// maintains backwards compatibility with underscore style method names
append_elements: self.appendElements,
prepend_elements: self.prependElements,
register_grid: self.registerGrid,
recreate_columns: self.recreateColumns,
rescan_media_queries: self.rescanMediaQueries
};
})(window, window.document);
OHA YOOOO