tbg-foundation-sites
Version:
TBG fork of the most advanced responsive front-end framework in the world.
1,787 lines (1,536 loc) • 122 kB
JavaScript
/*!
* Flickity PACKAGED v2.0.5
* Touch, responsive, flickable carousels
*
* Licensed GPLv3 for open source use
* or Flickity Commercial License for commercial use
*
* http://flickity.metafizzy.co
* Copyright 2016 Metafizzy
*/
/**
* Bridget makes jQuery widgets
* v2.0.1
* MIT license
*/
/* jshint browser: true, strict: true, undef: true, unused: true */
(function (window, factory) {
// universal module definition
/*jshint strict: false */ /* globals define, module, require */
if (typeof define == 'function' && define.amd) {
// AMD
define('jquery-bridget/jquery-bridget', ['jquery'], function (jQuery) {
return factory(window, jQuery);
});
} else if (typeof module == 'object' && module.exports) {
// CommonJS
module.exports = factory(window, require('jquery'));
} else {
// browser global
window.jQueryBridget = factory(window, window.jQuery);
}
})(window, function factory(window, jQuery) {
'use strict';
// ----- utils ----- //
var arraySlice = Array.prototype.slice;
// helper function for logging errors
// $.error breaks jQuery chaining
var console = window.console;
var logError = typeof console == 'undefined' ? function () {} : function (message) {
console.error(message);
};
// ----- jQueryBridget ----- //
function jQueryBridget(namespace, PluginClass, $) {
$ = $ || jQuery || window.jQuery;
if (!$) {
return;
}
// add option method -> $().plugin('option', {...})
if (!PluginClass.prototype.option) {
// option setter
PluginClass.prototype.option = function (opts) {
// bail out if not an object
if (!$.isPlainObject(opts)) {
return;
}
this.options = $.extend(true, this.options, opts);
};
}
// make jQuery plugin
$.fn[namespace] = function (arg0 /*, arg1 */) {
if (typeof arg0 == 'string') {
// method call $().plugin( 'methodName', { options } )
// shift arguments by 1
var args = arraySlice.call(arguments, 1);
return methodCall(this, arg0, args);
}
// just $().plugin({ options })
plainCall(this, arg0);
return this;
};
// $().plugin('methodName')
function methodCall($elems, methodName, args) {
var returnValue;
var pluginMethodStr = '$().' + namespace + '("' + methodName + '")';
$elems.each(function (i, elem) {
// get instance
var instance = $.data(elem, namespace);
if (!instance) {
logError(namespace + ' not initialized. Cannot call methods, i.e. ' + pluginMethodStr);
return;
}
var method = instance[methodName];
if (!method || methodName.charAt(0) == '_') {
logError(pluginMethodStr + ' is not a valid method');
return;
}
// apply method, get return value
var value = method.apply(instance, args);
// set return value if value is returned, use only first value
returnValue = returnValue === undefined ? value : returnValue;
});
return returnValue !== undefined ? returnValue : $elems;
}
function plainCall($elems, options) {
$elems.each(function (i, elem) {
var instance = $.data(elem, namespace);
if (instance) {
// set options & init
instance.option(options);
instance._init();
} else {
// initialize new instance
instance = new PluginClass(elem, options);
$.data(elem, namespace, instance);
}
});
}
updateJQuery($);
}
// ----- updateJQuery ----- //
// set $.bridget for v1 backwards compatibility
function updateJQuery($) {
if (!$ || $ && $.bridget) {
return;
}
$.bridget = jQueryBridget;
}
updateJQuery(jQuery || window.jQuery);
// ----- ----- //
return jQueryBridget;
});
/**
* EvEmitter v1.0.3
* Lil' event emitter
* MIT License
*/
/* jshint unused: true, undef: true, strict: true */
(function (global, factory) {
// universal module definition
/* jshint strict: false */ /* globals define, module, window */
if (typeof define == 'function' && define.amd) {
// AMD - RequireJS
define('ev-emitter/ev-emitter', factory);
} else if (typeof module == 'object' && module.exports) {
// CommonJS - Browserify, Webpack
module.exports = factory();
} else {
// Browser globals
global.EvEmitter = factory();
}
})(typeof window != 'undefined' ? window : this, function () {
function EvEmitter() {}
var proto = EvEmitter.prototype;
proto.on = function (eventName, listener) {
if (!eventName || !listener) {
return;
}
// set events hash
var events = this._events = this._events || {};
// set listeners array
var listeners = events[eventName] = events[eventName] || [];
// only add once
if (listeners.indexOf(listener) == -1) {
listeners.push(listener);
}
return this;
};
proto.once = function (eventName, listener) {
if (!eventName || !listener) {
return;
}
// add event
this.on(eventName, listener);
// set once flag
// set onceEvents hash
var onceEvents = this._onceEvents = this._onceEvents || {};
// set onceListeners object
var onceListeners = onceEvents[eventName] = onceEvents[eventName] || {};
// set flag
onceListeners[listener] = true;
return this;
};
proto.off = function (eventName, listener) {
var listeners = this._events && this._events[eventName];
if (!listeners || !listeners.length) {
return;
}
var index = listeners.indexOf(listener);
if (index != -1) {
listeners.splice(index, 1);
}
return this;
};
proto.emitEvent = function (eventName, args) {
var listeners = this._events && this._events[eventName];
if (!listeners || !listeners.length) {
return;
}
var i = 0;
var listener = listeners[i];
args = args || [];
// once stuff
var onceListeners = this._onceEvents && this._onceEvents[eventName];
while (listener) {
var isOnce = onceListeners && onceListeners[listener];
if (isOnce) {
// remove listener
// remove before trigger to prevent recursion
this.off(eventName, listener);
// unset once flag
delete onceListeners[listener];
}
// trigger listener
listener.apply(this, args);
// get next listener
i += isOnce ? 0 : 1;
listener = listeners[i];
}
return this;
};
return EvEmitter;
});
/*!
* getSize v2.0.2
* measure size of elements
* MIT license
*/
/*jshint browser: true, strict: true, undef: true, unused: true */
/*global define: false, module: false, console: false */
(function (window, factory) {
'use strict';
if (typeof define == 'function' && define.amd) {
// AMD
define('get-size/get-size', [], function () {
return factory();
});
} else if (typeof module == 'object' && module.exports) {
// CommonJS
module.exports = factory();
} else {
// browser global
window.getSize = factory();
}
})(window, function factory() {
'use strict';
// -------------------------- helpers -------------------------- //
// get a number from a string, not a percentage
function getStyleSize(value) {
var num = parseFloat(value);
// not a percent like '100%', and a number
var isValid = value.indexOf('%') == -1 && !isNaN(num);
return isValid && num;
}
function noop() {}
var logError = typeof console == 'undefined' ? noop : function (message) {
console.error(message);
};
// -------------------------- measurements -------------------------- //
var measurements = ['paddingLeft', 'paddingRight', 'paddingTop', 'paddingBottom', 'marginLeft', 'marginRight', 'marginTop', 'marginBottom', 'borderLeftWidth', 'borderRightWidth', 'borderTopWidth', 'borderBottomWidth'];
var measurementsLength = measurements.length;
function getZeroSize() {
var size = {
width: 0,
height: 0,
innerWidth: 0,
innerHeight: 0,
outerWidth: 0,
outerHeight: 0
};
for (var i = 0; i < measurementsLength; i++) {
var measurement = measurements[i];
size[measurement] = 0;
}
return size;
}
// -------------------------- getStyle -------------------------- //
/**
* getStyle, get style of element, check for Firefox bug
* https://bugzilla.mozilla.org/show_bug.cgi?id=548397
*/
function getStyle(elem) {
var style = getComputedStyle(elem);
if (!style) {
logError('Style returned ' + style + '. Are you running this code in a hidden iframe on Firefox? ' + 'See http://bit.ly/getsizebug1');
}
return style;
}
// -------------------------- setup -------------------------- //
var isSetup = false;
var isBoxSizeOuter;
/**
* setup
* check isBoxSizerOuter
* do on first getSize() rather than on page load for Firefox bug
*/
function setup() {
// setup once
if (isSetup) {
return;
}
isSetup = true;
// -------------------------- box sizing -------------------------- //
/**
* WebKit measures the outer-width on style.width on border-box elems
* IE & Firefox<29 measures the inner-width
*/
var div = document.createElement('div');
div.style.width = '200px';
div.style.padding = '1px 2px 3px 4px';
div.style.borderStyle = 'solid';
div.style.borderWidth = '1px 2px 3px 4px';
div.style.boxSizing = 'border-box';
var body = document.body || document.documentElement;
body.appendChild(div);
var style = getStyle(div);
getSize.isBoxSizeOuter = isBoxSizeOuter = getStyleSize(style.width) == 200;
body.removeChild(div);
}
// -------------------------- getSize -------------------------- //
function getSize(elem) {
setup();
// use querySeletor if elem is string
if (typeof elem == 'string') {
elem = document.querySelector(elem);
}
// do not proceed on non-objects
if (!elem || typeof elem != 'object' || !elem.nodeType) {
return;
}
var style = getStyle(elem);
// if hidden, everything is 0
if (style.display == 'none') {
return getZeroSize();
}
var size = {};
size.width = elem.offsetWidth;
size.height = elem.offsetHeight;
var isBorderBox = size.isBorderBox = style.boxSizing == 'border-box';
// get all measurements
for (var i = 0; i < measurementsLength; i++) {
var measurement = measurements[i];
var value = style[measurement];
var num = parseFloat(value);
// any 'auto', 'medium' value will be 0
size[measurement] = !isNaN(num) ? num : 0;
}
var paddingWidth = size.paddingLeft + size.paddingRight;
var paddingHeight = size.paddingTop + size.paddingBottom;
var marginWidth = size.marginLeft + size.marginRight;
var marginHeight = size.marginTop + size.marginBottom;
var borderWidth = size.borderLeftWidth + size.borderRightWidth;
var borderHeight = size.borderTopWidth + size.borderBottomWidth;
var isBorderBoxSizeOuter = isBorderBox && isBoxSizeOuter;
// overwrite width and height if we can get it from style
var styleWidth = getStyleSize(style.width);
if (styleWidth !== false) {
size.width = styleWidth + (
// add padding and border unless it's already including it
isBorderBoxSizeOuter ? 0 : paddingWidth + borderWidth);
}
var styleHeight = getStyleSize(style.height);
if (styleHeight !== false) {
size.height = styleHeight + (
// add padding and border unless it's already including it
isBorderBoxSizeOuter ? 0 : paddingHeight + borderHeight);
}
size.innerWidth = size.width - (paddingWidth + borderWidth);
size.innerHeight = size.height - (paddingHeight + borderHeight);
size.outerWidth = size.width + marginWidth;
size.outerHeight = size.height + marginHeight;
return size;
}
return getSize;
});
/**
* matchesSelector v2.0.1
* matchesSelector( element, '.selector' )
* MIT license
*/
/*jshint browser: true, strict: true, undef: true, unused: true */
(function (window, factory) {
/*global define: false, module: false */
'use strict';
// universal module definition
if (typeof define == 'function' && define.amd) {
// AMD
define('desandro-matches-selector/matches-selector', factory);
} else if (typeof module == 'object' && module.exports) {
// CommonJS
module.exports = factory();
} else {
// browser global
window.matchesSelector = factory();
}
})(window, function factory() {
'use strict';
var matchesMethod = function () {
var ElemProto = Element.prototype;
// check for the standard method name first
if (ElemProto.matches) {
return 'matches';
}
// check un-prefixed
if (ElemProto.matchesSelector) {
return 'matchesSelector';
}
// check vendor prefixes
var prefixes = ['webkit', 'moz', 'ms', 'o'];
for (var i = 0; i < prefixes.length; i++) {
var prefix = prefixes[i];
var method = prefix + 'MatchesSelector';
if (ElemProto[method]) {
return method;
}
}
}();
return function matchesSelector(elem, selector) {
return elem[matchesMethod](selector);
};
});
/**
* Fizzy UI utils v2.0.3
* MIT license
*/
/*jshint browser: true, undef: true, unused: true, strict: true */
(function (window, factory) {
// universal module definition
/*jshint strict: false */ /*globals define, module, require */
if (typeof define == 'function' && define.amd) {
// AMD
define('fizzy-ui-utils/utils', ['desandro-matches-selector/matches-selector'], function (matchesSelector) {
return factory(window, matchesSelector);
});
} else if (typeof module == 'object' && module.exports) {
// CommonJS
module.exports = factory(window, require('desandro-matches-selector'));
} else {
// browser global
window.fizzyUIUtils = factory(window, window.matchesSelector);
}
})(window, function factory(window, matchesSelector) {
var utils = {};
// ----- extend ----- //
// extends objects
utils.extend = function (a, b) {
for (var prop in b) {
a[prop] = b[prop];
}
return a;
};
// ----- modulo ----- //
utils.modulo = function (num, div) {
return (num % div + div) % div;
};
// ----- makeArray ----- //
// turn element or nodeList into an array
utils.makeArray = function (obj) {
var ary = [];
if (Array.isArray(obj)) {
// use object if already an array
ary = obj;
} else if (obj && typeof obj.length == 'number') {
// convert nodeList to array
for (var i = 0; i < obj.length; i++) {
ary.push(obj[i]);
}
} else {
// array of single index
ary.push(obj);
}
return ary;
};
// ----- removeFrom ----- //
utils.removeFrom = function (ary, obj) {
var index = ary.indexOf(obj);
if (index != -1) {
ary.splice(index, 1);
}
};
// ----- getParent ----- //
utils.getParent = function (elem, selector) {
while (elem != document.body) {
elem = elem.parentNode;
if (matchesSelector(elem, selector)) {
return elem;
}
}
};
// ----- getQueryElement ----- //
// use element as selector string
utils.getQueryElement = function (elem) {
if (typeof elem == 'string') {
return document.querySelector(elem);
}
return elem;
};
// ----- handleEvent ----- //
// enable .ontype to trigger from .addEventListener( elem, 'type' )
utils.handleEvent = function (event) {
var method = 'on' + event.type;
if (this[method]) {
this[method](event);
}
};
// ----- filterFindElements ----- //
utils.filterFindElements = function (elems, selector) {
// make array of elems
elems = utils.makeArray(elems);
var ffElems = [];
elems.forEach(function (elem) {
// check that elem is an actual element
if (!(elem instanceof HTMLElement)) {
return;
}
// add elem if no selector
if (!selector) {
ffElems.push(elem);
return;
}
// filter & find items if we have a selector
// filter
if (matchesSelector(elem, selector)) {
ffElems.push(elem);
}
// find children
var childElems = elem.querySelectorAll(selector);
// concat childElems to filterFound array
for (var i = 0; i < childElems.length; i++) {
ffElems.push(childElems[i]);
}
});
return ffElems;
};
// ----- debounceMethod ----- //
utils.debounceMethod = function (_class, methodName, threshold) {
// original method
var method = _class.prototype[methodName];
var timeoutName = methodName + 'Timeout';
_class.prototype[methodName] = function () {
var timeout = this[timeoutName];
if (timeout) {
clearTimeout(timeout);
}
var args = arguments;
var _this = this;
this[timeoutName] = setTimeout(function () {
method.apply(_this, args);
delete _this[timeoutName];
}, threshold || 100);
};
};
// ----- docReady ----- //
utils.docReady = function (callback) {
var readyState = document.readyState;
if (readyState == 'complete' || readyState == 'interactive') {
// do async to allow for other scripts to run. metafizzy/flickity#441
setTimeout(callback);
} else {
document.addEventListener('DOMContentLoaded', callback);
}
};
// ----- htmlInit ----- //
// http://jamesroberts.name/blog/2010/02/22/string-functions-for-javascript-trim-to-camel-case-to-dashed-and-to-underscore/
utils.toDashed = function (str) {
return str.replace(/(.)([A-Z])/g, function (match, $1, $2) {
return $1 + '-' + $2;
}).toLowerCase();
};
var console = window.console;
/**
* allow user to initialize classes via [data-namespace] or .js-namespace class
* htmlInit( Widget, 'widgetName' )
* options are parsed from data-namespace-options
*/
utils.htmlInit = function (WidgetClass, namespace) {
utils.docReady(function () {
var dashedNamespace = utils.toDashed(namespace);
var dataAttr = 'data-' + dashedNamespace;
var dataAttrElems = document.querySelectorAll('[' + dataAttr + ']');
var jsDashElems = document.querySelectorAll('.js-' + dashedNamespace);
var elems = utils.makeArray(dataAttrElems).concat(utils.makeArray(jsDashElems));
var dataOptionsAttr = dataAttr + '-options';
var jQuery = window.jQuery;
elems.forEach(function (elem) {
var attr = elem.getAttribute(dataAttr) || elem.getAttribute(dataOptionsAttr);
var options;
try {
options = attr && JSON.parse(attr);
} catch (error) {
// log error, do not initialize
if (console) {
console.error('Error parsing ' + dataAttr + ' on ' + elem.className + ': ' + error);
}
return;
}
// initialize
var instance = new WidgetClass(elem, options);
// make available via $().data('namespace')
if (jQuery) {
jQuery.data(elem, namespace, instance);
}
});
});
};
// ----- ----- //
return utils;
});
// Flickity.Cell
(function (window, factory) {
// universal module definition
/* jshint strict: false */
if (typeof define == 'function' && define.amd) {
// AMD
define('flickity/js/cell', ['get-size/get-size'], function (getSize) {
return factory(window, getSize);
});
} else if (typeof module == 'object' && module.exports) {
// CommonJS
module.exports = factory(window, require('get-size'));
} else {
// browser global
window.Flickity = window.Flickity || {};
window.Flickity.Cell = factory(window, window.getSize);
}
})(window, function factory(window, getSize) {
function Cell(elem, parent) {
this.element = elem;
this.parent = parent;
this.create();
}
var proto = Cell.prototype;
proto.create = function () {
this.element.style.position = 'absolute';
this.x = 0;
this.shift = 0;
};
proto.destroy = function () {
// reset style
this.element.style.position = '';
var side = this.parent.originSide;
this.element.style[side] = '';
};
proto.getSize = function () {
this.size = getSize(this.element);
};
proto.setPosition = function (x) {
this.x = x;
this.updateTarget();
this.renderPosition(x);
};
// setDefaultTarget v1 method, backwards compatibility, remove in v3
proto.updateTarget = proto.setDefaultTarget = function () {
var marginProperty = this.parent.originSide == 'left' ? 'marginLeft' : 'marginRight';
this.target = this.x + this.size[marginProperty] + this.size.width * this.parent.cellAlign;
};
proto.renderPosition = function (x) {
// render position of cell with in slider
var side = this.parent.originSide;
this.element.style[side] = this.parent.getPositionValue(x);
};
/**
* @param {Integer} factor - 0, 1, or -1
**/
proto.wrapShift = function (shift) {
this.shift = shift;
this.renderPosition(this.x + this.parent.slideableWidth * shift);
};
proto.remove = function () {
this.element.parentNode.removeChild(this.element);
};
return Cell;
});
// slide
(function (window, factory) {
// universal module definition
/* jshint strict: false */
if (typeof define == 'function' && define.amd) {
// AMD
define('flickity/js/slide', factory);
} else if (typeof module == 'object' && module.exports) {
// CommonJS
module.exports = factory();
} else {
// browser global
window.Flickity = window.Flickity || {};
window.Flickity.Slide = factory();
}
})(window, function factory() {
'use strict';
function Slide(parent) {
this.parent = parent;
this.isOriginLeft = parent.originSide == 'left';
this.cells = [];
this.outerWidth = 0;
this.height = 0;
}
var proto = Slide.prototype;
proto.addCell = function (cell) {
this.cells.push(cell);
this.outerWidth += cell.size.outerWidth;
this.height = Math.max(cell.size.outerHeight, this.height);
// first cell stuff
if (this.cells.length == 1) {
this.x = cell.x; // x comes from first cell
var beginMargin = this.isOriginLeft ? 'marginLeft' : 'marginRight';
this.firstMargin = cell.size[beginMargin];
}
};
proto.updateTarget = function () {
var endMargin = this.isOriginLeft ? 'marginRight' : 'marginLeft';
var lastCell = this.getLastCell();
var lastMargin = lastCell ? lastCell.size[endMargin] : 0;
var slideWidth = this.outerWidth - (this.firstMargin + lastMargin);
this.target = this.x + this.firstMargin + slideWidth * this.parent.cellAlign;
};
proto.getLastCell = function () {
return this.cells[this.cells.length - 1];
};
proto.select = function () {
this.changeSelectedClass('add');
};
proto.unselect = function () {
this.changeSelectedClass('remove');
};
proto.changeSelectedClass = function (method) {
this.cells.forEach(function (cell) {
cell.element.classList[method]('is-selected');
});
};
proto.getCellElements = function () {
return this.cells.map(function (cell) {
return cell.element;
});
};
return Slide;
});
// animate
(function (window, factory) {
// universal module definition
/* jshint strict: false */
if (typeof define == 'function' && define.amd) {
// AMD
define('flickity/js/animate', ['fizzy-ui-utils/utils'], function (utils) {
return factory(window, utils);
});
} else if (typeof module == 'object' && module.exports) {
// CommonJS
module.exports = factory(window, require('fizzy-ui-utils'));
} else {
// browser global
window.Flickity = window.Flickity || {};
window.Flickity.animatePrototype = factory(window, window.fizzyUIUtils);
}
})(window, function factory(window, utils) {
// -------------------------- requestAnimationFrame -------------------------- //
// get rAF, prefixed, if present
var requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame;
// fallback to setTimeout
var lastTime = 0;
if (!requestAnimationFrame) {
requestAnimationFrame = function (callback) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = setTimeout(callback, timeToCall);
lastTime = currTime + timeToCall;
return id;
};
}
// -------------------------- animate -------------------------- //
var proto = {};
proto.startAnimation = function () {
if (this.isAnimating) {
return;
}
this.isAnimating = true;
this.restingFrames = 0;
this.animate();
};
proto.animate = function () {
this.applyDragForce();
this.applySelectedAttraction();
var previousX = this.x;
this.integratePhysics();
this.positionSlider();
this.settle(previousX);
// animate next frame
if (this.isAnimating) {
var _this = this;
requestAnimationFrame(function animateFrame() {
_this.animate();
});
}
};
var transformProperty = function () {
var style = document.documentElement.style;
if (typeof style.transform == 'string') {
return 'transform';
}
return 'WebkitTransform';
}();
proto.positionSlider = function () {
var x = this.x;
// wrap position around
if (this.options.wrapAround && this.cells.length > 1) {
x = utils.modulo(x, this.slideableWidth);
x = x - this.slideableWidth;
this.shiftWrapCells(x);
}
x = x + this.cursorPosition;
// reverse if right-to-left and using transform
x = this.options.rightToLeft && transformProperty ? -x : x;
var value = this.getPositionValue(x);
// use 3D tranforms for hardware acceleration on iOS
// but use 2D when settled, for better font-rendering
this.slider.style[transformProperty] = this.isAnimating ? 'translate3d(' + value + ',0,0)' : 'translateX(' + value + ')';
// scroll event
var firstSlide = this.slides[0];
if (firstSlide) {
var positionX = -this.x - firstSlide.target;
var progress = positionX / this.slidesWidth;
this.dispatchEvent('scroll', null, [progress, positionX]);
}
};
proto.positionSliderAtSelected = function () {
if (!this.cells.length) {
return;
}
this.x = -this.selectedSlide.target;
this.positionSlider();
};
proto.getPositionValue = function (position) {
if (this.options.percentPosition) {
// percent position, round to 2 digits, like 12.34%
return Math.round(position / this.size.innerWidth * 10000) * 0.01 + '%';
} else {
// pixel positioning
return Math.round(position) + 'px';
}
};
proto.settle = function (previousX) {
// keep track of frames where x hasn't moved
if (!this.isPointerDown && Math.round(this.x * 100) == Math.round(previousX * 100)) {
this.restingFrames++;
}
// stop animating if resting for 3 or more frames
if (this.restingFrames > 2) {
this.isAnimating = false;
delete this.isFreeScrolling;
// render position with translateX when settled
this.positionSlider();
this.dispatchEvent('settle');
}
};
proto.shiftWrapCells = function (x) {
// shift before cells
var beforeGap = this.cursorPosition + x;
this._shiftCells(this.beforeShiftCells, beforeGap, -1);
// shift after cells
var afterGap = this.size.innerWidth - (x + this.slideableWidth + this.cursorPosition);
this._shiftCells(this.afterShiftCells, afterGap, 1);
};
proto._shiftCells = function (cells, gap, shift) {
for (var i = 0; i < cells.length; i++) {
var cell = cells[i];
var cellShift = gap > 0 ? shift : 0;
cell.wrapShift(cellShift);
gap -= cell.size.outerWidth;
}
};
proto._unshiftCells = function (cells) {
if (!cells || !cells.length) {
return;
}
for (var i = 0; i < cells.length; i++) {
cells[i].wrapShift(0);
}
};
// -------------------------- physics -------------------------- //
proto.integratePhysics = function () {
this.x += this.velocity;
this.velocity *= this.getFrictionFactor();
};
proto.applyForce = function (force) {
this.velocity += force;
};
proto.getFrictionFactor = function () {
return 1 - this.options[this.isFreeScrolling ? 'freeScrollFriction' : 'friction'];
};
proto.getRestingPosition = function () {
// my thanks to Steven Wittens, who simplified this math greatly
return this.x + this.velocity / (1 - this.getFrictionFactor());
};
proto.applyDragForce = function () {
if (!this.isPointerDown) {
return;
}
// change the position to drag position by applying force
var dragVelocity = this.dragX - this.x;
var dragForce = dragVelocity - this.velocity;
this.applyForce(dragForce);
};
proto.applySelectedAttraction = function () {
// do not attract if pointer down or no cells
if (this.isPointerDown || this.isFreeScrolling || !this.cells.length) {
return;
}
var distance = this.selectedSlide.target * -1 - this.x;
var force = distance * this.options.selectedAttraction;
this.applyForce(force);
};
return proto;
});
// Flickity main
(function (window, factory) {
// universal module definition
/* jshint strict: false */
if (typeof define == 'function' && define.amd) {
// AMD
define('flickity/js/flickity', ['ev-emitter/ev-emitter', 'get-size/get-size', 'fizzy-ui-utils/utils', './cell', './slide', './animate'], function (EvEmitter, getSize, utils, Cell, Slide, animatePrototype) {
return factory(window, EvEmitter, getSize, utils, Cell, Slide, animatePrototype);
});
} else if (typeof module == 'object' && module.exports) {
// CommonJS
module.exports = factory(window, require('ev-emitter'), require('get-size'), require('fizzy-ui-utils'), require('./cell'), require('./slide'), require('./animate'));
} else {
// browser global
var _Flickity = window.Flickity;
window.Flickity = factory(window, window.EvEmitter, window.getSize, window.fizzyUIUtils, _Flickity.Cell, _Flickity.Slide, _Flickity.animatePrototype);
}
})(window, function factory(window, EvEmitter, getSize, utils, Cell, Slide, animatePrototype) {
// vars
var jQuery = window.jQuery;
var getComputedStyle = window.getComputedStyle;
var console = window.console;
function moveElements(elems, toElem) {
elems = utils.makeArray(elems);
while (elems.length) {
toElem.appendChild(elems.shift());
}
}
// -------------------------- Flickity -------------------------- //
// globally unique identifiers
var GUID = 0;
// internal store of all Flickity intances
var instances = {};
function Flickity(element, options) {
var queryElement = utils.getQueryElement(element);
if (!queryElement) {
if (console) {
console.error('Bad element for Flickity: ' + (queryElement || element));
}
return;
}
this.element = queryElement;
// do not initialize twice on same element
if (this.element.flickityGUID) {
var instance = instances[this.element.flickityGUID];
instance.option(options);
return instance;
}
// add jQuery
if (jQuery) {
this.$element = jQuery(this.element);
}
// options
this.options = utils.extend({}, this.constructor.defaults);
this.option(options);
// kick things off
this._create();
}
Flickity.defaults = {
accessibility: true,
// adaptiveHeight: false,
cellAlign: 'center',
// cellSelector: undefined,
// contain: false,
freeScrollFriction: 0.075, // friction when free-scrolling
friction: 0.28, // friction when selecting
namespaceJQueryEvents: true,
// initialIndex: 0,
percentPosition: true,
resize: true,
selectedAttraction: 0.025,
setGallerySize: true
// watchCSS: false,
// wrapAround: false
};
// hash of methods triggered on _create()
Flickity.createMethods = [];
var proto = Flickity.prototype;
// inherit EventEmitter
utils.extend(proto, EvEmitter.prototype);
proto._create = function () {
// add id for Flickity.data
var id = this.guid = ++GUID;
this.element.flickityGUID = id; // expando
instances[id] = this; // associate via id
// initial properties
this.selectedIndex = 0;
// how many frames slider has been in same position
this.restingFrames = 0;
// initial physics properties
this.x = 0;
this.velocity = 0;
this.originSide = this.options.rightToLeft ? 'right' : 'left';
// create viewport & slider
this.viewport = document.createElement('div');
this.viewport.className = 'flickity-viewport';
this._createSlider();
if (this.options.resize || this.options.watchCSS) {
window.addEventListener('resize', this);
}
Flickity.createMethods.forEach(function (method) {
this[method]();
}, this);
if (this.options.watchCSS) {
this.watchCSS();
} else {
this.activate();
}
};
/**
* set options
* @param {Object} opts
*/
proto.option = function (opts) {
utils.extend(this.options, opts);
};
proto.activate = function () {
if (this.isActive) {
return;
}
this.isActive = true;
this.element.classList.add('flickity-enabled');
if (this.options.rightToLeft) {
this.element.classList.add('flickity-rtl');
}
this.getSize();
// move initial cell elements so they can be loaded as cells
var cellElems = this._filterFindCellElements(this.element.children);
moveElements(cellElems, this.slider);
this.viewport.appendChild(this.slider);
this.element.appendChild(this.viewport);
// get cells from children
this.reloadCells();
if (this.options.accessibility) {
// allow element to focusable
this.element.tabIndex = 0;
// listen for key presses
this.element.addEventListener('keydown', this);
}
this.emitEvent('activate');
var index;
var initialIndex = this.options.initialIndex;
if (this.isInitActivated) {
index = this.selectedIndex;
} else if (initialIndex !== undefined) {
index = this.cells[initialIndex] ? initialIndex : 0;
} else {
index = 0;
}
// select instantly
this.select(index, false, true);
// flag for initial activation, for using initialIndex
this.isInitActivated = true;
};
// slider positions the cells
proto._createSlider = function () {
// slider element does all the positioning
var slider = document.createElement('div');
slider.className = 'flickity-slider';
slider.style[this.originSide] = 0;
this.slider = slider;
};
proto._filterFindCellElements = function (elems) {
return utils.filterFindElements(elems, this.options.cellSelector);
};
// goes through all children
proto.reloadCells = function () {
// collection of item elements
this.cells = this._makeCells(this.slider.children);
this.positionCells();
this._getWrapShiftCells();
this.setGallerySize();
};
/**
* turn elements into Flickity.Cells
* @param {Array or NodeList or HTMLElement} elems
* @returns {Array} items - collection of new Flickity Cells
*/
proto._makeCells = function (elems) {
var cellElems = this._filterFindCellElements(elems);
// create new Flickity for collection
var cells = cellElems.map(function (cellElem) {
return new Cell(cellElem, this);
}, this);
return cells;
};
proto.getLastCell = function () {
return this.cells[this.cells.length - 1];
};
proto.getLastSlide = function () {
return this.slides[this.slides.length - 1];
};
// positions all cells
proto.positionCells = function () {
// size all cells
this._sizeCells(this.cells);
// position all cells
this._positionCells(0);
};
/**
* position certain cells
* @param {Integer} index - which cell to start with
*/
proto._positionCells = function (index) {
index = index || 0;
// also measure maxCellHeight
// start 0 if positioning all cells
this.maxCellHeight = index ? this.maxCellHeight || 0 : 0;
var cellX = 0;
// get cellX
if (index > 0) {
var startCell = this.cells[index - 1];
cellX = startCell.x + startCell.size.outerWidth;
}
var len = this.cells.length;
for (var i = index; i < len; i++) {
var cell = this.cells[i];
cell.setPosition(cellX);
cellX += cell.size.outerWidth;
this.maxCellHeight = Math.max(cell.size.outerHeight, this.maxCellHeight);
}
// keep track of cellX for wrap-around
this.slideableWidth = cellX;
// slides
this.updateSlides();
// contain slides target
this._containSlides();
// update slidesWidth
this.slidesWidth = len ? this.getLastSlide().target - this.slides[0].target : 0;
};
/**
* cell.getSize() on multiple cells
* @param {Array} cells
*/
proto._sizeCells = function (cells) {
cells.forEach(function (cell) {
cell.getSize();
});
};
// -------------------------- -------------------------- //
proto.updateSlides = function () {
this.slides = [];
if (!this.cells.length) {
return;
}
var slide = new Slide(this);
this.slides.push(slide);
var isOriginLeft = this.originSide == 'left';
var nextMargin = isOriginLeft ? 'marginRight' : 'marginLeft';
var canCellFit = this._getCanCellFit();
this.cells.forEach(function (cell, i) {
// just add cell if first cell in slide
if (!slide.cells.length) {
slide.addCell(cell);
return;
}
var slideWidth = slide.outerWidth - slide.firstMargin + (cell.size.outerWidth - cell.size[nextMargin]);
if (canCellFit.call(this, i, slideWidth)) {
slide.addCell(cell);
} else {
// doesn't fit, new slide
slide.updateTarget();
slide = new Slide(this);
this.slides.push(slide);
slide.addCell(cell);
}
}, this);
// last slide
slide.updateTarget();
// update .selectedSlide
this.updateSelectedSlide();
};
proto._getCanCellFit = function () {
var groupCells = this.options.groupCells;
if (!groupCells) {
return function () {
return false;
};
} else if (typeof groupCells == 'number') {
// group by number. 3 -> [0,1,2], [3,4,5], ...
var number = parseInt(groupCells, 10);
return function (i) {
return i % number !== 0;
};
}
// default, group by width of slide
// parse '75%
var percentMatch = typeof groupCells == 'string' && groupCells.match(/^(\d+)%$/);
var percent = percentMatch ? parseInt(percentMatch[1], 10) / 100 : 1;
return function (i, slideWidth) {
return slideWidth <= (this.size.innerWidth + 1) * percent;
};
};
// alias _init for jQuery plugin .flickity()
proto._init = proto.reposition = function () {
this.positionCells();
this.positionSliderAtSelected();
};
proto.getSize = function () {
this.size = getSize(this.element);
this.setCellAlign();
this.cursorPosition = this.size.innerWidth * this.cellAlign;
};
var cellAlignShorthands = {
// cell align, then based on origin side
center: {
left: 0.5,
right: 0.5
},
left: {
left: 0,
right: 1
},
right: {
right: 0,
left: 1
}
};
proto.setCellAlign = function () {
var shorthand = cellAlignShorthands[this.options.cellAlign];
this.cellAlign = shorthand ? shorthand[this.originSide] : this.options.cellAlign;
};
proto.setGallerySize = function () {
if (this.options.setGallerySize) {
var height = this.options.adaptiveHeight && this.selectedSlide ? this.selectedSlide.height : this.maxCellHeight;
this.viewport.style.height = height + 'px';
}
};
proto._getWrapShiftCells = function () {
// only for wrap-around
if (!this.options.wrapAround) {
return;
}
// unshift previous cells
this._unshiftCells(this.beforeShiftCells);
this._unshiftCells(this.afterShiftCells);
// get before cells
// initial gap
var gapX = this.cursorPosition;
var cellIndex = this.cells.length - 1;
this.beforeShiftCells = this._getGapCells(gapX, cellIndex, -1);
// get after cells
// ending gap between last cell and end of gallery viewport
gapX = this.size.innerWidth - this.cursorPosition;
// start cloning at first cell, working forwards
this.afterShiftCells = this._getGapCells(gapX, 0, 1);
};
proto._getGapCells = function (gapX, cellIndex, increment) {
// keep adding cells until the cover the initial gap
var cells = [];
while (gapX > 0) {
var cell = this.cells[cellIndex];
if (!cell) {
break;
}
cells.push(cell);
cellIndex += increment;
gapX -= cell.size.outerWidth;
}
return cells;
};
// ----- contain ----- //
// contain cell targets so no excess sliding
proto._containSlides = function () {
if (!this.options.contain || this.options.wrapAround || !this.cells.length) {
return;
}
var isRightToLeft = this.options.rightToLeft;
var beginMargin = isRightToLeft ? 'marginRight' : 'marginLeft';
var endMargin = isRightToLeft ? 'marginLeft' : 'marginRight';
var contentWidth = this.slideableWidth - this.getLastCell().size[endMargin];
// content is less than gallery size
var isContentSmaller = contentWidth < this.size.innerWidth;
// bounds
var beginBound = this.cursorPosition + this.cells[0].size[beginMargin];
var endBound = contentWidth - this.size.innerWidth * (1 - this.cellAlign);
// contain each cell target
this.slides.forEach(function (slide) {
if (isContentSmaller) {
// all cells fit inside gallery
slide.target = contentWidth * this.cellAlign;
} else {
// contain to bounds
slide.target = Math.max(slide.target, beginBound);
slide.target = Math.min(slide.target, endBound);
}
}, this);
};
// ----- ----- //
/**
* emits events via eventEmitter 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) {
var emitArgs = event ? [event].concat(args) : args;
this.emitEvent(type, emitArgs);
if (jQuery && this.$element) {
// default trigger with type if no event
type += this.options.namespaceJQueryEvents ? '.flickity' : '';
var $event = type;
if (event) {
// create jQuery event
var jQEvent = jQuery.Event(event);
jQEvent.type = type;
$event = jQEvent;
}
this.$element.trigger($event, args);
}
};
// -------------------------- select -------------------------- //
/**
* @param {Integer} index - index of the slide
* @param {Boolean} isWrap - will wrap-around to last/first if at the end
* @param {Boolean} isInstant - will immediately set position at selected cell
*/
proto.select = function (index, isWrap, isInstant) {
if (!this.isActive) {
return;
}
index = parseInt(index, 10);
this._wrapSelect(index);
if (this.options.wrapAround || isWrap) {
index = utils.modulo(index, this.slides.length);
}
// bail if invalid index
if (!this.slides[index]) {
return;
}
this.selectedIndex = index;
this.updateSelectedSlide();
if (isInstant) {
this.positionSliderAtSelected();
} else {
this.startAnimation();
}
if (this.options.adaptiveHeight) {
this.setGallerySize();
}
this.dispatchEvent('select');
// old v1 event name, remove in v3
this.dispatchEvent('cellSelect');
};
// wraps position for wrapAround, to move to closest slide. #113
proto._wrapSelect = function (index) {
var len = this.slides.length;
var isWrapping = this.options.wrapAround && len > 1;
if (!isWrapping) {
return index;
}
var wrapIndex = utils.modulo(index, len);
// go to shortest
var delta = Math.abs(wrapIndex - this.selectedIndex);
var backWrapDelta = Math.abs(wrapIndex + len - this.selectedIndex);
var forewardWrapDelta = Math.abs(wrapIndex - len - this.selectedIndex);
if (!this.isDragSelect && backWrapDelta < delta) {
index += len;
} else if (!this.isDragSelect && forewardWrapDelta < delta) {
index -= len;
}
// wrap position so slider is within normal area
if (index < 0) {
this.x -= this.slideableWidth;
} else if (index >= len) {
this.x += this.slideableWidth;
}
};
proto.previous = function (isWrap, isInstant) {
this.select(this.selectedIndex - 1, isWrap, isInstant);
};
proto.next = function (isWrap, isInstant) {
this.select(this.selectedIndex + 1, isWrap, isInstant);
};
proto.updateSelectedSlide = function () {
var slide = this.slides[this.selectedIndex];
// selectedIndex could be outside of slides, if triggered before resize()
if (!slide) {
return;
}
// unselect previous selected slide
this.unselectSelectedSlide();
// update new selected slide
this.selectedSlide = slide;
slide.select();
this.selectedCells = slide.cells;
this.selectedElements = slide.getCellElements();
// HACK: selectedCell & selectedElement is first cell in slide, backwards compatibility
// Remove in v3?
this.selectedCell = slide.cells[0];
this.selectedElement = this.selectedElements[0];
};
proto.unselectSelectedSlide = function () {
if (this.selectedSlide) {
this.selectedSlide.unselect();
}
};
/**
* select slide from number or cell element
* @param {Element or Number} elem
*/
proto.selectCell = function (value, isWrap, isInstant) {
// get cell
var cell;
if (typeof value == 'number') {
cell = this.cells[value];
} else {
// use string as selector
if (typeof value == 'string') {
value = this.element.querySelector(value);
}
// get cell from element
cell = this.getCell(value);
}
// select slide that has cell
for (var i = 0; cell && i < this.slides.length; i++) {
var slide = this.slides[i];
var index = slide.cells.indexOf(cell);
if (index != -1) {
this.select(i, isWrap, isInstant);
return;
}
}
};
// -------------------------- get cells -------------------------- //
/**
* get Flickity.Cell, given an Element
* @param {Element} elem
* @returns {Flickity.Cell} item
*/
proto.getCell = function (elem) {
// loop through cells to get the one that matches
for (var i = 0; i < this.cells.length; i++) {
var cell = this.cells[i];
if (cell.element == elem) {
return cell;
}
}
};
/**
* get collection of Flickity.Cells, given Elements
* @param {Element, Array, NodeList} elems
* @returns {Array} cells - Flickity.Cells
*/
proto.getCells = function (elems) {
elems = utils.makeArray(elems);
var cells = [];
elems.forEach(function (elem) {
var cell = this.getCell(elem);
if (cell) {
cells.push(cell);
}
}, this);
return cells;
};
/**
* get cell elements
* @returns {Array} cellElems
*/
proto.getCellElements = function () {
return this.cells.map(function (cell) {
return cell.element;
});
};
/**
* get parent cell from an element
* @param {Element} elem
* @returns {Flickit.Cell} cell
*/
proto.getParentCell = function (elem) {
// first check if elem is cell
var cell = this.getCell(elem);
if (cell) {
return cell;
}
// try to get parent cell elem
elem = utils.getParent(elem, '.flickity-slider > *');
return this.getCell(elem);
};
/**
* get cells adjacent to a slide
* @param {Integer} adjCount - number of adjacent slides
* @param {Integer} index - index of slide to start
* @returns {Array} cells - array of Flickity.Cells
*/
proto.getAdjacentCellElements = function (adjCount, index) {
if (!adjCount) {
return this.selectedSlide.getCellElements();
}
index = index === undefined ? this.selectedIndex : index;
var len = this.slides.length;
if (1 + adjCount * 2 >= len) {
return this.getCellElements();
}
var cellElems = [];
for (var i = index - adjCount; i <= index + adjCount; i++) {
var slideIndex = this.options.wrapAround ? utils.modulo(i, len) : i;
var slide = this.slides[slideIndex];
if (slide) {
cellElems = cellElems.concat(slide.getCellElements());
}
}
return cellElems;
};
// -------------------------- events -------------------------- //
proto.uiChange = function () {
this.emitEvent('uiChange');
};
proto.childUIPointerDown = function (event) {
this.emitEvent('childUIPointerDown', [event]);
};
// ----- resize ----- //
proto.onresize = function () {
this.watchCSS();
this.resize();
};
utils.debounceMethod(Flickity, 'onresize', 150);
proto.resize = function () {
if (!this.isActive) {
return;
}
this.getSize();
// wrap values
if (this.options.wrapAround) {
this.x = utils.modulo(this.x, this.slideableWidth);
}
this.positionCells();
this._getWrapShiftCells();
this.setGallerySize();
this.emitEvent('resize');
// update selected index for group slides, instant
// TODO: position can be lost between groups of various numbers
var selectedElement = this.selectedElements && this.selectedElements[0];
this.select