META-INF.resources.bower_components.outlayer.outlayer.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jwebmp-jqui-vertical-timeline Show documentation
Show all versions of jwebmp-jqui-vertical-timeline Show documentation
The JWebSwing JQuery Vertical Timeline
/*!
* Outlayer v2.1.1
* the brains and guts of a layout library
* MIT license
*/
( function (window, factory) {
'use strict';
// universal module definition
/* jshint strict: false */
/* globals define, module, require */
if (typeof define == 'function' && define.amd) {
// AMD - RequireJS
define([
'ev-emitter/ev-emitter',
'get-size/get-size',
'fizzy-ui-utils/utils',
'./item'
],
function (EvEmitter, getSize, utils, Item) {
return factory(window, EvEmitter, getSize, utils, Item);
}
);
} else if (typeof module == 'object' && module.exports) {
// CommonJS - Browserify, Webpack
module.exports = factory(
window,
require('ev-emitter'),
require('get-size'),
require('fizzy-ui-utils'),
require('./item')
);
} else {
// browser global
window.Outlayer = factory(
window,
window.EvEmitter,
window.getSize,
window.fizzyUIUtils,
window.Outlayer.Item
);
}
}(window, function factory(window, EvEmitter, getSize, utils, Item) {
'use strict';
// ----- vars ----- //
var console = window.console;
var jQuery = window.jQuery;
var noop = function () {
};
// -------------------------- Outlayer -------------------------- //
// globally unique identifiers
var GUID = 0;
// internal store of all Outlayer intances
var instances = {};
/**
* @param {Element, String} element
* @param {Object} options
* @constructor
*/
function Outlayer(element, options) {
var queryElement = utils.getQueryElement(element);
if (!queryElement) {
if (console) {
console.error('Bad element for ' + this.constructor.namespace +
': ' + ( queryElement || element ));
}
return;
}
this.element = queryElement;
// add jQuery
if (jQuery) {
this.$element = jQuery(this.element);
}
// options
this.options = utils.extend({}, this.constructor.defaults);
this.option(options);
// add id for Outlayer.getFromElement
var id = ++GUID;
this.element.outlayerGUID = id; // expando
instances[id] = this; // associate via id
// kick it off
this._create();
var isInitLayout = this._getOption('initLayout');
if (isInitLayout) {
this.layout();
}
}
// settings are for internal use only
Outlayer.namespace = 'outlayer';
Outlayer.Item = Item;
// default options
Outlayer.defaults = {
containerStyle: {
position: 'relative'
},
initLayout: true,
originLeft: true,
originTop: true,
resize: true,
resizeContainer: true,
// item options
transitionDuration: '0.4s',
hiddenStyle: {
opacity: 0,
transform: 'scale(0.001)'
},
visibleStyle: {
opacity: 1,
transform: 'scale(1)'
}
};
var proto = Outlayer.prototype;
// inherit EvEmitter
utils.extend(proto, EvEmitter.prototype);
/**
* set options
* @param {Object} opts
*/
proto.option = function (opts) {
utils.extend(this.options, opts);
};
/**
* get backwards compatible option value, check old name
*/
proto._getOption = function (option) {
var oldOption = this.constructor.compatOptions[option];
return oldOption && this.options[oldOption] !== undefined ?
this.options[oldOption] : this.options[option];
};
Outlayer.compatOptions = {
// currentName: oldName
initLayout: 'isInitLayout',
horizontal: 'isHorizontal',
layoutInstant: 'isLayoutInstant',
originLeft: 'isOriginLeft',
originTop: 'isOriginTop',
resize: 'isResizeBound',
resizeContainer: 'isResizingContainer'
};
proto._create = function () {
// get items from children
this.reloadItems();
// elements that affect layout, but are not laid out
this.stamps = [];
this.stamp(this.options.stamp);
// set container style
utils.extend(this.element.style, this.options.containerStyle);
// bind resize method
var canBindResize = this._getOption('resize');
if (canBindResize) {
this.bindResize();
}
};
// goes through all children again and gets bricks in proper order
proto.reloadItems = function () {
// collection of item elements
this.items = this._itemize(this.element.children);
};
/**
* turn elements into Outlayer.Items to be used in layout
* @param {Array or NodeList or HTMLElement} elems
* @returns {Array} items - collection of new Outlayer Items
*/
proto._itemize = function (elems) {
var itemElems = this._filterFindItemElements(elems);
var Item = this.constructor.Item;
// create new Outlayer Items for collection
var items = [];
for (var i = 0; i < itemElems.length; i++) {
var elem = itemElems[i];
var item = new Item(elem, this);
items.push(item);
}
return items;
};
/**
* get item elements to be used in layout
* @param {Array or NodeList or HTMLElement} elems
* @returns {Array} items - item elements
*/
proto._filterFindItemElements = function (elems) {
return utils.filterFindElements(elems, this.options.itemSelector);
};
/**
* getter method for getting item elements
* @returns {Array} elems - collection of item elements
*/
proto.getItemElements = function () {
return this.items.map(function (item) {
return item.element;
});
};
// ----- init & layout ----- //
/**
* lays out all items
*/
proto.layout = function () {
this._resetLayout();
this._manageStamps();
// don't animate first layout
var layoutInstant = this._getOption('layoutInstant');
var isInstant = layoutInstant !== undefined ?
layoutInstant : !this._isLayoutInited;
this.layoutItems(this.items, isInstant);
// flag for initalized
this._isLayoutInited = true;
};
// _init is alias for layout
proto._init = proto.layout;
/**
* logic before any new layout
*/
proto._resetLayout = function () {
this.getSize();
};
proto.getSize = function () {
this.size = getSize(this.element);
};
/**
* get measurement from option, for columnWidth, rowHeight, gutter
* if option is String -> get element from selector string, & get size of element
* if option is Element -> get size of element
* else use option as a number
*
* @param {String} measurement
* @param {String} size - width or height
* @private
*/
proto._getMeasurement = function (measurement, size) {
var option = this.options[measurement];
var elem;
if (!option) {
// default to 0
this[measurement] = 0;
} else {
// use option as an element
if (typeof option == 'string') {
elem = this.element.querySelector(option);
} else if (option instanceof HTMLElement) {
elem = option;
}
// use size of element, if element
this[measurement] = elem ? getSize(elem)[size] : option;
}
};
/**
* layout a collection of item elements
* @api public
*/
proto.layoutItems = function (items, isInstant) {
items = this._getItemsForLayout(items);
this._layoutItems(items, isInstant);
this._postLayout();
};
/**
* get the items to be laid out
* you may want to skip over some items
* @param {Array} items
* @returns {Array} items
*/
proto._getItemsForLayout = function (items) {
return items.filter(function (item) {
return !item.isIgnored;
});
};
/**
* layout items
* @param {Array} items
* @param {Boolean} isInstant
*/
proto._layoutItems = function (items, isInstant) {
this._emitCompleteOnItems('layout', items);
if (!items || !items.length) {
// no items, emit event with empty array
return;
}
var queue = [];
items.forEach(function (item) {
// get x/y object from method
var position = this._getItemLayoutPosition(item);
// enqueue
position.item = item;
position.isInstant = isInstant || item.isLayoutInstant;
queue.push(position);
}, this);
this._processLayoutQueue(queue);
};
/**
* get item layout position
* @param {Outlayer.Item} item
* @returns {Object} x and y position
*/
proto._getItemLayoutPosition = function (/* item */) {
return {
x: 0,
y: 0
};
};
/**
* iterate over array and position each item
* Reason being - separating this logic prevents 'layout invalidation'
* thx @paul_irish
* @param {Array} queue
*/
proto._processLayoutQueue = function (queue) {
this.updateStagger();
queue.forEach(function (obj, i) {
this._positionItem(obj.item, obj.x, obj.y, obj.isInstant, i);
}, this);
};
// set stagger from option in milliseconds number
proto.updateStagger = function () {
var stagger = this.options.stagger;
if (stagger === null || stagger === undefined) {
this.stagger = 0;
return;
}
this.stagger = getMilliseconds(stagger);
return this.stagger;
};
/**
* Sets position of item in DOM
* @param {Outlayer.Item} item
* @param {Number} x - horizontal position
* @param {Number} y - vertical position
* @param {Boolean} isInstant - disables transitions
*/
proto._positionItem = function (item, x, y, isInstant, i) {
if (isInstant) {
// if not transition, just set CSS
item.goTo(x, y);
} else {
item.stagger(i * this.stagger);
item.moveTo(x, y);
}
};
/**
* Any logic you want to do after each layout,
* i.e. size the container
*/
proto._postLayout = function () {
this.resizeContainer();
};
proto.resizeContainer = function () {
var isResizingContainer = this._getOption('resizeContainer');
if (!isResizingContainer) {
return;
}
var size = this._getContainerSize();
if (size) {
this._setContainerMeasure(size.width, true);
this._setContainerMeasure(size.height, false);
}
};
/**
* Sets width or height of container if returned
* @returns {Object} size
* @param {Number} width
* @param {Number} height
*/
proto._getContainerSize = noop;
/**
* @param {Number} measure - size of width or height
* @param {Boolean} isWidth
*/
proto._setContainerMeasure = function (measure, isWidth) {
if (measure === undefined) {
return;
}
var elemSize = this.size;
// add padding and border width if border box
if (elemSize.isBorderBox) {
measure += isWidth ? elemSize.paddingLeft + elemSize.paddingRight +
elemSize.borderLeftWidth + elemSize.borderRightWidth :
elemSize.paddingBottom + elemSize.paddingTop +
elemSize.borderTopWidth + elemSize.borderBottomWidth;
}
measure = Math.max(measure, 0);
this.element.style[isWidth ? 'width' : 'height'] = measure + 'px';
};
/**
* emit eventComplete on a collection of items events
* @param {String} eventName
* @param {Array} items - Outlayer.Items
*/
proto._emitCompleteOnItems = function (eventName, items) {
var _this = this;
function onComplete() {
_this.dispatchEvent(eventName + 'Complete', null, [items]);
}
var count = items.length;
if (!items || !count) {
onComplete();
return;
}
var doneCount = 0;
function tick() {
doneCount++;
if (doneCount == count) {
onComplete();
}
}
// bind callback
items.forEach(function (item) {
item.once(eventName, tick);
});
};
/**
* emits events via EvEmitter and jQuery events
* @param {String} type - name of event
* @param {Event} event - original event
* @param {Array} args - extra arguments
*/
proto.dispatchEvent = function (type, event, args) {
// add original event to arguments
var emitArgs = event ? [event].concat(args) : args;
this.emitEvent(type, emitArgs);
if (jQuery) {
// set this.$element
this.$element = this.$element || jQuery(this.element);
if (event) {
// create jQuery event
var $event = jQuery.Event(event);
$event.type = type;
this.$element.trigger($event, args);
} else {
// just trigger with type if no event available
this.$element.trigger(type, args);
}
}
};
// -------------------------- ignore & stamps -------------------------- //
/**
* keep item in collection, but do not lay it out
* ignored items do not get skipped in layout
* @param {Element} elem
*/
proto.ignore = function (elem) {
var item = this.getItem(elem);
if (item) {
item.isIgnored = true;
}
};
/**
* return item to layout collection
* @param {Element} elem
*/
proto.unignore = function (elem) {
var item = this.getItem(elem);
if (item) {
delete item.isIgnored;
}
};
/**
* adds elements to stamps
* @param {NodeList, Array, Element, or String} elems
*/
proto.stamp = function (elems) {
elems = this._find(elems);
if (!elems) {
return;
}
this.stamps = this.stamps.concat(elems);
// ignore
elems.forEach(this.ignore, this);
};
/**
* removes elements to stamps
* @param {NodeList, Array, or Element} elems
*/
proto.unstamp = function (elems) {
elems = this._find(elems);
if (!elems) {
return;
}
elems.forEach(function (elem) {
// filter out removed stamp elements
utils.removeFrom(this.stamps, elem);
this.unignore(elem);
}, this);
};
/**
* finds child elements
* @param {NodeList, Array, Element, or String} elems
* @returns {Array} elems
*/
proto._find = function (elems) {
if (!elems) {
return;
}
// if string, use argument as selector string
if (typeof elems == 'string') {
elems = this.element.querySelectorAll(elems);
}
elems = utils.makeArray(elems);
return elems;
};
proto._manageStamps = function () {
if (!this.stamps || !this.stamps.length) {
return;
}
this._getBoundingRect();
this.stamps.forEach(this._manageStamp, this);
};
// update boundingLeft / Top
proto._getBoundingRect = function () {
// get bounding rect for container element
var boundingRect = this.element.getBoundingClientRect();
var size = this.size;
this._boundingRect = {
left: boundingRect.left + size.paddingLeft + size.borderLeftWidth,
top: boundingRect.top + size.paddingTop + size.borderTopWidth,
right: boundingRect.right - ( size.paddingRight + size.borderRightWidth ),
bottom: boundingRect.bottom - ( size.paddingBottom + size.borderBottomWidth )
};
};
/**
* @param {Element} stamp
**/
proto._manageStamp = noop;
/**
* get x/y position of element relative to container element
* @param {Element} elem
* @returns {Object} offset - has left, top, right, bottom
*/
proto._getElementOffset = function (elem) {
var boundingRect = elem.getBoundingClientRect();
var thisRect = this._boundingRect;
var size = getSize(elem);
var offset = {
left: boundingRect.left - thisRect.left - size.marginLeft,
top: boundingRect.top - thisRect.top - size.marginTop,
right: thisRect.right - boundingRect.right - size.marginRight,
bottom: thisRect.bottom - boundingRect.bottom - size.marginBottom
};
return offset;
};
// -------------------------- resize -------------------------- //
// enable event handlers for listeners
// i.e. resize -> onresize
proto.handleEvent = utils.handleEvent;
/**
* Bind layout to window resizing
*/
proto.bindResize = function () {
window.addEventListener('resize', this);
this.isResizeBound = true;
};
/**
* Unbind layout to window resizing
*/
proto.unbindResize = function () {
window.removeEventListener('resize', this);
this.isResizeBound = false;
};
proto.onresize = function () {
this.resize();
};
utils.debounceMethod(Outlayer, 'onresize', 100);
proto.resize = function () {
// don't trigger if size did not change
// or if resize was unbound. See #9
if (!this.isResizeBound || !this.needsResizeLayout()) {
return;
}
this.layout();
};
/**
* check if layout is needed post layout
* @returns Boolean
*/
proto.needsResizeLayout = function () {
var size = getSize(this.element);
// check that this.size and size are there
// IE8 triggers resize on body size change, so they might not be
var hasSizes = this.size && size;
return hasSizes && size.innerWidth !== this.size.innerWidth;
};
// -------------------------- methods -------------------------- //
/**
* add items to Outlayer instance
* @param {Array or NodeList or Element} elems
* @returns {Array} items - Outlayer.Items
**/
proto.addItems = function (elems) {
var items = this._itemize(elems);
// add items to collection
if (items.length) {
this.items = this.items.concat(items);
}
return items;
};
/**
* Layout newly-appended item elements
* @param {Array or NodeList or Element} elems
*/
proto.appended = function (elems) {
var items = this.addItems(elems);
if (!items.length) {
return;
}
// layout and reveal just the new items
this.layoutItems(items, true);
this.reveal(items);
};
/**
* Layout prepended elements
* @param {Array or NodeList or Element} elems
*/
proto.prepended = function (elems) {
var items = this._itemize(elems);
if (!items.length) {
return;
}
// add items to beginning of collection
var previousItems = this.items.slice(0);
this.items = items.concat(previousItems);
// start new layout
this._resetLayout();
this._manageStamps();
// layout new stuff without transition
this.layoutItems(items, true);
this.reveal(items);
// layout previous items
this.layoutItems(previousItems);
};
/**
* reveal a collection of items
* @param {Array of Outlayer.Items} items
*/
proto.reveal = function (items) {
this._emitCompleteOnItems('reveal', items);
if (!items || !items.length) {
return;
}
var stagger = this.updateStagger();
items.forEach(function (item, i) {
item.stagger(i * stagger);
item.reveal();
});
};
/**
* hide a collection of items
* @param {Array of Outlayer.Items} items
*/
proto.hide = function (items) {
this._emitCompleteOnItems('hide', items);
if (!items || !items.length) {
return;
}
var stagger = this.updateStagger();
items.forEach(function (item, i) {
item.stagger(i * stagger);
item.hide();
});
};
/**
* reveal item elements
* @param {Array}, {Element}, {NodeList} items
*/
proto.revealItemElements = function (elems) {
var items = this.getItems(elems);
this.reveal(items);
};
/**
* hide item elements
* @param {Array}, {Element}, {NodeList} items
*/
proto.hideItemElements = function (elems) {
var items = this.getItems(elems);
this.hide(items);
};
/**
* get Outlayer.Item, given an Element
* @param {Element} elem
* @param {Function} callback
* @returns {Outlayer.Item} item
*/
proto.getItem = function (elem) {
// loop through items to get the one that matches
for (var i = 0; i < this.items.length; i++) {
var item = this.items[i];
if (item.element == elem) {
// return item
return item;
}
}
};
/**
* get collection of Outlayer.Items, given Elements
* @param {Array} elems
* @returns {Array} items - Outlayer.Items
*/
proto.getItems = function (elems) {
elems = utils.makeArray(elems);
var items = [];
elems.forEach(function (elem) {
var item = this.getItem(elem);
if (item) {
items.push(item);
}
}, this);
return items;
};
/**
* remove element(s) from instance and DOM
* @param {Array or NodeList or Element} elems
*/
proto.remove = function (elems) {
var removeItems = this.getItems(elems);
this._emitCompleteOnItems('remove', removeItems);
// bail if no items to remove
if (!removeItems || !removeItems.length) {
return;
}
removeItems.forEach(function (item) {
item.remove();
// remove item from collection
utils.removeFrom(this.items, item);
}, this);
};
// ----- destroy ----- //
// remove and disable Outlayer instance
proto.destroy = function () {
// clean up dynamic styles
var style = this.element.style;
style.height = '';
style.position = '';
style.width = '';
// destroy items
this.items.forEach(function (item) {
item.destroy();
});
this.unbindResize();
var id = this.element.outlayerGUID;
delete instances[id]; // remove reference to instance by id
delete this.element.outlayerGUID;
// remove data for jQuery
if (jQuery) {
jQuery.removeData(this.element, this.constructor.namespace);
}
};
// -------------------------- data -------------------------- //
/**
* get Outlayer instance from element
* @param {Element} elem
* @returns {Outlayer}
*/
Outlayer.data = function (elem) {
elem = utils.getQueryElement(elem);
var id = elem && elem.outlayerGUID;
return id && instances[id];
};
// -------------------------- create Outlayer class -------------------------- //
/**
* create a layout class
* @param {String} namespace
*/
Outlayer.create = function (namespace, options) {
// sub-class Outlayer
var Layout = subclass(Outlayer);
// apply new options and compatOptions
Layout.defaults = utils.extend({}, Outlayer.defaults);
utils.extend(Layout.defaults, options);
Layout.compatOptions = utils.extend({}, Outlayer.compatOptions);
Layout.namespace = namespace;
Layout.data = Outlayer.data;
// sub-class Item
Layout.Item = subclass(Item);
// -------------------------- declarative -------------------------- //
utils.htmlInit(Layout, namespace);
// -------------------------- jQuery bridge -------------------------- //
// make into jQuery plugin
if (jQuery && jQuery.bridget) {
jQuery.bridget(namespace, Layout);
}
return Layout;
};
function subclass(Parent) {
function SubClass() {
Parent.apply(this, arguments);
}
SubClass.prototype = Object.create(Parent.prototype);
SubClass.prototype.constructor = SubClass;
return SubClass;
}
// ----- helpers ----- //
// how many milliseconds are in each unit
var msUnits = {
ms: 1,
s: 1000
};
// munge time-like parameter into millisecond number
// '0.4s' -> 40
function getMilliseconds(time) {
if (typeof time == 'number') {
return time;
}
var matches = time.match(/(^\d*\.?\d*)(\w*)/);
var num = matches && matches[1];
var unit = matches && matches[2];
if (!num.length) {
return 0;
}
num = parseFloat(num);
var mult = msUnits[unit] || 1;
return num * mult;
}
// ----- fin ----- //
// back in global
Outlayer.Item = Item;
return Outlayer;
}));
© 2015 - 2025 Weber Informatics LLC | Privacy Policy