shell bypass 403
/* Simple min-height-masonry layout plugin. Like masonry column shift, but works. */ ;(function($) { 'use strict'; // get css prefix for current browser var cssPrefix = detectCSSPrefix(); /** * @desc Plugin prototype definition. * - just run function ._create * @param {jQuery} el - jquery dom object * @param {Object} opts - options used in plugin * @constructor */ var Waterfall = function(el, opts) { // get dom refs this.$el = $(el); this.el = el[0]; // run internal function to create plugin this._create(opts); }; // set default class for plugin Waterfall.defaultClass = 'waterfall'; /** * @desc extend definition of plugin prototype. * - add default options * - add all internal methods used by plugin. */ $.extend(Waterfall.prototype, { options: { colMinWidth: 300, //width of column, used to calculate number of columns possible to display defaultContainerWidth: window.clientWidth, autoresize: true, maxCols: 16, //used to restrict max number of columns updateDelay: 45, //how often to reflow layout on window resize useCalc: undefined, //set width through -prefix-calc value. Values: true, false, undefined. Autodetection. useTranslate3d: undefined, //place items through translate3d instead of top/left. Values: true, false, undefined. Autodetection animateShow: false, //whether to animate appending items (causes browser extra-reflows, slows down rendering) //callbacks reflow: null }, /** * @desc make plugin works. * - hide container, * - check if plugin should use calc or css translate3d * - get dom childrens of container, save it in items attr and remove text node * - update styles of each children on list * - add window resize listener if needed * - add MutationObserver to remove/add new items on list if browser will handle this * @param {Object} opts - passed in plugin init * @private */ _create: function(opts) { // local vard var self = this, o = self.options = $.extend({}, self.options, opts); // init basic vars this.items = []; self.lastHeights = []; self.lastItems = []; self.colPriority = []; //most left = most minimal column self.baseOrder = []; // get styles of container var cStyle = getComputedStyle(self.el); // hide element self.el.hidden = true; // prevent scrollbar width changing self.el.style.minHeight = cStyle.height; // set position relative if contianer have static position if (self.$el.css('position') === 'static') self.el.style.position = 'relative'; //detect placing mode needed // check if useCalc option is setted by used if (o.useCalc === undefined) { /** * @desc check if calc function can be used by browser */ this.prefixedCalc = (function() { // get test dom element var dummy = document.createElement('div'), // set list of properties to test props = ['calc', '-webkit-calc', '-moz-calc', '-o-calc']; // check each property from list for (var i = 0; i < props.length; ++i) { var prop = props[i], propStr = prop + '(1px)'; // create css style needed to test dummy.style.cssText = cssPrefix + 'transform: translate3d(' + [propStr, propStr, propStr].join(',') +');'; //console.log(dummy.style[cssPrefix + 'transform']) // check if dom have needed styles apply if (dummy.style.length && dummy.style[cssPrefix + 'transform'].length > 14) { return prop; } } })(); // change options useCalc and verify is calc function is used by browser o.useCalc = !!this.prefixedCalc; } //console.log(this.prefixedCalc); // check if useCalc option is setted by used if (o.useTranslate3d === undefined) { /** * @desc check if browser can use css translate3d propery */ this.prefixedTranslate3d = (function() { // get test dom element var dummy = document.createElement('div'); // set list of properties to test var props = ['translate3d', '-webkit-translate3d', '-moz-translate3d', '-o-translate3d']; for (var i = 0; i < props.length; ++i) { var prop = props[i]; // create css style needed to test dummy.style.cssText = cssPrefix + 'transform:' + prop + '(1px, 0, 0);'; // check if dom have needed styles apply if (dummy.style.length){ return prop; } } })(); // check if browser have use css translate3d property o.useTranslate3d = !!this.prefixedTranslate3d; } //console.log(this.prefixedTranslate3d) //populate items var items; // get list of dom childerns { items = self.$el.children(); } // remove text nodes for (var i = 0; i < self.el.childNodes.length;) { // check dom node type if (self.el.childNodes[i].nodeType !== 1 && self.el.childNodes[i].nodeType !== 8){ self.el.removeChild(self.el.childNodes[i]); } else { i++; } } // for each children add item to list and init styles items.each(function(i, e) { //self.items[i].data('id', i); // add item to internal items list self._addItem(e); // apply needed styles to item self._initItem(e); }); // get dom refs to last children self.lastItem = self.items[self.items.length - 1]; // show container self.el.removeAttribute("hidden"); // set proper styles for each item in items array self._update(); // verify is autoresise is on if (o.autoresize) { // trigger reflow function when window resize event occure $(window) .resize(self.reflow.bind(self)); } // use MutationObserver functionality to add/remove items on list if browser can handle this this._observeMutations(); }, /** * @desc add item to internal list od items * @param {jQuery dom node} item * @private */ _addItem: function(item){ // check if item shouldnt be added to list if (item.getAttribute("data-exclude")) return; // add item to array this.items.push(item); }, /** * @desc Make Node changing observer - the fastest way to add items * - on dom change sync internal array of items * @private */ _observeMutations: function() { // check if browser support observers if (window.MutationObserver) { //FF, chrome // create new observer for children nodes this.observer = new MutationObserver(function(mutations) { // get size of changes var mNum = mutations.length; // for each change take action for (var i = 0; i < mNum; i++) { //console.log(mutations[i]) // check if items were removed if (mutations[i].removedNodes.length) { // remove items from internal array of items this._removedItems(Array.prototype.slice.apply(mutations[i].removedNodes)); } // check if items were added if (mutations[i].addedNodes.length) { // add items to internal array of items var nodes = Array.prototype.slice.apply(mutations[i].addedNodes); // add nodes to dom if (mutations[i].nextSibling) { this._insertedItems(nodes); } else { this._appendedItems(nodes); } } } }.bind(this)); // set observe all childrens of container this.observer.observe(this.el, { attributes: false, childList: true, characterData: false }); } else { //opera, ie this.$el // handle action when new dom was inserted .on('DOMNodeInserted', function(e) { var evt = (e.originalEvent || e), target = evt.target; // check is new node is text if (target.nodeType !== 1 && target.nodeType !== 8) return; //if insertee is below container if (target.parentNode !== this.el) return; //console.log("--------" + target.className + " next:" + target.nextSibling + " prev:" + target.previousSibling) // check if new item have special case if (target.previousSibling && target.previousSibling.span && (!target.nextSibling || !target.nextSibling.span)) { //append specific case, times faster than _insertedItems this._appendedItems([target]); } else { this._insertedItems([target]); } }.bind(this)) // handle action when dom was removed .on('DOMNodeRemoved', function(e) { // get target var el = (e.originalEvent || e).target; // check is removed node was text if (el.nodeType !== 1 && el.nodeType !== 8) return; //if insertee is below container if (el.parentNode !== this.el) return; // remove item from list this._removedItems([el]); }.bind(this)); } }, /** * @desc API :: Ensures column number correct, reallocates items * @returns {Waterfall} */ reflow: function() { // get local vars var self = this, o = self.options; // clear timeout from last timeout window.clearTimeout(self._updateInterval); // trigger _update function after timeout have done self._updateInterval = window.setTimeout(self._update.bind(self), o.updateDelay); // return Waterfall instance return self; }, /** * @desc sync passed array of items with internal list of item and update position of each item * - called by mutation observer * @param {Array} items - list of container childrens, jquery dom objects * @private */ _appendedItems: function(items) { // local vars var l = items.length, i = 0; //console.log("append: " + this.items.length) // touch each item on list for (; i < l; i++) { // get item var el = items[i]; // check item type. Dont touch text node if (el.nodeType !== 1) continue; // append item to array of items this._addItem(el); // set styles for dom item //TODO: optimize this._initItem(el); // set width based on calculated valued this._setItemWidth(el); } // update position of each item in array for (i = 0; i < l; i++) { // dont touch text nodes if (items[i].nodeType !== 1) continue; // udpdate position this._placeItem(items[i]); } // update refs to last item this.lastItem = this.items[this.items.length - 1]; // set proper height of container this._maximizeHeight(); }, /** * @desc sync passed array of items with internal list of item and update position of each item * - if new items inserted somewhere inside the list * @param {Array} items - list of container childrens, jquery dom objects * @private */ _insertedItems: function(items) { //console.log("insert: " + this.items.length) //clear old items this.items.length = 0; //init new items var l = items.length; for (var i = 0; i < l; i++) { // get item var el = items[i]; // check item type. Dont touch text node if (el.nodeType !== 1 && el.nodeType !== 8) continue; // init styles for dom item //TODO: optimize this._initItem(el); // set width based on calculated values this._setItemWidth(el); } // reinit all items var children = this.el.childNodes, itemsL = children.length; for (var i = 0; i < itemsL; i++){ // check item type. Dont touch text node if (children[i].nodeType !== 1 && el.nodeType !== 8) continue; if (!children[i].span) continue; // add item to internal list of items this._addItem(children[i]); } // update refs to last item this.lastItem = this.items[this.items.length - 1]; // trigger update styles of items this.reflow(); }, /** * @desc sync passed array of items with internal list of item and update position of each item * - called by mutation observer * @param {Array} items - list of container childrens, jquery dom objects * @private */ _removedItems: function(items) { // get local vars var childItems = this.el.childNodes, cl = childItems.length; //console.log("before removed: " + this.items.length) // reinit items for (var i = 0; i < items.length; i++){ // add/remove items to list this.items.splice(this.items.indexOf(items[i]), 1); } //console.log("after remove:" + this.items.length) // refresh last item refs this.lastItem = this.items[this.items.length - 1]; // trigger update styles of items this.reflow(); }, /** * @desc simple trigger routine * @param cbName * @param arg * @private */ _trigger: function(cbName, arg) { try { // call event on container if (this.options[cbName]){ this.options[cbName].call(this.$el, arg); } // trigger event this.$el.trigger(cbName, [arg]); } catch (err) { // throw err if occur throw (err); } }, /** * @desc init item properties once item appended * @param {jquery dom object} el - children of container * @private */ _initItem: function(el) { // get variables var o = this.options, span = el.getAttribute('data-span') || 1, floatVal = el.getAttribute('data-float') || el.getAttribute('data-column'); // set span span = (span === 'all' ? o.maxCols : Math.max(0, Math.min(~~(span), o.maxCols))); //quite bad, but no choice: dataset is sloow el.span = span; // save heavy style-attrs var style = getComputedStyle(el); el.mr = ~~(style.marginRight.slice(0, -2)); el.ml = ~~(style.marginLeft.slice(0, -2)); el.bt = ~~(style.borderTopWidth.slice(0, -2)); el.bb = ~~(style.borderBottomWidth.slice(0, -2)); el.mt = ~~(style.marginTop.slice(0, -2)); //ignored because of offsetTop instead of style.top el.mb = ~~(style.marginBottom.slice(0, -2)); // set style el.style.position = 'absolute'; //this._setItemWidth(el); //make it external action to not to init frominside create // parset float switch (floatVal) { case null: //no float el.floatCol = null; break; case 'right': case 'last': el.floatCol = -span; break; case 'left': case 'first': el.floatCol = 0; break; default: //int column el.floatCol = ~~(floatVal) - 1; break; } // check options if (o.animateShow) { // check if should be used css if (o.useTranslate3d) { //TODO: this below crashes chrome //el.style[cssPrefix+'translate'] = 'translate3d(0, ' + this.lastHeights[this.colPriority[0]] + 'px ,0)' } else { // set style for each item. Default value el.style.top = this.lastHeights[this.colPriority[this.colPriority.length - 1]] + 'px'; el.style.left = this.colWidth * this.colPriority[this.colPriority.length - 1] + 'px'; } // show item el.removeAttribute('hidden'); } }, /** * @desc * @todo make docs * @returns {Number} * @private */ _initLayoutParams: function() { // set local vars var self = this, o = self.options, cStyle = window.getComputedStyle(self.el), i = 0, prevCols = self.lastItems.length; self.pl = ~~(cStyle.paddingLeft.slice(0, -2)); self.pt = ~~(cStyle.paddingTop.slice(0, -2)); self.pr = ~~(cStyle.paddingRight.slice(0, -2)); self.pb = ~~(cStyle.paddingBottom.slice(0, -2)); self.lastHeights.length = 0; self.colPriority.length = 0; //most left = most minimal column self.baseOrder.length = 0; self.colWidth = self.el.offsetWidth - self.pl - self.pr; self.lastItems.length = ~~(self.colWidth / o.colMinWidth) || 1; //needed length // console.log(o.colMinWidth) var top = o.useTranslate3d ? 0 : self.pt; for (i = 0; i < self.lastItems.length; i++) { self.lastHeights.push(top); self.baseOrder.push(i); self.colPriority.push(i); } self.colWidth /= self.lastItems.length; //console.log(prevCols + '->' + self.lastItems.length); if (!o.useCalc || prevCols !== self.lastItems.length) { //set item widths carefully - if columns changed or px widths used for (i = self.items.length; i--;) { this._setItemWidth(self.items[i]); } } return self.lastItems.length; }, // full update of layout _updateInterval: 0, /** * @desc trigger update position of each item, container and run reflow * @param {Integer} from - number between items should be updated * @param {Integer} to - number between items should be updated * @private */ _update: function(from, to) { //window.start = Date.now() // set local vars var self = this, i = 0, start = from || 0, end = to || self.items.length, colsNeeded = self._initLayoutParams(); //console.log('beforePlace:' + this.lastItems.length) // update styles of each item in array of childrens for (i = start; i < end; i++) { self._placeItem(self.items[i]); } //console.log('afterPlace:' + this.lastItems.length) // set proper height of container self._maximizeHeight(); // trigger reflow of each item self._trigger('reflow'); //console.log('time elapsed: ' + (Date.now() - window.start) + 'ms') }, /** * @desc set item width based on span/colWidth * @param {jquery dom object} el - element which should be changed * @private */ _setItemWidth: function(el) { // get amount of items var span = el.span > this.lastItems.length ? this.lastItems.length : el.span, // get amount of columns cols = this.lastItems.length, // one column width in percentage colWeight = span / cols; // check if use css calc function if (this.options.useCalc) { // get 100% of width el.w = (100 * colWeight); // set item width based of columns amount, margins and paddings el.style.width = this.prefixedCalc + '(' + (100 * colWeight) + '% - ' + (el.mr + el.ml + (this.pl + this.pr) * colWeight) + 'px)'; } else { // set new width based on columns amount and margins el.w = ~~(this.colWidth * span - (el.ml + el.mr)); // se new width el.style.width = el.w + 'px'; } }, /** * @desc set position of item in array of items. * @todo add docs * @param {jquery dom object} e - element which should be changed * @private */ _placeItem: function(e) { // set local vars var self = this, o = self.options; var lastHeights = self.lastHeights, lastItems = self.lastItems, colPriority = self.colPriority, minCol = 0, minH = 0, h = 0, c = 0, t = 0, end = 0, start = 0, span = e.span > lastItems.length ? lastItems.length : e.span, newH = 0, spanCols = [], //numbers of spanned columns spanHeights = [], //heights of spanned columns style, floatCol = e.floatCol; //console.log('------ item:' + e.innerHTML) //console.log('span:'+span) //Find pro→per column to place item //console.log(colPriority) if (floatCol) { floatCol = floatCol > 0 ? Math.min(floatCol, lastItems.length - span) : (lastItems.length + floatCol); } // check amount of columns if (span === 1) { // single-span element if (floatCol === null) { //no align minCol = colPriority.shift(); } else { //predefined column to align minCol = floatCol; for (c = 0; c < colPriority.length; c++) { if (colPriority[c] == minCol) { colPriority.splice(c, 1); break; } } } spanCols.push(minCol); minH = lastHeights[minCol]; } else if (span >= lastItems.length) { //Full-span element minCol = 0; minH = lastHeights[colPriority[colPriority.length - 1]]; spanCols = self.baseOrder.slice(); spanCols.length = lastHeights.length; colPriority.length = 0; } else { //Some-span element if (floatCol !== null) { minCol = floatCol; minH = Math.max.apply(Math, lastHeights.slice(minCol, minCol + span)); //console.log(lastHeights.slice(minCol, span)) //console.log('fCol:' + floatCol + ' minH: ' + minH) } else { //Make span heights alternatives spanHeights.length = 0; minH = Infinity; minCol = 0; for (c = 0; c <= lastItems.length - span; c++) { spanHeights[c] = Math.max.apply(Math, lastHeights.slice(c, c + span)); if (spanHeights[c] < minH) { minCol = c; minH = spanHeights[c]; } } } //Replace priorities for (c = 0; c < colPriority.length;) { if (colPriority[c] >= minCol && colPriority[c] < minCol + span) { spanCols.push(colPriority.splice(c, 1)[0]); } else c++; } } //console.log(spanCols) //console.log(lastHeights) //console.log('↑ spanCols to ↓') //TODO: correct to work ok with options e.top = ~~minH; //stupid save value for translate3d if (o.useTranslate3d) { var offset = (100 * minCol / span) + '% + ' + ~~((e.ml + e.mr) * minCol / span) + 'px'; if (o.useCalc) { e.style[cssPrefix + 'transform'] = this.prefixedTranslate3d + '( ' + this.prefixedCalc + '(' + offset + '), ' + e.top + 'px, 0)'; } else { //Safari won't set -webkit-calc in element.style e.style[cssPrefix + 'transform'] = this.prefixedTranslate3d + '(' + ~~(self.colWidth * minCol) + 'px, ' + e.top + 'px, 0)'; } } else { e.style.top = e.top + 'px'; e.style.left = self.colWidth * minCol + self.pl + 'px'; } //console.log(e.style[cssPrefix + 'transform']) //if element was added first time and is out of flow - show it //e.style.opacity = 1; e.removeAttribute('hidden'); newH = self._getBottom(e); //this is the most difficult operation (e.clientHeight) for (t = 0; t < spanCols.length; t++) { lastItems[spanCols[t]] = e; self.lastHeights[spanCols[t]] = newH; } //console.log(lastItems) //console.log('↑ self.lastHeights to ↓') //console.log(self.lastHeights) //console.log('minCol:'+minCol+' minH:'+minH+' newH:'+newH) //console.log(colPriority) //console.log('↑ colPriorities to ↓') //Update colPriority for (c = colPriority.length; c--;) { h = self.lastHeights[colPriority[c]]; if (newH >= h) { Array.prototype.splice.apply(colPriority, [c + 1, 0].concat(spanCols)); break; } } if (colPriority.length < lastHeights.length) { Array.prototype.unshift.apply(colPriority, spanCols); //self.colPriority = spanCols.concat(colPriority) } }, /** * @desc get bottom edge position of item(in pixels) * @param {jquery dom object} e - item * @returns {*} * @private */ _getBottom: function(e) { // check if param is seteted if (!e) return 0; //this.pt; //TODO: memrize height, look for height change to avoid reflow return e.top + e.offsetHeight + e.bt + e.bb + e.mb + e.mt; }, /** * @desc update style(minHeight) of container * @private */ _maximizeHeight: function() { // get top position var top = this.options.useTranslate3d ? this.pt : 0; // set new height based on padding, height and position of last item in height this.el.style.minHeight = this.lastHeights[this.colPriority[this.colPriority.length - 1]] + this.pb + top + 'px'; } }); /** * @desc register plugin as jq library. * - Init plugin for each item in selector if arg is string * - Verify plugin dom refs and init plugin with arg2 as options. Moreover check min width of column. * @param arg - selector || jq dom item * @param arg2 - options * @returns {*} */ $.fn.waterfall = function(arg, arg2) { //Call API method if (typeof arg == 'string') { // init plugin for each jQ object from selector return $(this).each(function(i, el) { $(el).data('waterfall')[arg](arg2); }); } else { // check amount of dom refs if (!this.length) { throw new Error("No element passed to waterfall"); return false; } // get basic values var $this = $(this), // set default options opts = $.extend({}, { // try to get minimal column width from html attr "colMinWidth": ~~$this[0].getAttribute("data-col-min-width") || ~~$this[0].getAttribute("data-width") }, arg); // set minimal column width of container if is not setted if (opts.width && !opts.colMinWidth) { opts.colMinWidth = opts.width; } // run plugin var wf = new Waterfall($this, opts); // set plugin instance reference if (!$this.data('waterfall')) $this.data('waterfall', wf); // return plugin instance return wf; } }; /** * @desc Get name of css prefix based on document.defaultView styles * @param {String} property * @returns {*} */ function detectCSSPrefix(property) { // check default values if (!property) property = 'transform'; // get values of all css properties that document.body can have var style = document.defaultView.getComputedStyle(document.body, ''); // check if style property is in object if (style[property]) return ''; if (style['-webkit-' + property]) return '-webkit-'; if (style['-moz-' + property]) return '-moz-'; if (style['-o-' + property]) return '-o-'; if (style['-khtml-' + property]) return '-khtml-'; // false if non of options is proper attr return false; } // run plugin after document ready // $(function() { // get name of class for plugin var defClass = window.waterfall && window.waterfall.defaultClass || Waterfall.defaultClass; // find dom refs and init plugin $('.' + defClass) .each(function(i, e) { // get jQ dom ref and run plugin. var $e = $(e), opts = window.waterfall || {}; // init plugin $e.waterfall(opts); }); }); })(window.jQuery || window.Zepto);