mirror of
https://github.com/yogeshojha/rengine.git
synced 2026-09-05 17:37:41 +02:00
1862 lines
62 KiB
JavaScript
1862 lines
62 KiB
JavaScript
/**
|
|
* Gridzy 2.3.0
|
|
*
|
|
* Copyright 2020
|
|
* Helmut Wandl
|
|
* https://ehtmlu.com/
|
|
*/
|
|
|
|
(function () {
|
|
'use strict';
|
|
|
|
var version = '2.3.0';
|
|
|
|
var defaultOptionsTpl = {
|
|
animate: true,
|
|
layout: 'justified',
|
|
filter: '*',
|
|
filterControls: '',
|
|
autoConsiderScrollBars: true,
|
|
onBeforeOptionsChanged: function () {},
|
|
onOptionsChanged: function () {},
|
|
onBeforeRender: function () {},
|
|
onRender: function () {},
|
|
autoInitOnDomReady: true,
|
|
autoInitOnDomMutation: true,
|
|
autoSyncChildListMutation: true,
|
|
useOptionAttributes: true,
|
|
autoSyncAttributesMutation: true,
|
|
autoSyncChildClassMutation: true
|
|
};
|
|
|
|
var defaultOptionsLayoutTpl = {};
|
|
|
|
var defaultOptionsCompleted = null;
|
|
|
|
var idAutoIncrement = 0;
|
|
|
|
var lazyLoadImagesQueue = null;
|
|
|
|
var helper = {
|
|
isTruly: function (i) {
|
|
return !!i && (typeof i !== 'string' || i.toLowerCase() !== 'false');
|
|
},
|
|
expectArray: function (i) {
|
|
return helper.isArrayLike(i) ? i : [i];
|
|
},
|
|
isArrayLike: function (i) {
|
|
return i.length === +i.length && typeof i !== 'string';
|
|
},
|
|
inArray: function (arr, elt) {
|
|
return arr.indexOf(elt) !== -1;
|
|
},
|
|
objectWalk: function (obj, func) {
|
|
var a;
|
|
func(obj);
|
|
if (typeof obj === 'object') {
|
|
for (a in obj) {
|
|
helper.objectWalk(obj[a], func);
|
|
}
|
|
}
|
|
},
|
|
objectAssign: function (target) {
|
|
var pos,
|
|
name;
|
|
for (pos = 1; pos < arguments.length; pos++) {
|
|
for (name in arguments[pos]) {
|
|
target[name] = arguments[pos][name];
|
|
}
|
|
}
|
|
return target;
|
|
},
|
|
optionsMerge: function (target, add) {
|
|
var name;
|
|
for (name in add) {
|
|
if (typeof target[name] !== 'undefined') {
|
|
target[name] = add[name];
|
|
}
|
|
}
|
|
return target;
|
|
},
|
|
isValidCssSelector: function (selector) {
|
|
var result = false;
|
|
if (typeof selector === 'string') {
|
|
try {
|
|
helper.matches(document.body, selector);
|
|
result = true;
|
|
} catch (e) {}
|
|
}
|
|
return result;
|
|
},
|
|
matches: Element.prototype.matches ? function (element, selector) {
|
|
return element.matches(selector);
|
|
} : (Element.prototype.msMatchesSelector ? function (element, selector) {
|
|
return element.msMatchesSelector(selector);
|
|
} : (Element.prototype.webkitMatchesSelector ? function (element, selector) {
|
|
return element.webkitMatchesSelector(selector);
|
|
} : function (element, selector) {
|
|
var matches = (element.document || element.ownerDocument).querySelectorAll(selector),
|
|
i = matches.length;
|
|
while (--i >= 0 && matches.item(i) !== element) {}
|
|
return i > -1;
|
|
})),
|
|
changeMultipleClassNames: (function () { // IE11 support for adding multiple css classes
|
|
var list = document.createElement('div').classList;
|
|
list.add('a', 'b');
|
|
return list.contains('b');
|
|
})() ? function (element, add, classNames) {
|
|
element.classList[add ? 'add' : 'remove'].apply(element.classList, classNames);
|
|
} : function (element, add, classNames) {
|
|
var length = classNames.length,
|
|
change = add ? 'add' : 'remove',
|
|
pos = length;
|
|
while (pos--) {
|
|
element.classList[change](classNames[length - pos - 1]);
|
|
}
|
|
},
|
|
eventListener: (function () {
|
|
var eventListeners = [];
|
|
|
|
return {
|
|
add: function(relatedGridzyInstance, relatedItem, element, type, handler, options, removeWhenOccurred) {
|
|
|
|
eventListeners.push([relatedGridzyInstance, relatedItem, element, type, handler, options]);
|
|
|
|
element.addEventListener(type, removeWhenOccurred ? function(event) {
|
|
helper.eventListener.remove(relatedGridzyInstance, relatedItem, element, type, handler, options);
|
|
return handler(event);
|
|
} : handler, options);
|
|
},
|
|
remove: function(relatedGridzyInstance, relatedItem, element, type, handler, options) {
|
|
var pos = eventListeners.length;
|
|
var eventListener;
|
|
while (pos--) {
|
|
eventListener = eventListeners[pos];
|
|
if (relatedGridzyInstance === undefined || relatedGridzyInstance === eventListener[0]) {
|
|
if (relatedItem === undefined || relatedItem === eventListener[1]) {
|
|
if (element === undefined || element === eventListener[2]) {
|
|
if (type === undefined || type === eventListener[3]) {
|
|
if (handler === undefined || handler === eventListener[4]) {
|
|
if (options === undefined || options === eventListener[5]) {
|
|
eventListener[2].removeEventListener(eventListener[3], eventListener[4], eventListener[5]);
|
|
eventListeners.splice(pos, 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
})(),
|
|
expectNaturalImageSize: (function () {
|
|
var interval, images = [], update;
|
|
|
|
update = function () {
|
|
var a;
|
|
for (a = 0; a < images.length; a++) {
|
|
if (images[a].img.naturalWidth && images[a].img.naturalHeight || images[a].ready) {
|
|
images[a].callback(images[a].img, !images[a].error);
|
|
images.splice(a, 1);
|
|
a--;
|
|
}
|
|
}
|
|
|
|
if (!images.length) {
|
|
clearInterval(interval);
|
|
interval = null;
|
|
}
|
|
};
|
|
|
|
return function (gridzyInstance, item, img, onReadyCallback) {
|
|
var obj = {
|
|
img: img,
|
|
callback: onReadyCallback || function () {
|
|
},
|
|
imgB: document.createElement('img'),
|
|
ready: false,
|
|
error: false
|
|
};
|
|
var onLoad = function () {
|
|
obj.ready = true;
|
|
helper.eventListener.remove(gridzyInstance, item, obj.imgB, 'error', onError, false);
|
|
};
|
|
var onError = function () {
|
|
obj.ready = true;
|
|
obj.error = true;
|
|
helper.eventListener.remove(gridzyInstance, item, obj.imgB, 'load', onLoad, false);
|
|
};
|
|
|
|
helper.eventListener.add(gridzyInstance, item, obj.imgB, 'load', onLoad, false, true);
|
|
helper.eventListener.add(gridzyInstance, item, obj.imgB, 'error', onError, false, true);
|
|
obj.imgB.src = obj.img.src;
|
|
|
|
images.push(obj);
|
|
if (!interval) interval = setInterval(update, 0);
|
|
};
|
|
})(),
|
|
processLazyLoading: function () {
|
|
if (lazyLoadImagesQueue === null) {
|
|
return;
|
|
}
|
|
|
|
// temporarily disable observations
|
|
pauseMutationObservation();
|
|
|
|
var pos = lazyLoadImagesQueue.length,
|
|
viewportWidth = Math.max(document.documentElement.clientWidth, window.innerWidth || 0),
|
|
viewportHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0),
|
|
imagesToLoad = [],
|
|
rect,
|
|
xPos,
|
|
yPos,
|
|
edge = 1.5; // 1 is exactly the viewport size; 2 is the space of one viewport more in each direction and so on
|
|
|
|
while (pos--) {
|
|
rect = lazyLoadImagesQueue[pos].image.getBoundingClientRect();
|
|
|
|
// calculate visibility of the image - the values will be between 0 and 1 if the image is visible
|
|
xPos = rect.right / (viewportWidth + (rect.right - rect.left));
|
|
yPos = rect.bottom / (viewportHeight + (rect.bottom - rect.top));
|
|
|
|
if (xPos > 1 - edge && xPos < edge && yPos > 1 - edge && yPos < edge) {
|
|
imagesToLoad.push(lazyLoadImagesQueue.splice(pos, 1)[0]);
|
|
}
|
|
}
|
|
|
|
// start loading images
|
|
// we use "for" instead of "while", because we need the correct order. (The loading order should be from top to bottom)
|
|
for (pos = 0; pos < imagesToLoad.length; pos++) {
|
|
imagesToLoad[pos].item.contentElement.classList.add('gridzyItemLoading');
|
|
if (imagesToLoad[pos].src) {
|
|
imagesToLoad[pos].image.src = imagesToLoad[pos].src;
|
|
}
|
|
if (imagesToLoad[pos].srcset) {
|
|
imagesToLoad[pos].image.srcset = imagesToLoad[pos].srcset;
|
|
}
|
|
}
|
|
},
|
|
registerReadOnlyObjectParameters: function (obj, parameters) {
|
|
var key;
|
|
for (key in parameters) {
|
|
Object.defineProperty(obj, key, {
|
|
value: parameters[key],
|
|
enumerable: true
|
|
});
|
|
}
|
|
},
|
|
completeDefaultOptionsObject: function () {
|
|
if (defaultOptionsCompleted !== Gridzy._defaultOptions.layout && typeof Gridzy._layouts[Gridzy._defaultOptions.layout] !== 'undefined') {
|
|
Gridzy._defaultOptions = helper.optionsMerge(helper.cloneDefaultOptionsTemplate(Gridzy._defaultOptions.layout), Gridzy._defaultOptions);
|
|
defaultOptionsCompleted = Gridzy._defaultOptions.layout;
|
|
}
|
|
},
|
|
cloneDefaultOptionsTemplate: function (layout) {
|
|
if (typeof defaultOptionsLayoutTpl[layout] === 'undefined' && typeof Gridzy._layouts[layout] !== 'undefined') {
|
|
defaultOptionsLayoutTpl[layout] = helper.objectAssign({}, Gridzy._layouts[layout]._defaultOptions, defaultOptionsTpl);
|
|
}
|
|
return typeof defaultOptionsLayoutTpl[layout] !== 'undefined' ? helper.objectAssign({}, defaultOptionsLayoutTpl[layout]) : {};
|
|
}
|
|
};
|
|
|
|
var mutationObservation = true;
|
|
|
|
function pauseMutationObservation () {
|
|
if (mutationObservation) {
|
|
mutationObservation = false;
|
|
setTimeout(function() {
|
|
mutationObservation = true;
|
|
}, 0);
|
|
}
|
|
}
|
|
|
|
var initMutationObserver = new function() {
|
|
var active = false;
|
|
var mutationObserver = new MutationObserver(function () {
|
|
if (mutationObservation) {
|
|
initAll();
|
|
}
|
|
});
|
|
|
|
this.reset = function () {
|
|
var value = Gridzy._defaultOptions.autoInitOnDomMutation;
|
|
if (active !== !!value) {
|
|
if (value) {
|
|
initAll();
|
|
mutationObserver.observe(document.body, {
|
|
attributes: true,
|
|
attributeFilter: ['class'],
|
|
subtree: true,
|
|
childList: true
|
|
});
|
|
} else {
|
|
mutationObserver.disconnect();
|
|
}
|
|
active = !!value;
|
|
}
|
|
};
|
|
};
|
|
|
|
function GridzyRenderObject(gridzyInstance) {
|
|
var style = getComputedStyle(gridzyInstance.element),
|
|
dimensions = {
|
|
actualBox: null,
|
|
borderBox: {
|
|
width: gridzyInstance.element.offsetWidth,
|
|
height: gridzyInstance.element.offsetHeight
|
|
},
|
|
paddingBox: {
|
|
width: gridzyInstance.element.clientWidth,
|
|
height: gridzyInstance.element.clientHeight
|
|
},
|
|
contentBox: null,
|
|
border: {
|
|
top: parseInt(style.borderTopWidth),
|
|
right: parseInt(style.borderRightWidth),
|
|
bottom: parseInt(style.borderBottomWidth),
|
|
left: parseInt(style.borderLeftWidth)
|
|
},
|
|
padding: {
|
|
top: parseInt(style.paddingTop),
|
|
right: parseInt(style.paddingRight),
|
|
bottom: parseInt(style.paddingBottom),
|
|
left: parseInt(style.paddingLeft)
|
|
}
|
|
};
|
|
dimensions.contentBox = {
|
|
width: dimensions.paddingBox.width - dimensions.padding.left - dimensions.padding.right,
|
|
height: dimensions.paddingBox.height - dimensions.padding.top - dimensions.padding.bottom
|
|
};
|
|
dimensions.actualBox = style.boxSizing === 'border-box' ? dimensions.borderBox : dimensions.contentBox;
|
|
|
|
helper.registerReadOnlyObjectParameters(this, {
|
|
gridzyInstance: gridzyInstance,
|
|
dimensions: dimensions,
|
|
items: [],
|
|
layoutStorage: gridzyInstance._layoutStorage
|
|
});
|
|
this.styleToApply = '';
|
|
|
|
var pos = gridzyInstance._allItems.length;
|
|
while (pos--) {
|
|
this.items.unshift(new GridzyRenderObjectItem(gridzyInstance._allItems[pos]));
|
|
}
|
|
}
|
|
|
|
function GridzyRenderObjectItem(item) {
|
|
helper.registerReadOnlyObjectParameters(this, {
|
|
element: item.contentElement,
|
|
valid: item.valid,
|
|
imageError: item.failures,
|
|
matchFilter: item.matchFilter,
|
|
width: item.size[0],
|
|
height: item.size[1],
|
|
aspectRatio: item.ratio,
|
|
layoutStorage: item.layoutStorage
|
|
});
|
|
this.visible = item.visible;
|
|
this.styleToApply = '';
|
|
}
|
|
|
|
window.GridzyLayout = function (parameter) {
|
|
if (typeof parameter.name === 'string' && typeof Gridzy._layouts[parameter.name] === 'undefined') {
|
|
this.name = parameter.name;
|
|
this._defaultOptions = parameter && typeof parameter.options === 'object' ? parameter.options : {};
|
|
this._handler = parameter && typeof parameter.handler === 'function' ? parameter.handler : function() {};
|
|
Gridzy._layouts[parameter.name] = this;
|
|
}
|
|
};
|
|
|
|
GridzyLayout.prototype = {
|
|
constructor: GridzyLayout,
|
|
set handler(value) {
|
|
},
|
|
get handler() {
|
|
return this._handler;
|
|
}
|
|
};
|
|
|
|
window.Gridzy = function (element, options) {
|
|
var isNewInstance = typeof element.gridzy === 'undefined',
|
|
self = isNewInstance ? this : element.gridzy,
|
|
visibilityClassName,
|
|
prevContainerWidth = 0;
|
|
|
|
options = options || {};
|
|
|
|
// initialize default options
|
|
self._options = Gridzy.getDefaultOptions();
|
|
|
|
// merge options from element and options object
|
|
if (helper.objectAssign({}, self._options, options).useOptionAttributes) {
|
|
options = helper.objectAssign({}, self._getOptionParametersFromElement(element), options);
|
|
}
|
|
|
|
if (isNewInstance) {
|
|
|
|
// bind instance to container element
|
|
element.gridzy = self;
|
|
|
|
// register several parameters
|
|
helper.registerReadOnlyObjectParameters(self, {
|
|
element: element,
|
|
id: ++idAutoIncrement,
|
|
version: version,
|
|
_allItems: [],
|
|
_layoutStorage: {},
|
|
_filterControls: [],
|
|
_itemVisibilityClasses: {
|
|
null_true: {
|
|
add: ['gridzyItemInitialToVisible'],
|
|
remove: []
|
|
},
|
|
null_false: {
|
|
add: ['gridzyItemHidden'],
|
|
remove: []
|
|
},
|
|
false_true: {
|
|
add: ['gridzyItemHiddenToVisible'],
|
|
remove: []
|
|
},
|
|
true_false: {
|
|
add: ['gridzyItemVisibleToHidden'],
|
|
remove: []
|
|
},
|
|
true_true: {
|
|
add: ['gridzyItemVisibleToVisible'],
|
|
remove: []
|
|
},
|
|
true: {
|
|
add: ['gridzyItemVisible'],
|
|
remove: []
|
|
},
|
|
false: {
|
|
add: ['gridzyItemHidden'],
|
|
remove: []
|
|
},
|
|
null: {
|
|
add: [],
|
|
remove: []
|
|
}
|
|
},
|
|
_itemVisibilityClassesArray: [],
|
|
_insertQueue: {
|
|
maxQueueTime: 1000,
|
|
maxQuietTime: 400,
|
|
items: [],
|
|
lastProcess: null,
|
|
timer: null
|
|
},
|
|
_elementMutationObserverConfig: {
|
|
attributes: false,
|
|
childList: false
|
|
},
|
|
_elementMutationObserver: new MutationObserver(function(mutations) {
|
|
if (mutationObservation) {
|
|
var options = {},
|
|
setOptions = false,
|
|
render = false,
|
|
syncElements = false;
|
|
mutations.forEach(function(mutation) {
|
|
switch (mutation.type) {
|
|
case 'attributes':
|
|
if (mutation.attributeName === 'class' && self._options.useOptionAttributes) {
|
|
if (self._options.animate !== mutation.target.classList.contains('gridzyAnimated') && typeof options.animate === 'undefined') {
|
|
options.animate = !self._options.animate;
|
|
setOptions = true;
|
|
}
|
|
}
|
|
else if (mutation.attributeName === 'style') {
|
|
render = true;
|
|
}
|
|
else if (mutation.attributeName.indexOf('data-gridzy-') === 0 && self._options.useOptionAttributes) {
|
|
var optionData = self._getOptionParametersFromElement(null, null, mutation.attributeName);
|
|
options[optionData.name] = optionData.value;
|
|
setOptions = true;
|
|
}
|
|
break;
|
|
case 'childList':
|
|
syncElements = true;
|
|
render = true;
|
|
break;
|
|
}
|
|
});
|
|
if (syncElements) {
|
|
self._syncElements();
|
|
}
|
|
if (setOptions) {
|
|
self.setOptions(options);
|
|
} else if (render) {
|
|
self.render();
|
|
}
|
|
}
|
|
}),
|
|
_childMutationObserverConfig: {
|
|
attributes: false,
|
|
attributeFilter: ["class"]
|
|
},
|
|
_childMutationObserver: new MutationObserver(function(mutations) {
|
|
if (mutationObservation) {
|
|
var filter = self._options.filter,
|
|
render = false;
|
|
mutations.forEach(function(mutation) {
|
|
switch (mutation.type) {
|
|
case 'attributes':
|
|
if (typeof mutation.target.gridzyItem !== 'undefined') {
|
|
if (self._setMatchFilter(mutation.target.gridzyItem, filter)) {
|
|
render = true;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
if (render) {
|
|
self.render();
|
|
}
|
|
}
|
|
})
|
|
});
|
|
self._itemIdAutoIncrement = 0;
|
|
|
|
// fill _itemVisibilityClassesArray
|
|
helper.objectWalk(self._itemVisibilityClasses, function(obj) {
|
|
if (typeof obj === 'string' && self._itemVisibilityClassesArray.indexOf(obj) === -1) {
|
|
self._itemVisibilityClassesArray.push(obj);
|
|
}
|
|
});
|
|
|
|
// set remove arrays of _itemVisibilityClasses
|
|
for (visibilityClassName in self._itemVisibilityClasses) {
|
|
self._itemVisibilityClasses[visibilityClassName].remove = self._itemVisibilityClassesArray.filter(function (el) {
|
|
return self._itemVisibilityClasses[visibilityClassName].add.indexOf(el) < 0;
|
|
});
|
|
}
|
|
|
|
// initialize user specific options
|
|
self._updateOptionParameters(options);
|
|
|
|
// temporarily disable observations
|
|
pauseMutationObservation();
|
|
|
|
// set gridzy id and version as element attributes (so it's easy recognizable outside)
|
|
self.element.setAttribute('data-gridzyid', self.id);
|
|
self.element.setAttribute('data-gridzyversion', self.version);
|
|
|
|
// initialize animationend event listener
|
|
helper.eventListener.add(self, null, self.element, "animationend", function (event) {
|
|
if (event.target.gridzyItem) {
|
|
// temporarily disable observations
|
|
pauseMutationObservation();
|
|
|
|
helper.changeMultipleClassNames(event.target, true, self._itemVisibilityClasses[JSON.stringify(event.target.gridzyItem.visible)].add);
|
|
helper.changeMultipleClassNames(event.target, false, self._itemVisibilityClasses[JSON.stringify(event.target.gridzyItem.visible)].remove);
|
|
}
|
|
});
|
|
|
|
// initialize child elements
|
|
self._syncElements();
|
|
|
|
// force rendering on window resize
|
|
prevContainerWidth = self.element.clientWidth;
|
|
helper.eventListener.add(self, null, window, 'resize', function () {
|
|
if (self.element.clientWidth !== prevContainerWidth) {
|
|
prevContainerWidth = self.element.clientWidth;
|
|
self.render();
|
|
}
|
|
}, false);
|
|
|
|
// to permit css hover state on mobile devices
|
|
helper.eventListener.add(self, null, document, "touchstart", function(){}, true);
|
|
} else {
|
|
self.setOptions(options);
|
|
}
|
|
|
|
return self;
|
|
};
|
|
|
|
Gridzy._layouts = {};
|
|
Gridzy._defaultOptions = helper.objectAssign({}, defaultOptionsTpl);
|
|
|
|
Gridzy.setDefaultOptions = function (options) {
|
|
if (typeof options.layout !== 'undefined' && Gridzy._defaultOptions.layout !== options.layout && typeof Gridzy._layouts[options.layout] !== 'undefined') {
|
|
Gridzy._defaultOptions.layout = options.layout;
|
|
}
|
|
helper.completeDefaultOptionsObject();
|
|
Gridzy._defaultOptions = helper.optionsMerge(Gridzy._defaultOptions, options);
|
|
initMutationObserver.reset();
|
|
};
|
|
|
|
Gridzy.getDefaultOptions = function (layout) {
|
|
if (layout && layout !== Gridzy._defaultOptions.layout && typeof Gridzy._layouts[layout] !== 'undefiend') {
|
|
return helper.optionsMerge(helper.cloneDefaultOptionsTemplate(layout), Gridzy._defaultOptions);
|
|
} else {
|
|
helper.completeDefaultOptionsObject();
|
|
return helper.objectAssign({}, Gridzy._defaultOptions);
|
|
}
|
|
};
|
|
|
|
Gridzy.getDefaultOption = function (name) {
|
|
helper.completeDefaultOptionsObject();
|
|
return typeof Gridzy._defaultOptions[name] !== 'undefined' ? Gridzy._defaultOptions[name] : null;
|
|
};
|
|
|
|
|
|
Gridzy.prototype = {
|
|
IMG_FAILURE: 0,
|
|
IMG_LOADING: 1,
|
|
IMG_LOADING_READY: 2, // the image is loading but we have width and height attributes for calculation
|
|
IMG_READY: 3, // the image is still loading but we have naturalWidth and naturalHeight for calculation
|
|
IMG_FINISHED: 4,
|
|
|
|
ITEM_REGISTERED: 1,
|
|
ITEM_INSERT_QUEUE: 2,
|
|
ITEM_INSERTED: 3,
|
|
|
|
setOptions: function (options) {
|
|
this._updateOptionParameters(options);
|
|
this.render();
|
|
helper.processLazyLoading();
|
|
},
|
|
getOptions: function () {
|
|
return helper.objectAssign({}, this._options);
|
|
},
|
|
getOption: function (optionName) {
|
|
return typeof this._options[optionName] !== 'undefined' ? this._options[optionName] : null;
|
|
},
|
|
syncAttributes: function () {
|
|
if (this._options.useOptionAttributes) {
|
|
this.setOptions(this._getOptionParametersFromElement());
|
|
}
|
|
},
|
|
syncChild: function(childElement) {
|
|
this._syncElements(childElement);
|
|
this.render();
|
|
},
|
|
syncChildList: function () {
|
|
this._syncElements();
|
|
this.render();
|
|
},
|
|
destroy: function () {
|
|
var items;
|
|
|
|
// remove event listeners
|
|
helper.eventListener.remove(this);
|
|
|
|
// remove items from internal list
|
|
items = this._allItems.splice(0, this._allItems.length);
|
|
|
|
// remove reference from DOM element
|
|
delete this.element.gridzy;
|
|
|
|
pauseMutationObservation();
|
|
|
|
// prepare removing styles
|
|
this.element.setAttribute('style', 'transition: none; animation: none; ');
|
|
|
|
// reset item DOM elements
|
|
this._resetItemsDOM(items);
|
|
|
|
// remove attributes
|
|
this.element.removeAttribute('data-gridzyid');
|
|
this.element.removeAttribute('data-gridzyversion');
|
|
this.element.removeAttribute('style');
|
|
|
|
// trigger layout engine
|
|
document.body.offsetWidth;
|
|
},
|
|
render: function () {
|
|
var pos,
|
|
item,
|
|
itemStyle,
|
|
changeClasses,
|
|
renderObject = this._getRenderObject(),
|
|
filter = this._options.filter;
|
|
|
|
// execute user defined call back function
|
|
this._options.onBeforeRender(this);
|
|
|
|
// temporarily disable observations
|
|
pauseMutationObservation();
|
|
|
|
// preset all new values to the style attribute of hidden item elements (to start animations from there)
|
|
pos = renderObject.items.length;
|
|
while (pos--) {
|
|
item = renderObject.items[pos];
|
|
|
|
if (item.valid && !item.element.gridzyItem.visible) {
|
|
item.element.style.cssText = item.element.style.cssText + '; ' + item.styleToApply + '; transition: none; ';
|
|
}
|
|
}
|
|
|
|
// force painting
|
|
document.body.offsetWidth;
|
|
|
|
// set container element style
|
|
renderObject.gridzyInstance.element.style.cssText = renderObject.gridzyInstance.element.style.cssText + '; ' + renderObject.styleToApply;
|
|
|
|
// set all new values to the style attribute and to the class attribute of item elements
|
|
pos = renderObject.items.length;
|
|
while (pos--) {
|
|
item = renderObject.items[pos];
|
|
|
|
if (item.valid) {
|
|
if (item.element.gridzyItem.visible) {
|
|
item.element.style.cssText = item.element.style.cssText + '; ' + item.styleToApply;
|
|
} else {
|
|
item.element.style.transition = '';
|
|
}
|
|
|
|
changeClasses = this._itemVisibilityClasses[JSON.stringify(item.element.gridzyItem.visible) + '_' + JSON.stringify(item.visible)];
|
|
if (changeClasses) {
|
|
helper.changeMultipleClassNames(item.element, true, changeClasses.add || []);
|
|
helper.changeMultipleClassNames(item.element, false, changeClasses.remove || []);
|
|
}
|
|
item.element.gridzyItem.visible = item.visible;
|
|
}
|
|
}
|
|
|
|
// reset matchFilter attribute
|
|
// set class attribute of item elements which have no animation
|
|
pos = renderObject.items.length;
|
|
while (pos--) {
|
|
item = renderObject.items[pos];
|
|
|
|
if (item.valid) {
|
|
this._setMatchFilter(item.element.gridzyItem, filter);
|
|
|
|
itemStyle = getComputedStyle(item.element);
|
|
if (itemStyle.animationName === 'none') {
|
|
changeClasses = this._itemVisibilityClasses[JSON.stringify(item.visible)];
|
|
helper.changeMultipleClassNames(item.element, true, changeClasses.add || []);
|
|
helper.changeMultipleClassNames(item.element, false, changeClasses.remove || []);
|
|
}
|
|
}
|
|
}
|
|
|
|
// execute user defined call back function
|
|
this._options.onRender(this);
|
|
},
|
|
_resetElementMutationObserver: function () {
|
|
var config = this._elementMutationObserverConfig,
|
|
changed = false,
|
|
observed = config.attributes || config.childList,
|
|
observe;
|
|
|
|
if (config.attributes !== this._options.autoSyncAttributesMutation) {
|
|
config.attributes = !config.attributes;
|
|
changed = true;
|
|
}
|
|
if (config.childList !== this._options.autoSyncChildListMutation) {
|
|
config.childList = !config.childList;
|
|
changed = true;
|
|
}
|
|
observe = config.attributes || config.childList;
|
|
if (changed) {
|
|
if (observed) {
|
|
this._elementMutationObserver.disconnect();
|
|
}
|
|
if (observe) {
|
|
this._elementMutationObserver.observe(this.element, helper.objectAssign({}, config));
|
|
}
|
|
}
|
|
},
|
|
_resetChildMutationObserver: function (force) {
|
|
var config = this._childMutationObserverConfig,
|
|
configCopy,
|
|
changed = false,
|
|
observed = config.attributes,
|
|
observe,
|
|
item,
|
|
filter,
|
|
render = false,
|
|
pos;
|
|
|
|
if (config.attributes !== this._options.autoSyncChildClassMutation) {
|
|
config.attributes = !config.attributes;
|
|
changed = true;
|
|
}
|
|
observe = config.attributes;
|
|
if (changed || force) {
|
|
if (observed) {
|
|
this._childMutationObserver.disconnect();
|
|
}
|
|
if (observe) {
|
|
configCopy = helper.objectAssign({}, config);
|
|
filter = this._options.filter;
|
|
pos = this._allItems.length;
|
|
while (pos--) {
|
|
item = this._allItems[pos];
|
|
this._childMutationObserver.observe(item.contentElement, configCopy);
|
|
if (this._setMatchFilter(item, filter)) {
|
|
render = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (render) {
|
|
this.render();
|
|
}
|
|
},
|
|
_setMatchFilter: function (item, filter) {
|
|
if (item.status === this.ITEM_INSERTED) {
|
|
var matchFilter = helper.matches(item.contentElement, filter);
|
|
if (matchFilter !== item.matchFilter) {
|
|
item.matchFilter = matchFilter;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
_filterControlEventHandler: function (event) {
|
|
var pos = this._filterControls.length,
|
|
pos2,
|
|
filter = [],
|
|
controlElement,
|
|
targetElement = event.target;
|
|
|
|
// get the select element if an option element is clicked
|
|
if (targetElement.nodeName === 'OPTION') {
|
|
targetElement = targetElement.parentNode;
|
|
}
|
|
|
|
// collect filter selectors of all control elements
|
|
while (pos--) {
|
|
controlElement = this._filterControls[pos];
|
|
|
|
if (targetElement === controlElement) {
|
|
if (controlElement.nodeName === 'SELECT') {
|
|
// get filters from selected options of select field
|
|
pos2 = controlElement.options.length;
|
|
while (pos2--) {
|
|
if (controlElement.options[pos2].selected) {
|
|
filter = filter.concat(controlElement.options[pos2].value);
|
|
}
|
|
}
|
|
}
|
|
else if (controlElement.checked || controlElement.type !== 'checkbox') {
|
|
// get filters of focused elements which are not unchecked checkboxes
|
|
filter = filter.concat(controlElement.value);
|
|
}
|
|
} else if (controlElement.checked) {
|
|
if (targetElement.type === 'checkbox' && controlElement.type === 'checkbox') {
|
|
// get filters from active checkboxes which are not clicked
|
|
filter = filter.concat(controlElement.value);
|
|
} else {
|
|
// uncheck active radio buttons which are not clicked
|
|
controlElement.checked = false;
|
|
}
|
|
} else if (controlElement.nodeName === 'SELECT') {
|
|
// deselect options of select fields which are not clicked
|
|
pos2 = controlElement.options.length;
|
|
while (pos2--) {
|
|
controlElement.options[pos2].selected = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
this.setOptions({filter:(filter.join(', ') || ':not(*)')});
|
|
},
|
|
_getRenderObject: function () {
|
|
var autoConsiderScrollBars = this._options.autoConsiderScrollBars,
|
|
renderObject = new GridzyRenderObject(this),
|
|
renderObjectChanged = false,
|
|
ancestor = renderObject.gridzyInstance.element,
|
|
ancestorOverflowY = null,
|
|
ancestorHeight = null,
|
|
width = renderObject.gridzyInstance.element.offsetWidth,
|
|
position = renderObject.gridzyInstance.element.style.position,
|
|
style = renderObject.gridzyInstance.element.style.cssText,
|
|
newHeight,
|
|
testRequired = false;
|
|
|
|
this._prepareRenderObject(renderObject);
|
|
|
|
// test whether we need a recalculation because of appearing or disappearing scrollbars
|
|
if (autoConsiderScrollBars) {
|
|
|
|
// test whether we really need an expensive test
|
|
while (ancestor.parentNode && ancestor.parentNode.nodeName !== '#document') {
|
|
ancestor = ancestor.parentNode;
|
|
ancestorOverflowY = getComputedStyle(ancestor).overflowY;
|
|
if (ancestorOverflowY === 'auto' || ancestor.nodeName === 'BODY' && ancestorOverflowY === 'visible') {
|
|
testRequired = true;
|
|
|
|
ancestorHeight = ancestor.nodeName === 'BODY'
|
|
? Math.max(document.documentElement.clientHeight, window.innerHeight || 0)
|
|
: ancestor.offsetHeight;
|
|
if (ancestorHeight < renderObject.gridzyInstance.element.offsetHeight) {
|
|
newHeight = renderObject.styleToApply.match(/height:\s*([0-9]+)px;/);
|
|
newHeight = newHeight ? parseInt(newHeight[1]) : null;
|
|
if (ancestorHeight < newHeight) {
|
|
testRequired = false;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if we really need the expensive test let's do it
|
|
if (testRequired) {
|
|
renderObject.gridzyInstance.element.style.cssText =
|
|
style + '; ' +
|
|
renderObject.styleToApply + '; ' +
|
|
'transition: none; ' +
|
|
'animation: none; ' +
|
|
'overflow: hidden; ';
|
|
if (width !== renderObject.gridzyInstance.element.offsetWidth) {
|
|
renderObject.gridzyInstance.element.style.position = position;
|
|
renderObject = new GridzyRenderObject(this);
|
|
renderObjectChanged = true;
|
|
}
|
|
renderObject.gridzyInstance.element.style.cssText = style + '; transition: none; animation: none; ';
|
|
document.body.offsetWidth;
|
|
renderObject.gridzyInstance.element.style.cssText = style;
|
|
document.body.offsetWidth;
|
|
|
|
// if there is a new renderObject let's prepare it
|
|
if (renderObjectChanged) {
|
|
this._prepareRenderObject(renderObject);
|
|
}
|
|
}
|
|
}
|
|
|
|
return renderObject;
|
|
},
|
|
_prepareRenderObject: function (renderObject) {
|
|
|
|
// insert the new calculated values
|
|
Gridzy._layouts[this._options.layout].handler(renderObject);
|
|
|
|
// complete container element style
|
|
if (this.element.children.length && this.element.children[0].offsetParent !== this.element) {
|
|
renderObject.styleToApply += '; position: relative; ';
|
|
}
|
|
if (this.element.style.display !== 'block') {
|
|
renderObject.styleToApply += '; display: block; ';
|
|
}
|
|
},
|
|
_syncElements: function (childElement) {
|
|
var children = this.element.children,
|
|
pos,
|
|
item,
|
|
filter = this.getOption('filter');
|
|
|
|
// clear items array
|
|
this._allItems.length = 0;
|
|
|
|
// rebuild items array
|
|
for (pos = 0; pos < children.length; pos++) {
|
|
item = false;
|
|
|
|
if (children[pos].gridzyItem !== undefined) {
|
|
item = children[pos].gridzyItem;
|
|
}
|
|
else if (childElement === undefined || childElement === children[pos]) {
|
|
item = this._createItem(children[pos]);
|
|
}
|
|
|
|
if (item) {
|
|
this._setMatchFilter(item, filter);
|
|
this._allItems.push(item);
|
|
}
|
|
}
|
|
|
|
this._resetChildMutationObserver(true);
|
|
},
|
|
_resetItemsDOM: function (items) {
|
|
var pos;
|
|
var item;
|
|
|
|
pauseMutationObservation();
|
|
|
|
pos = items.length;
|
|
while (pos--) {
|
|
item = items[pos];
|
|
|
|
// prepare removing styles
|
|
item.contentElement.setAttribute('style', 'transition: none; animation: none; ');
|
|
}
|
|
|
|
// trigger layout engine
|
|
document.body.offsetWidth;
|
|
|
|
pos = items.length;
|
|
while (pos--) {
|
|
item = items[pos];
|
|
|
|
// remove item reference from item DOM element
|
|
delete item.contentElement.gridzyItem;
|
|
|
|
// remove existing gridzy classes from element
|
|
helper.changeMultipleClassNames(item.contentElement, false, this._itemVisibilityClassesArray.concat(['gridzyItem', 'gridzyItemLoading', 'gridzyItemInitializing', 'gridzyItemReady', 'gridzyItemComplete']));
|
|
|
|
// remove attributes
|
|
if (item.contentElement.className === '') {
|
|
item.contentElement.removeAttribute('class');
|
|
}
|
|
item.contentElement.removeAttribute('style');
|
|
item.contentElement.removeAttribute('data-gridzyitemid');
|
|
}
|
|
},
|
|
_createItem: function (contentElement) {
|
|
|
|
if (this._insertQueue.items.length === 0) {
|
|
this._insertQueue.lastProcess = (new Date()).getTime();
|
|
}
|
|
|
|
// temporarily disable observations
|
|
pauseMutationObservation();
|
|
|
|
// remove existing gridzy classes from element
|
|
helper.changeMultipleClassNames(contentElement, false, this._itemVisibilityClassesArray.concat(['gridzyItem', 'gridzyItemLoading', 'gridzyItemInitializing', 'gridzyItemReady', 'gridzyItemComplete']));
|
|
|
|
// generate and insert the item object
|
|
var item = {
|
|
id: ++this._itemIdAutoIncrement,
|
|
contentElement: contentElement,
|
|
size: [0, 0],
|
|
ratio: 0,
|
|
failures: false,
|
|
status: this.ITEM_REGISTERED,
|
|
images: [],
|
|
imagesStatus: [],
|
|
imagesInfo: [],
|
|
matchFilter: null,
|
|
layoutStorage: {},
|
|
visible: null,
|
|
destroy: function() {
|
|
|
|
// remove event listeners
|
|
helper.eventListener.remove(this, item);
|
|
|
|
// remove item from internal list
|
|
this._allItems.splice(this._allItems.indexOf(item), 1);
|
|
|
|
// reset item DOM element
|
|
this._resetItemsDOM([item]);
|
|
|
|
// trigger layout engine
|
|
document.body.offsetWidth;
|
|
}.bind(this)
|
|
};
|
|
contentElement.gridzyItem = item;
|
|
|
|
// set attributes, classes and styles
|
|
item.contentElement.setAttribute('data-gridzyitemid', item.id);
|
|
item.contentElement.style.visibility = 'hidden';
|
|
item.contentElement.style.position = 'absolute';
|
|
helper.changeMultipleClassNames(item.contentElement, true, ['gridzyItem', 'gridzyItemLoading', 'gridzyItemInitializing']);
|
|
|
|
// search for images to get natural image sizes (they are needed to get the element size)
|
|
var images = contentElement.nodeName === 'IMG' ? [contentElement] : contentElement.querySelectorAll('img'),
|
|
pos = images.length;
|
|
while (pos--) {
|
|
(function (e) {
|
|
item.images.push(e);
|
|
|
|
var attributeWidth = e.getAttribute('width');
|
|
var attributeHeight = e.getAttribute('height');
|
|
var lazySrc = e.getAttribute('data-gridzylazysrc');
|
|
var lazySrcset = e.getAttribute('data-gridzylazysrcset');
|
|
var src = e.getAttribute('src');
|
|
|
|
if (!src && (lazySrc || lazySrcset)) {
|
|
// if there is a data-gridzylazysrc or a data-gridzylazysrcset attribute but there is no width or height attribute, we deactivate lazy loading for this image
|
|
if (!attributeWidth || !attributeHeight) {
|
|
e.src = lazySrc;
|
|
e.srcset = lazySrcset;
|
|
lazySrc = null;
|
|
lazySrcset = null;
|
|
} else {
|
|
// set a transparent pixel as an image if there is no src
|
|
e.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEHAAEALAAAAAABAAEAAAICTAEAOw==';
|
|
}
|
|
}
|
|
|
|
item.imagesInfo.push({
|
|
styleWidth: e.style.width,
|
|
styleHeight: e.style.height,
|
|
attributeWidth: attributeWidth,
|
|
attributeHeight: attributeHeight,
|
|
lazySrc: lazySrc,
|
|
lazySrcset: lazySrcset
|
|
});
|
|
|
|
if (attributeWidth !== null && attributeHeight !== null && parseInt(attributeWidth) > 0 && parseInt(attributeHeight) > 0) {
|
|
item.imagesStatus.push(this.IMG_LOADING_READY);
|
|
} else {
|
|
item.imagesStatus.push(this.IMG_LOADING);
|
|
}
|
|
}).bind(this)(images[pos]);
|
|
}
|
|
|
|
// force painting
|
|
document.body.offsetWidth;
|
|
|
|
item.images.forEach(function (image, index) {
|
|
var onLoad = function () {
|
|
item.imagesStatus[index] = image.naturalWidth ? this.IMG_FINISHED : this.IMG_FAILURE;
|
|
this._onImageFinished(item);
|
|
helper.eventListener.remove(this, item, image, 'error', onError, false);
|
|
}.bind(this);
|
|
|
|
var onError = function () {
|
|
item.imagesStatus[index] = this.IMG_FAILURE;
|
|
this._onImageFinished(item);
|
|
helper.eventListener.remove(this, item, image, 'load', onLoad, false);
|
|
}.bind(this);
|
|
|
|
helper.eventListener.add(this, item, image, 'load', onLoad, false, true);
|
|
helper.eventListener.add(this, item, image, 'error', onError, false, true);
|
|
|
|
if (image.complete) {
|
|
onLoad();
|
|
}
|
|
|
|
if (!item.imagesInfo[index].lazySrc && !item.imagesInfo[index].lazySrcset) {
|
|
helper.expectNaturalImageSize(this, item, image, function (img, success) {
|
|
item.imagesStatus[index] = success && image.naturalWidth ? (image.complete ? this.IMG_FINISHED : this.IMG_READY) : this.IMG_FAILURE;
|
|
this._onImageReady(item);
|
|
}.bind(this));
|
|
}
|
|
}.bind(this));
|
|
|
|
this._onImageReady(item);
|
|
this._onImageFinished(item);
|
|
|
|
return item;
|
|
},
|
|
_processInsertQueue: function (maxQuietTimeReached) {
|
|
var queueItem;
|
|
var maxQueueTimeReached = this._insertQueue.lastProcess < (new Date()).getTime() - this._insertQueue.maxQueueTime;
|
|
var imagesStylesToReset = [];
|
|
var imagesStylesToResetPos;
|
|
var imagesInfo;
|
|
var imagePos;
|
|
var imageStyle;
|
|
var queueItems = [];
|
|
var queueItemsPos;
|
|
var filter = this._options.filter;
|
|
var lazyLoadImages = [];
|
|
|
|
if (this._insertQueue.timer !== null) {
|
|
clearTimeout(this._insertQueue.timer);
|
|
this._insertQueue.timer = null;
|
|
}
|
|
|
|
if (maxQuietTimeReached || maxQueueTimeReached) {
|
|
|
|
// temporarily disable observations
|
|
pauseMutationObservation();
|
|
|
|
// insert item elements
|
|
queueItem = this._insertQueue.items.shift();
|
|
while (queueItem) {
|
|
queueItems.push(queueItem);
|
|
queueItem = this._insertQueue.items.shift();
|
|
}
|
|
|
|
// get images where to set width and height style to force image sizes from attributes
|
|
queueItemsPos = queueItems.length;
|
|
while (queueItemsPos--) {
|
|
queueItem = queueItems[queueItemsPos];
|
|
|
|
imagePos = queueItem.item.images.length;
|
|
while (imagePos--) {
|
|
imagesInfo = queueItem.item.imagesInfo[imagePos];
|
|
|
|
// in some cases google chrome and firefox don't have the correct offset size even though the natural size is known
|
|
if (!imagesInfo.attributeWidth && queueItem.item.images[imagePos].offsetWidth < 40 && queueItem.item.images[imagePos].naturalWidth) {
|
|
imagesInfo.attributeWidth = queueItem.item.images[imagePos].naturalWidth;
|
|
}
|
|
if (!imagesInfo.attributeHeight && queueItem.item.images[imagePos].offsetHeight < 40 && queueItem.item.images[imagePos].naturalHeight) {
|
|
imagesInfo.attributeHeight = queueItem.item.images[imagePos].naturalHeight;
|
|
}
|
|
|
|
if (imagesInfo.attributeWidth || imagesInfo.attributeHeight) {
|
|
imageStyle = getComputedStyle(queueItem.item.images[imagePos]);
|
|
|
|
if ((parseInt(imageStyle.width) < 40 || imageStyle.width === 'auto' || imagesInfo.lazySrc || imagesInfo.lazySrcset) && imagesInfo.attributeWidth && !imagesInfo.styleWidth) {
|
|
imagesStylesToReset.push({
|
|
image: queueItem.item.images[imagePos],
|
|
definition: 'width',
|
|
value: imagesInfo.attributeWidth + 'px'
|
|
});
|
|
}
|
|
if ((parseInt(imageStyle.height) < 40 || imageStyle.height === 'auto' || imagesInfo.lazySrc || imagesInfo.lazySrcset) && imagesInfo.attributeHeight && !imagesInfo.styleHeight) {
|
|
imagesStylesToReset.push({
|
|
image: queueItem.item.images[imagePos],
|
|
definition: 'height',
|
|
value: imagesInfo.attributeHeight + 'px'
|
|
});
|
|
}
|
|
}
|
|
|
|
// get images to lazy load
|
|
if (imagesInfo.lazySrc || imagesInfo.lazySrcset) {
|
|
lazyLoadImages.push({
|
|
item: queueItem.item,
|
|
image: queueItem.item.images[imagePos],
|
|
src: imagesInfo.lazySrc,
|
|
srcset: imagesInfo.lazySrcset
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// set width and height style to force image sizes from attributes
|
|
imagesStylesToResetPos = imagesStylesToReset.length;
|
|
while (imagesStylesToResetPos--) {
|
|
imagesStylesToReset[imagesStylesToResetPos].image.style[imagesStylesToReset[imagesStylesToResetPos].definition] = imagesStylesToReset[imagesStylesToResetPos].value;
|
|
|
|
// set display to "none" because FF need more than only a browser rerendering to apply the sizes to elements
|
|
imagesStylesToReset[imagesStylesToResetPos].image.style.display = 'none';
|
|
}
|
|
|
|
// force browser rendering and display images again after hiding it because of FF
|
|
document.body.offsetWidth;
|
|
imagesStylesToResetPos = imagesStylesToReset.length;
|
|
while (imagesStylesToResetPos--) {
|
|
imagesStylesToReset[imagesStylesToResetPos].image.style.display = '';
|
|
}
|
|
|
|
// get size of item element
|
|
queueItemsPos = queueItems.length;
|
|
while (queueItemsPos--) {
|
|
queueItem = queueItems[queueItemsPos];
|
|
queueItem.item.size = [queueItem.contentElement.offsetWidth, queueItem.contentElement.offsetHeight];
|
|
queueItem.item.ratio = queueItem.item.size[0] / queueItem.item.size[1];
|
|
}
|
|
|
|
// remove width and height style where it was set before getting size of item element
|
|
imagesStylesToResetPos = imagesStylesToReset.length;
|
|
while (imagesStylesToResetPos--) {
|
|
imagesStylesToReset[imagesStylesToResetPos].image.style[imagesStylesToReset[imagesStylesToResetPos].definition] = '';
|
|
}
|
|
|
|
// set styles for item element
|
|
queueItemsPos = queueItems.length;
|
|
while (queueItemsPos--) {
|
|
queueItem = queueItems[queueItemsPos];
|
|
queueItem.item.status = this.ITEM_INSERTED;
|
|
queueItem.item.valid = !!(queueItem.item.size[0] && queueItem.item.size[1]);
|
|
this._setMatchFilter(queueItem.item, filter);
|
|
queueItem.item.contentElement.classList.remove('gridzyItemInitializing');
|
|
queueItem.item.contentElement.classList.add('gridzyItemReady');
|
|
}
|
|
|
|
// calculate and render layout
|
|
this.render();
|
|
|
|
// make item elements visible
|
|
queueItemsPos = queueItems.length;
|
|
while (queueItemsPos--) {
|
|
queueItem = queueItems[queueItemsPos];
|
|
queueItem.item.contentElement.style.visibility = '';
|
|
}
|
|
|
|
// register lazy loading images
|
|
if (lazyLoadImages.length) {
|
|
this._addImagesToLazyLoadQueue(lazyLoadImages);
|
|
}
|
|
|
|
this._insertQueue.lastProcess = (new Date()).getTime();
|
|
} else {
|
|
this._insertQueue.timer = setTimeout(function () {
|
|
this._processInsertQueue(true);
|
|
}.bind(this), this._insertQueue.maxQuietTime);
|
|
}
|
|
},
|
|
_addImagesToLazyLoadQueue: function (images) {
|
|
// activate lazy loading if is not already activated
|
|
if (lazyLoadImagesQueue === null && images.length) {
|
|
lazyLoadImagesQueue = [];
|
|
helper.eventListener.add(this, null, window, 'resize', helper.processLazyLoading, false);
|
|
helper.eventListener.add(this, null, window, 'scroll', helper.processLazyLoading, true);
|
|
}
|
|
|
|
// add images to lazy loading queue
|
|
lazyLoadImagesQueue = lazyLoadImagesQueue.concat(images);
|
|
|
|
helper.processLazyLoading();
|
|
},
|
|
_addToInsertQueue: function (contentElement, item) {
|
|
this._insertQueue.items.push({contentElement: contentElement, item: item});
|
|
this._processInsertQueue(false);
|
|
},
|
|
_onImageFinished: function (item) {
|
|
var failures = item.failures;
|
|
|
|
if (!helper.inArray(item.imagesStatus, this.IMG_LOADING) && !helper.inArray(item.imagesStatus, this.IMG_LOADING_READY) && !helper.inArray(item.imagesStatus, this.IMG_READY)) {
|
|
|
|
// temporarily disable observations
|
|
pauseMutationObservation();
|
|
|
|
if (helper.inArray(item.imagesStatus, this.IMG_FAILURE)) {
|
|
item.failures = true;
|
|
}
|
|
if (item.status === this.ITEM_INSERTED && failures !== item.failures) {
|
|
this.render();
|
|
}
|
|
|
|
item.contentElement.classList.remove('gridzyItemLoading');
|
|
item.contentElement.classList.add('gridzyItemComplete');
|
|
}
|
|
},
|
|
_onImageReady: function (item) {
|
|
var failures = item.failures;
|
|
|
|
// register image loading failures
|
|
item.failures = helper.inArray(item.imagesStatus, this.IMG_FAILURE);
|
|
|
|
if (item.status === this.ITEM_INSERTED && failures !== item.failures) {
|
|
this.render();
|
|
}
|
|
|
|
// insert the element if the sizes of all contained images are known (because the size is important to calculate the layout)
|
|
if (item.status === this.ITEM_REGISTERED && !helper.inArray(item.imagesStatus, this.IMG_LOADING)) {
|
|
item.status = this.ITEM_INSERT_QUEUE;
|
|
this._addToInsertQueue(item.contentElement, item);
|
|
}
|
|
},
|
|
_getOptionParametersFromElement: function (element, layout, attributeName) {
|
|
var defaultOptions,
|
|
defaultOptionsNormalized = {},
|
|
options = {},
|
|
optionName,
|
|
attributes,
|
|
pos;
|
|
|
|
element = element || this.element;
|
|
layout = layout || element.getAttribute('data-gridzy-layout') || this._options.layout;
|
|
attributes = element.attributes;
|
|
|
|
defaultOptions = Gridzy.getDefaultOptions(layout);
|
|
|
|
for (optionName in defaultOptions) {
|
|
defaultOptionsNormalized[optionName.toLowerCase()] = {name: optionName, value: defaultOptions[optionName]};
|
|
}
|
|
|
|
if (attributeName) {
|
|
optionName = attributeName.substr(12).toLowerCase();
|
|
if (typeof defaultOptionsNormalized[optionName] !== 'undefined') {
|
|
options.name = defaultOptionsNormalized[optionName].name;
|
|
options.value = typeof attributes[attributeName] !== 'undefined' ? attributes[attributeName].value : null;
|
|
}
|
|
} else {
|
|
if (element.classList.contains('gridzyAnimated')) {
|
|
options.animate = true;
|
|
}
|
|
|
|
pos = attributes.length;
|
|
while (pos--) {
|
|
if (attributes[pos].name.substr(0, 12) === 'data-gridzy-') {
|
|
optionName = attributes[pos].name.substr(12).toLowerCase();
|
|
if (typeof defaultOptionsNormalized[optionName] !== 'undefined') {
|
|
options[defaultOptionsNormalized[optionName].name] = attributes[pos].value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return options;
|
|
},
|
|
_updateOptionParameters: function (options) {
|
|
var name,
|
|
layoutChanged = false,
|
|
oldOptions,
|
|
defaultOptions,
|
|
optionParametersFromElement,
|
|
pos,
|
|
value,
|
|
filterControls,
|
|
attributeName;
|
|
|
|
options = options || {};
|
|
|
|
this._options.onBeforeOptionsChanged(this);
|
|
|
|
oldOptions = helper.objectAssign({}, this._options);
|
|
|
|
// set layout
|
|
if (typeof options.layout === 'object' && options.layout === null && this._options.layout !== Gridzy._defaultOptions.layout) {
|
|
this._options.layout = Gridzy._defaultOptions.layout;
|
|
layoutChanged = true;
|
|
}
|
|
if (typeof options.layout === 'string' && typeof Gridzy._layouts[options.layout] === 'object' && Gridzy._layouts[options.layout].constructor === GridzyLayout && this._options.layout !== options.layout) {
|
|
this._options.layout = options.layout;
|
|
layoutChanged = true;
|
|
}
|
|
|
|
defaultOptions = Gridzy.getDefaultOptions(this._options.layout);
|
|
|
|
// sync layout options if layout has changed
|
|
if (layoutChanged) {
|
|
// remove unused layout options
|
|
for (name in this._options) {
|
|
if (typeof defaultOptions[name] === 'undefined') {
|
|
delete this._options[name];
|
|
}
|
|
}
|
|
// set default layout options
|
|
for (name in defaultOptions) {
|
|
if (typeof this._options[name] === 'undefined') {
|
|
this._options[name] = defaultOptions[name];
|
|
}
|
|
}
|
|
// restore layout options from element attributes
|
|
if (this._options.useOptionAttributes) {
|
|
optionParametersFromElement = this._getOptionParametersFromElement(null, this._options.layout);
|
|
for (name in optionParametersFromElement) {
|
|
if (typeof defaultOptions[name] !== 'undefined' && typeof options[name] === 'undefined' && this._options[name] !== optionParametersFromElement[name]) {
|
|
options[name] = optionParametersFromElement[name];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// set layout options
|
|
for (name in defaultOptions) {
|
|
if (typeof options[name] !== 'undefined' && options[name] !== null) {
|
|
switch (typeof defaultOptions[name]) {
|
|
case 'boolean':
|
|
this._options[name] = helper.isTruly(options[name]);
|
|
break;
|
|
case 'number':
|
|
this._options[name] = +options[name];
|
|
break;
|
|
default:
|
|
this._options[name] = options[name];
|
|
}
|
|
}
|
|
}
|
|
|
|
// set filter
|
|
if (typeof options.filter === 'string' && this._options.filter !== options.filter && helper.isValidCssSelector(options.filter)) this._options.filter = options.filter;
|
|
|
|
// set filterControls
|
|
if (typeof options.filterControls === 'string' && (options.filterControls === '' || helper.isValidCssSelector(options.filterControls))) this._options.filterControls = options.filterControls;
|
|
|
|
// set animate
|
|
if (typeof options.animate !== 'undefined' && options.animate !== null) this._options.animate = helper.isTruly(options.animate);
|
|
|
|
// set autoConsiderScrollBars
|
|
if (typeof options.autoConsiderScrollBars !== 'undefined' && options.autoConsiderScrollBars !== null) this._options.autoConsiderScrollBars = helper.isTruly(options.autoConsiderScrollBars);
|
|
|
|
// set event handlers
|
|
if (typeof options.onBeforeOptionsChanged === 'function') this._options.onBeforeOptionsChanged = options.onBeforeOptionsChanged;
|
|
if (typeof options.onOptionsChanged === 'function') this._options.onOptionsChanged = options.onOptionsChanged;
|
|
if (typeof options.onBeforeRender === 'function') this._options.onBeforeRender = options.onBeforeRender;
|
|
if (typeof options.onRender === 'function') this._options.onRender = options.onRender;
|
|
|
|
// set autoSyncChildListMutation
|
|
if (typeof options.autoSyncChildListMutation !== 'undefined' && options.autoSyncChildListMutation !== null) this._options.autoSyncChildListMutation = helper.isTruly(options.autoSyncChildListMutation);
|
|
|
|
// set autoSyncAttributesMutation
|
|
if (typeof options.autoSyncAttributesMutation !== 'undefined' && options.autoSyncAttributesMutation !== null) this._options.autoSyncAttributesMutation = helper.isTruly(options.autoSyncAttributesMutation);
|
|
|
|
// set autoSyncChildClassMutation
|
|
if (typeof options.autoSyncChildClassMutation !== 'undefined' && options.autoSyncChildClassMutation !== null) this._options.autoSyncChildClassMutation = helper.isTruly(options.autoSyncChildClassMutation);
|
|
|
|
|
|
// reset options with value null
|
|
for (name in options) {
|
|
if (options[name] === null && typeof defaultOptions[name] !== 'undefined') {
|
|
this._options[name] = defaultOptions[name];
|
|
}
|
|
}
|
|
|
|
// reset matchFilter parameter of items
|
|
if (oldOptions.filter !== this._options.filter) {
|
|
pos = this._allItems.length;
|
|
while (pos--) {
|
|
this._setMatchFilter(this._allItems[pos], this._options.filter);
|
|
}
|
|
}
|
|
|
|
// set event listeners for filterControls
|
|
if (oldOptions.filterControls !== this._options.filterControls || this._options.filterControls && this._filterControls.length === 0) {
|
|
filterControls = [].slice.call(this._options.filterControls ? document.querySelectorAll(this._options.filterControls) : []);
|
|
|
|
pos = this._filterControls.length;
|
|
while (pos--) {
|
|
if (!helper.inArray(filterControls, this._filterControls[pos])) {
|
|
switch (this._filterControls[pos].nodeName + this._filterControls[pos].type) {
|
|
case 'SELECT':
|
|
case 'INPUTcheckbox':
|
|
case 'INPUTradio':
|
|
helper.eventListener.remove(this, null, this._filterControls[pos], 'change', this._filterControls[pos].gridzyFilterControlHandler);
|
|
break;
|
|
case 'INPUTbutton':
|
|
case 'INPUTsubmit':
|
|
case 'BUTTONbutton':
|
|
case 'BUTTONsubmit':
|
|
case 'BUTTONreset':
|
|
helper.eventListener.remove(this, null, this._filterControls[pos], 'click', this._filterControls[pos].gridzyFilterControlHandler);
|
|
break;
|
|
default:
|
|
helper.eventListener.remove(this, null, this._filterControls[pos], 'input', this._filterControls[pos].gridzyFilterControlHandler);
|
|
}
|
|
this._filterControls.splice(pos, 1);
|
|
}
|
|
}
|
|
pos = filterControls.length;
|
|
while (pos--) {
|
|
if (!helper.inArray(this._filterControls, filterControls[pos])) {
|
|
filterControls[pos].gridzyFilterControlHandler = this._filterControlEventHandler.bind(this);
|
|
switch (filterControls[pos].nodeName + filterControls[pos].type) {
|
|
case 'SELECT':
|
|
case 'INPUTcheckbox':
|
|
case 'INPUTradio':
|
|
helper.eventListener.add(this, null, filterControls[pos], 'change', filterControls[pos].gridzyFilterControlHandler);
|
|
break;
|
|
case 'INPUTbutton':
|
|
case 'INPUTsubmit':
|
|
case 'BUTTONbutton':
|
|
case 'BUTTONsubmit':
|
|
case 'BUTTONreset':
|
|
helper.eventListener.add(this, null, filterControls[pos], 'click', filterControls[pos].gridzyFilterControlHandler);
|
|
break;
|
|
default:
|
|
helper.eventListener.add(this, null, filterControls[pos], 'input', filterControls[pos].gridzyFilterControlHandler);
|
|
}
|
|
this._filterControls.push(filterControls[pos]);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// temporarily disable observations
|
|
pauseMutationObservation();
|
|
|
|
// reset element mutation observer to new options
|
|
this._resetElementMutationObserver();
|
|
|
|
// update "class" element attribute
|
|
this.element.classList[this._options.animate ? 'add' : 'remove']('gridzyAnimated');
|
|
|
|
// update "data-gridzy-" element attributes
|
|
if (this._options.useOptionAttributes) {
|
|
for (name in this._options) {
|
|
attributeName = 'data-gridzy-' + name.toLowerCase();
|
|
value = this._options[name];
|
|
if (typeof options[name] !== 'undefined' && options[name] === null && this.element.hasAttribute(attributeName)) {
|
|
this.element.removeAttribute(attributeName);
|
|
}
|
|
else if (defaultOptions[name] !== this._options[name] || this.element.hasAttribute(attributeName)) {
|
|
switch (typeof value) {
|
|
case 'boolean':
|
|
value = value ? 'true' : 'false';
|
|
break;
|
|
case 'number':
|
|
value = value + '';
|
|
}
|
|
if (typeof value === 'string') {
|
|
this.element.setAttribute(attributeName, value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// reset child mutation observer to new options
|
|
this._resetChildMutationObserver();
|
|
|
|
this._options.onOptionsChanged(this);
|
|
}
|
|
};
|
|
|
|
function initAll() {
|
|
// find all elements with class 'gridzy'
|
|
var elements = document.getElementsByClassName('gridzy'),
|
|
pos = elements.length;
|
|
|
|
// initialize them automatically if they aren't yet
|
|
while (pos--) {
|
|
if (typeof elements[pos].gridzy === 'undefined') {
|
|
new Gridzy(elements[pos]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// as soon as content is loaded
|
|
helper.eventListener.add(null, null, document, 'DOMContentLoaded', function() {
|
|
if (Gridzy._defaultOptions.autoInitOnDomReady) {
|
|
initAll();
|
|
}
|
|
initMutationObserver.reset();
|
|
}, false, true);
|
|
|
|
// if jQuery is available register Gridzy as a plugin
|
|
if (typeof jQuery !== 'undefined' && typeof jQuery.fn !== 'undefined') {
|
|
jQuery.fn.gridzy = function (options) {
|
|
return this.each(function () {
|
|
jQuery(this).data('gridzy', new Gridzy(this, options));
|
|
});
|
|
};
|
|
}
|
|
|
|
})();
|
|
|
|
new GridzyLayout({
|
|
name: 'justified',
|
|
options: {
|
|
autoFontSize: false,
|
|
desiredHeight: 190,
|
|
hideOnMissingImage: true,
|
|
spaceBetween: 4,
|
|
fillLastRow: true, // deprecated since v2.3.0
|
|
fillSingleRow: true, // deprecated since v2.3.0
|
|
lastRowAlign: 'justified',
|
|
singleRowAlign: 'justified'
|
|
},
|
|
handler: function(renderObject) {
|
|
|
|
var // get layout options
|
|
autoFontSize = !!renderObject.gridzyInstance.getOption('autoFontSize'),
|
|
desiredHeight = parseInt(renderObject.gridzyInstance.getOption('desiredHeight')),
|
|
hideOnMissingImage = !!renderObject.gridzyInstance.getOption('hideOnMissingImage'),
|
|
spaceBetween = parseInt(renderObject.gridzyInstance.getOption('spaceBetween')),
|
|
fillLastRow = !!renderObject.gridzyInstance.getOption('fillLastRow'),
|
|
fillSingleRow = !!renderObject.gridzyInstance.getOption('fillSingleRow'),
|
|
lastRowAlign = renderObject.gridzyInstance.getOption('lastRowAlign'),
|
|
singleRowAlign = renderObject.gridzyInstance.getOption('singleRowAlign'),
|
|
|
|
loopPos,
|
|
loopPos2,
|
|
items = renderObject.items,
|
|
itemsVisible = [],
|
|
itemsHidden = [],
|
|
itemsLength,
|
|
item,
|
|
containerWidth = renderObject.dimensions.contentBox.width,
|
|
optWidthAll = 0,
|
|
optWidthRow,
|
|
curWidthRow = 0,
|
|
rows = [],
|
|
rowsLength,
|
|
row,
|
|
rowLength,
|
|
prevRow,
|
|
displaySizeAllX = 0,
|
|
displaySizeAllY = 0,
|
|
containerWidthWithoutSpaces,
|
|
fillRow;
|
|
|
|
lastRowAlign = fillLastRow === false && lastRowAlign === 'justified' ? 'left' : lastRowAlign;
|
|
singleRowAlign = fillSingleRow === false && singleRowAlign === 'justified' ? 'left' : singleRowAlign;
|
|
|
|
// get optimal width of all items
|
|
loopPos = items.length;
|
|
while (loopPos--) {
|
|
item = items[loopPos];
|
|
if (item.valid && item.matchFilter && !(hideOnMissingImage && item.imageError)) {
|
|
itemsVisible.unshift(item);
|
|
item.optWidth = desiredHeight * item.width / item.height;
|
|
optWidthAll += item.optWidth;
|
|
} else {
|
|
itemsHidden.unshift(item);
|
|
}
|
|
}
|
|
itemsLength = itemsVisible.length;
|
|
|
|
// find the optimal length (pixels) of a row
|
|
optWidthRow = lastRowAlign === 'justified' ? optWidthAll / (Math.round((optWidthAll + (spaceBetween * itemsLength)) / (containerWidth + spaceBetween)) || 1) : containerWidth;
|
|
|
|
// attach items to rows
|
|
loopPos = itemsLength;
|
|
while (loopPos--) {
|
|
item = itemsVisible[itemsLength - 1 - loopPos];
|
|
if (row === undefined || curWidthRow + (item.optWidth / 2) > optWidthRow * rows.length) {
|
|
rows.push(row = {
|
|
displaySizeY: 0,
|
|
displayPosY: 0,
|
|
ratioAll: 0,
|
|
optWidth: 0,
|
|
items: []
|
|
});
|
|
}
|
|
curWidthRow += item.optWidth;
|
|
row.optWidth += item.optWidth;
|
|
row.ratioAll += item.aspectRatio;
|
|
row.items.push(item);
|
|
}
|
|
|
|
// calculate sizes and positions of elements
|
|
renderObject.height = 0;
|
|
rowsLength = rows.length;
|
|
prevRow = null;
|
|
for (loopPos = 0; loopPos < rowsLength; loopPos++) {
|
|
row = rows[loopPos];
|
|
displaySizeAllX = 0;
|
|
displaySizeAllY = 0;
|
|
rowLength = row.items.length;
|
|
containerWidthWithoutSpaces = containerWidth - ((rowLength - 1) * spaceBetween);
|
|
fillRow = rowsLength === 1 && singleRowAlign === 'justified' || rowsLength > 1 && lastRowAlign === 'justified' || loopPos < rowsLength - 1 || row.optWidth >= containerWidthWithoutSpaces;
|
|
|
|
if (!fillRow && (rowsLength === 1 && singleRowAlign !== 'left' || rowsLength > 1 && lastRowAlign !== 'left')) {
|
|
displaySizeAllX += Math.round((containerWidth - (row.optWidth + ((rowLength - 1) * spaceBetween))) * ((rowsLength > 1 ? lastRowAlign : singleRowAlign) === 'right' ? 1 : .5));
|
|
}
|
|
|
|
for (loopPos2 = 0; loopPos2 < rowLength; loopPos2++) {
|
|
item = row.items[loopPos2];
|
|
item.displayPosX = displaySizeAllX + (loopPos2 * spaceBetween);
|
|
|
|
item.displaySizeX = fillRow ? Math.round((item.aspectRatio / row.ratioAll) * containerWidthWithoutSpaces) : Math.round(item.optWidth);
|
|
displaySizeAllX += item.displaySizeX;
|
|
|
|
if (fillRow && loopPos2 === rowLength - 1 && displaySizeAllX !== containerWidthWithoutSpaces) {
|
|
item.displaySizeX += containerWidthWithoutSpaces - displaySizeAllX;
|
|
}
|
|
displaySizeAllY += item.displaySizeX / item.aspectRatio;
|
|
}
|
|
|
|
row.displaySizeY = Math.round(displaySizeAllY / rowLength);
|
|
row.displayPosY = prevRow ? prevRow.displayPosY + prevRow.displaySizeY + spaceBetween : 0;
|
|
|
|
for (loopPos2 = 0; loopPos2 < rowLength; loopPos2++) {
|
|
item = row.items[loopPos2];
|
|
item.displaySizeY = row.displaySizeY;
|
|
item.displayPosY = row.displayPosY;
|
|
|
|
// set visible parameter and styles for current item
|
|
item.visible = true;
|
|
item.styleToApply = ''
|
|
+ 'position: absolute; '
|
|
+ 'width: ' + item.displaySizeX + 'px; '
|
|
+ 'height: ' + item.displaySizeY + 'px; '
|
|
+ 'left: ' + (renderObject.dimensions.padding.left + item.displayPosX) + 'px; '
|
|
+ 'top: ' + (renderObject.dimensions.padding.top + item.displayPosY) + 'px; '
|
|
+ 'font-size: ' + (autoFontSize ? (item.displaySizeX * 100 / item.width) + '%' : '100%') + '; ';
|
|
}
|
|
|
|
renderObject.height += row.displaySizeY + (loopPos ? spaceBetween : 0);
|
|
prevRow = row;
|
|
}
|
|
|
|
// set styles for container
|
|
renderObject.styleToApply = 'height: ' + (renderObject.dimensions.actualBox.height - renderObject.dimensions.contentBox.height + renderObject.height) + 'px; ';
|
|
|
|
// set visible parameter for elements which get hidden
|
|
loopPos = itemsHidden.length;
|
|
while (loopPos--) {
|
|
itemsHidden[loopPos].visible = false;
|
|
}
|
|
}
|
|
});
|
|
|
|
new GridzyLayout({
|
|
name: 'waterfall',
|
|
options: {
|
|
autoFontSize: false,
|
|
desiredWidth: 250,
|
|
hideOnMissingImage: true,
|
|
horizontalOrder: false,
|
|
spaceBetween: 4
|
|
},
|
|
handler: function(renderObject) {
|
|
|
|
var // get layout options
|
|
autoFontSize = !!renderObject.gridzyInstance.getOption('autoFontSize'),
|
|
desiredWidth = parseInt(renderObject.gridzyInstance.getOption('desiredWidth')),
|
|
hideOnMissingImage = !!renderObject.gridzyInstance.getOption('hideOnMissingImage'),
|
|
horizontalOrder = !!renderObject.gridzyInstance.getOption('horizontalOrder'),
|
|
spaceBetween = parseInt(renderObject.gridzyInstance.getOption('spaceBetween')),
|
|
|
|
columnCount,
|
|
availableContainerWidth,
|
|
usedContainerWidth = 0,
|
|
columnWidth,
|
|
grid = [],
|
|
currentColumn,
|
|
largestColumnHeight = 0,
|
|
loopPos,
|
|
loopPos2,
|
|
items = renderObject.items,
|
|
itemsVisible = [],
|
|
itemsHidden = [],
|
|
itemsLength,
|
|
containerWidth = renderObject.dimensions.contentBox.width,
|
|
item;
|
|
|
|
// separate visible versus hidden items
|
|
loopPos = items.length;
|
|
while (loopPos--) {
|
|
item = items[loopPos];
|
|
if (item.valid && item.matchFilter && !(hideOnMissingImage && item.imageError)) {
|
|
itemsVisible.push(item);
|
|
} else {
|
|
itemsHidden.push(item);
|
|
}
|
|
}
|
|
itemsLength = itemsVisible.length;
|
|
|
|
// get optimal count of columns
|
|
columnCount = Math.round((containerWidth + spaceBetween) / (desiredWidth + spaceBetween)) || 1;
|
|
columnCount = columnCount > itemsLength ? itemsLength : columnCount;
|
|
availableContainerWidth = containerWidth - ((columnCount - 1) * spaceBetween);
|
|
|
|
// get width of columns
|
|
loopPos = columnCount;
|
|
while (loopPos--) {
|
|
columnWidth = availableContainerWidth - Math.round(availableContainerWidth * loopPos / columnCount) - usedContainerWidth;
|
|
usedContainerWidth += columnWidth;
|
|
|
|
grid.push({
|
|
index: grid.length,
|
|
width: columnWidth,
|
|
left: availableContainerWidth - usedContainerWidth + (loopPos * spaceBetween),
|
|
height: 0
|
|
});
|
|
}
|
|
|
|
//attach items to columns
|
|
loopPos = itemsLength;
|
|
while (loopPos--) {
|
|
// get current column
|
|
if (horizontalOrder) {
|
|
currentColumn = grid[currentColumn && currentColumn.index > 0 ? currentColumn.index - 1 : columnCount - 1];
|
|
} else {
|
|
loopPos2 = columnCount - 1;
|
|
currentColumn = grid[loopPos2];
|
|
while (loopPos2--) {
|
|
if (grid[loopPos2].height < currentColumn.height - 2) { // minus 2 to prevent to many jumps if there are many images with same size on top
|
|
currentColumn = grid[loopPos2];
|
|
}
|
|
}
|
|
}
|
|
|
|
// calculate size and position
|
|
item = itemsVisible[loopPos];
|
|
item.displaySizeX = currentColumn.width;
|
|
item.displaySizeY = Math.round((item.displaySizeX / item.width) * item.height);
|
|
item.displayPosX = currentColumn.left;
|
|
item.displayPosY = currentColumn.height + (currentColumn.height ? spaceBetween : 0);
|
|
currentColumn.height += item.displaySizeY + (currentColumn.height ? spaceBetween : 0);
|
|
|
|
if (largestColumnHeight < currentColumn.height) {
|
|
largestColumnHeight = currentColumn.height;
|
|
}
|
|
|
|
// set visible parameter and styles for current item
|
|
item.visible = true;
|
|
item.styleToApply = ''
|
|
+ 'position: absolute; '
|
|
+ 'width: ' + item.displaySizeX + 'px; '
|
|
+ 'height: ' + item.displaySizeY + 'px; '
|
|
+ 'left: ' + (renderObject.dimensions.padding.left + item.displayPosX) + 'px; '
|
|
+ 'top: ' + (renderObject.dimensions.padding.top + item.displayPosY) + 'px; '
|
|
+ 'font-size: ' + (autoFontSize ? (item.displaySizeX * 100 / item.width) + '%' : '100%') + '; ';
|
|
}
|
|
|
|
// set styles for container
|
|
renderObject.styleToApply = 'height: ' + (renderObject.dimensions.actualBox.height - renderObject.dimensions.contentBox.height + largestColumnHeight) + 'px; ';
|
|
|
|
// set visible parameter for elements which get hidden
|
|
loopPos = itemsHidden.length;
|
|
while (loopPos--) {
|
|
itemsHidden[loopPos].visible = false;
|
|
}
|
|
}
|
|
});
|