diff --git a/ajax/libs/Sly/1.2.1/sly.js b/ajax/libs/Sly/1.2.1/sly.js new file mode 100644 index 000000000..74ab12370 --- /dev/null +++ b/ajax/libs/Sly/1.2.1/sly.js @@ -0,0 +1,2068 @@ +/*! + * sly 1.2.1 - 12th Nov 2013 + * https://github.com/Darsain/sly + * + * Licensed under the MIT license. + * http://opensource.org/licenses/MIT + */ +;(function ($, w, undefined) { + 'use strict'; + + var pluginName = 'sly'; + var className = 'Sly'; + var namespace = pluginName; + + // Local WindowAnimationTiming interface + var cAF = w.cancelAnimationFrame || w.cancelRequestAnimationFrame; + var rAF = w.requestAnimationFrame; + + // Support indicators + var transform, gpuAcceleration; + + // Other global values + var $doc = $(document); + var dragInitEvents = 'touchstart.' + namespace + ' mousedown.' + namespace; + var dragMouseEvents = 'mousemove.' + namespace + ' mouseup.' + namespace; + var dragTouchEvents = 'touchmove.' + namespace + ' touchend.' + namespace; + var clickEvent = 'click.' + namespace; + var mouseDownEvent = 'mousedown.' + namespace; + var interactiveElements = ['INPUT', 'SELECT', 'BUTTON', 'TEXTAREA']; + var tmpArray = []; + var time; + + /** + * Sly. + * + * @class + * + * @param {Element} frame DOM element of sly container. + * @param {Object} options Object with options. + * @param {Object} callbackMap Callbacks map. + */ + function Sly(frame, options, callbackMap) { + // Extend options + var o = $.extend({}, Sly.defaults, options); + + // Private variables + var self = this; + var parallax = isNumber(frame); + + // Frame + var $frame = $(frame); + var $slidee = $frame.children().eq(0); + var frameSize = 0; + var slideeSize = 0; + var pos = { + start: 0, + center: 0, + end: 0, + cur: 0, + dest: 0 + }; + + // Scrollbar + var $sb = $(o.scrollBar).eq(0); + var $handle = $sb.children().eq(0); + var sbSize = 0; + var handleSize = 0; + var hPos = { + start: 0, + end: 0, + cur: 0 + }; + + // Pagesbar + var $pb = $(o.pagesBar); + var $pages = 0; + var pages = []; + + // Items + var $items = 0; + var items = []; + var rel = { + firstItem: 0, + lastItem: 0, + centerItem: 0, + activeItem: -1, + activePage: 0 + }; + + // Navigation type booleans + var basicNav = o.itemNav === 'basic'; + var forceCenteredNav = o.itemNav === 'forceCentered'; + var centeredNav = o.itemNav === 'centered' || forceCenteredNav; + var itemNav = !parallax && (basicNav || centeredNav || forceCenteredNav); + + // Miscellaneous + var $scrollSource = o.scrollSource ? $(o.scrollSource) : $frame; + var $dragSource = o.dragSource ? $(o.dragSource) : $frame; + var $forwardButton = $(o.forward); + var $backwardButton = $(o.backward); + var $prevButton = $(o.prev); + var $nextButton = $(o.next); + var $prevPageButton = $(o.prevPage); + var $nextPageButton = $(o.nextPage); + var callbacks = {}; + var last = {}; + var animation = {}; + var move = {}; + var dragging = { + released: 1 + }; + var scrolling = { + last: 0, + delta: 0, + resetTime: 200 + }; + var renderID = 0; + var historyID = 0; + var cycleID = 0; + var continuousID = 0; + var i, l; + + // Normalizing frame + if (!parallax) { + frame = $frame[0]; + } + + // Expose properties + self.initialized = 0; + self.frame = frame; + self.slidee = $slidee[0]; + self.pos = pos; + self.rel = rel; + self.items = items; + self.pages = pages; + self.isPaused = 0; + self.options = o; + self.dragging = dragging; + + /** + * (Re)Loading function. + * + * Populate arrays, set sizes, bind events, ... + * + * @return {Void} + */ + function load() { + // Local variables + var lastItemsCount = 0; + var lastPagesCount = pages.length; + + // Save old position + pos.old = $.extend({}, pos); + + // Reset global variables + frameSize = parallax ? 0 : $frame[o.horizontal ? 'width' : 'height'](); + sbSize = $sb[o.horizontal ? 'width' : 'height'](); + slideeSize = parallax ? frame : $slidee[o.horizontal ? 'outerWidth' : 'outerHeight'](); + pages.length = 0; + + // Set position limits & relatives + pos.start = 0; + pos.end = Math.max(slideeSize - frameSize, 0); + + // Sizes & offsets for item based navigations + if (itemNav) { + // Save the number of current items + lastItemsCount = items.length; + + // Reset itemNav related variables + $items = $slidee.children(o.itemSelector); + items.length = 0; + + // Needed variables + var paddingStart = getPx($slidee, o.horizontal ? 'paddingLeft' : 'paddingTop'); + var paddingEnd = getPx($slidee, o.horizontal ? 'paddingRight' : 'paddingBottom'); + var borderBox = $($items).css('boxSizing') === 'border-box'; + var areFloated = $items.css('float') !== 'none'; + var ignoredMargin = 0; + var lastItemIndex = $items.length - 1; + var lastItem; + + // Reset slideeSize + slideeSize = 0; + + // Iterate through items + $items.each(function (i, element) { + // Item + var $item = $(element); + var itemSize = $item[o.horizontal ? 'outerWidth' : 'outerHeight'](); + var itemMarginStart = getPx($item, o.horizontal ? 'marginLeft' : 'marginTop'); + var itemMarginEnd = getPx($item, o.horizontal ? 'marginRight' : 'marginBottom'); + var itemSizeFull = itemSize + itemMarginStart + itemMarginEnd; + var singleSpaced = !itemMarginStart || !itemMarginEnd; + var item = {}; + item.el = element; + item.size = singleSpaced ? itemSize : itemSizeFull; + item.half = item.size / 2; + item.start = slideeSize + (singleSpaced ? itemMarginStart : 0); + item.center = item.start - Math.round(frameSize / 2 - item.size / 2); + item.end = item.start - frameSize + item.size; + + // Account for slidee padding + if (!i) { + slideeSize += paddingStart; + } + + // Increment slidee size for size of the active element + slideeSize += itemSizeFull; + + // Try to account for vertical margin collapsing in vertical mode + // It's not bulletproof, but should work in 99% of cases + if (!o.horizontal && !areFloated) { + // Subtract smaller margin, but only when top margin is not 0, and this is not the first element + if (itemMarginEnd && itemMarginStart && i > 0) { + slideeSize -= Math.min(itemMarginStart, itemMarginEnd); + } + } + + // Things to be done on last item + if (i === lastItemIndex) { + item.end += paddingEnd; + slideeSize += paddingEnd; + ignoredMargin = singleSpaced ? itemMarginEnd : 0; + } + + // Add item object to items array + items.push(item); + lastItem = item; + }); + + // Resize SLIDEE to fit all items + $slidee[0].style[o.horizontal ? 'width' : 'height'] = (borderBox ? slideeSize: slideeSize - paddingStart - paddingEnd) + 'px'; + + // Adjust internal SLIDEE size for last margin + slideeSize -= ignoredMargin; + + // Set limits + if (items.length) { + pos.start = items[0][forceCenteredNav ? 'center' : 'start']; + pos.end = forceCenteredNav ? lastItem.center : frameSize < slideeSize ? lastItem.end : pos.start; + } else { + pos.start = pos.end = 0; + } + } + + // Calculate SLIDEE center position + pos.center = Math.round(pos.end / 2 + pos.start / 2); + + // Update relative positions + updateRelatives(); + + // Scrollbar + if ($handle.length && sbSize > 0) { + // Stretch scrollbar handle to represent the visible area + if (o.dynamicHandle) { + handleSize = pos.start === pos.end ? sbSize : Math.round(sbSize * frameSize / slideeSize); + handleSize = within(handleSize, o.minHandleSize, sbSize); + $handle[0].style[o.horizontal ? 'width' : 'height'] = handleSize + 'px'; + } else { + handleSize = $handle[o.horizontal ? 'outerWidth' : 'outerHeight'](); + } + + hPos.end = sbSize - handleSize; + + if (!renderID) { + syncScrollbar(); + } + } + + // Pages + if (!parallax && frameSize > 0) { + var tempPagePos = pos.start; + var pagesHtml = ''; + + // Populate pages array + if (itemNav) { + $.each(items, function (i, item) { + if (forceCenteredNav) { + pages.push(item.center); + } else if (item.start + item.size > tempPagePos && tempPagePos <= pos.end) { + tempPagePos = item.start; + pages.push(tempPagePos); + tempPagePos += frameSize; + if (tempPagePos > pos.end && tempPagePos < pos.end + frameSize) { + pages.push(pos.end); + } + } + }); + } else { + while (tempPagePos - frameSize < pos.end) { + pages.push(tempPagePos); + tempPagePos += frameSize; + } + } + + // Pages bar + if ($pb[0] && lastPagesCount !== pages.length) { + for (var i = 0; i < pages.length; i++) { + pagesHtml += o.pageBuilder.call(self, i); + } + $pages = $pb.html(pagesHtml).children(); + $pages.eq(rel.activePage).addClass(o.activeClass); + } + } + + // Extend relative variables object with some useful info + rel.slideeSize = slideeSize; + rel.frameSize = frameSize; + rel.sbSize = sbSize; + rel.handleSize = handleSize; + + // Activate requested position + if (itemNav) { + if (!self.initialized) { + activate(o.startAt); + self[centeredNav ? 'toCenter' : 'toStart'](o.startAt); + } else if (rel.activeItem >= items.length || lastItemsCount === 0 && items.length > 0) { + // Activate last item if previous active has been removed, or first item + // when there were no items before, and new got appended. + activate(rel.activeItem >= items.length ? items.length - 1 : 0, !lastItemsCount); + } + // Fix possible overflowing + slideTo(centeredNav && items.length ? items[rel.activeItem].center : within(pos.dest, pos.start, pos.end)); + } else { + if (!self.initialized) { + slideTo(o.startAt, 1); + } else { + // Fix possible overflowing + slideTo(within(pos.dest, pos.start, pos.end)); + } + } + + // Trigger load event + trigger('load'); + } + self.reload = load; + + /** + * Animate to a position. + * + * @param {Int} newPos New position. + * @param {Bool} immediate Reposition immediately without an animation. + * @param {Bool} dontAlign Do not align items, use the raw position passed in first argument. + * + * @return {Void} + */ + function slideTo(newPos, immediate, dontAlign) { + // Align items + if (itemNav && dragging.released && !dontAlign) { + var tempRel = getRelatives(newPos); + var isNotBordering = newPos > pos.start && newPos < pos.end; + + if (centeredNav) { + if (isNotBordering) { + newPos = items[tempRel.centerItem].center; + } + if (forceCenteredNav && o.activateMiddle) { + activate(tempRel.centerItem); + } + } else if (isNotBordering) { + newPos = items[tempRel.firstItem].start; + } + } + + // Handle overflowing position limits + if (dragging.init && dragging.slidee && o.elasticBounds) { + if (newPos > pos.end) { + newPos = pos.end + (newPos - pos.end) / 6; + } else if (newPos < pos.start) { + newPos = pos.start + (newPos - pos.start) / 6; + } + } else { + newPos = within(newPos, pos.start, pos.end); + } + + // Update the animation object + animation.start = +new Date(); + animation.time = 0; + animation.from = pos.cur; + animation.to = newPos; + animation.delta = newPos - pos.cur; + animation.tweesing = dragging.tweese || dragging.init && !dragging.slidee; + animation.immediate = !animation.tweesing && (immediate || dragging.init && dragging.slidee || !o.speed); + + // Reset dragging tweesing request + dragging.tweese = 0; + + // Start animation rendering + if (newPos !== pos.dest) { + pos.dest = newPos; + trigger('change'); + if (!renderID) { + render(); + } + } + + // Reset next cycle timeout + resetCycle(); + + // Synchronize states + updateRelatives(); + updateButtonsState(); + syncPagesbar(); + } + + /** + * Render animation frame. + * + * @return {Void} + */ + function render() { + // If first render call, wait for next animationFrame + if (!renderID) { + renderID = rAF(render); + if (dragging.released) { + trigger('moveStart'); + } + return; + } + + // If immediate repositioning is requested, don't animate. + if (animation.immediate) { + pos.cur = animation.to; + } + // Use tweesing for animations without known end point + else if (animation.tweesing) { + animation.tweeseDelta = animation.to - pos.cur; + // Fuck Zeno's paradox + if (Math.abs(animation.tweeseDelta) < 0.1) { + pos.cur = animation.to; + } else { + pos.cur += animation.tweeseDelta * (dragging.released ? o.swingSpeed : o.syncSpeed); + } + } + // Use tweening for basic animations with known end point + else { + animation.time = Math.min(+new Date() - animation.start, o.speed); + pos.cur = animation.from + animation.delta * jQuery.easing[o.easing](animation.time/o.speed, animation.time, 0, 1, o.speed); + } + + // If there is nothing more to render break the rendering loop, otherwise request new animation frame. + if (animation.to === pos.cur) { + pos.cur = animation.to; + dragging.tweese = renderID = 0; + } else { + renderID = rAF(render); + } + + trigger('move'); + + // Update SLIDEE position + if (!parallax) { + if (transform) { + $slidee[0].style[transform] = gpuAcceleration + (o.horizontal ? 'translateX' : 'translateY') + '(' + (-pos.cur) + 'px)'; + } else { + $slidee[0].style[o.horizontal ? 'left' : 'top'] = -Math.round(pos.cur) + 'px'; + } + } + + // When animation reached the end, and dragging is not active, trigger moveEnd + if (!renderID && dragging.released) { + trigger('moveEnd'); + } + + syncScrollbar(); + } + + /** + * Synchronizes scrollbar with the SLIDEE. + * + * @return {Void} + */ + function syncScrollbar() { + if ($handle.length) { + hPos.cur = pos.start === pos.end ? 0 : (((dragging.init && !dragging.slidee) ? pos.dest : pos.cur) - pos.start) / (pos.end - pos.start) * hPos.end; + hPos.cur = within(Math.round(hPos.cur), hPos.start, hPos.end); + if (last.hPos !== hPos.cur) { + last.hPos = hPos.cur; + if (transform) { + $handle[0].style[transform] = gpuAcceleration + (o.horizontal ? 'translateX' : 'translateY') + '(' + hPos.cur + 'px)'; + } else { + $handle[0].style[o.horizontal ? 'left' : 'top'] = hPos.cur + 'px'; + } + } + } + } + + /** + * Synchronizes pagesbar with SLIDEE. + * + * @return {Void} + */ + function syncPagesbar() { + if ($pages[0] && last.page !== rel.activePage) { + last.page = rel.activePage; + $pages.removeClass(o.activeClass).eq(rel.activePage).addClass(o.activeClass); + trigger('activePage', last.page); + } + } + + /** + * Returns the position object. + * + * @param {Mixed} item + * + * @return {Object} + */ + self.getPos = function (item) { + if (itemNav) { + var index = getIndex(item); + return index !== -1 ? items[index] : false; + } else { + var $item = $slidee.find(item).eq(0); + + if ($item[0]) { + var offset = o.horizontal ? $item.offset().left - $slidee.offset().left : $item.offset().top - $slidee.offset().top; + var size = $item[o.horizontal ? 'outerWidth' : 'outerHeight'](); + + return { + start: offset, + center: offset - frameSize / 2 + size / 2, + end: offset - frameSize + size, + size: size + }; + } else { + return false; + } + } + }; + + /** + * Continuous move in a specified direction. + * + * @param {Bool} forward True for forward movement, otherwise it'll go backwards. + * @param {Int} speed Movement speed in pixels per frame. Overrides options.moveBy value. + * + * @return {Void} + */ + self.moveBy = function (speed) { + move.speed = speed; + // If already initiated, or there is nowhere to move, abort + if (dragging.init || !move.speed || pos.cur === (move.speed > 0 ? pos.end : pos.start)) { + return; + } + // Initiate move object + move.lastTime = +new Date(); + move.startPos = pos.cur; + // Set dragging as initiated + continuousInit('button'); + dragging.init = 1; + // Start movement + trigger('moveStart'); + cAF(continuousID); + moveLoop(); + }; + + /** + * Continuous movement loop. + * + * @return {Void} + */ + function moveLoop() { + // If there is nowhere to move anymore, stop + if (!move.speed || pos.cur === (move.speed > 0 ? pos.end : pos.start)) { + self.stop(); + } + // Request new move loop if it hasn't been stopped + continuousID = dragging.init ? rAF(moveLoop) : 0; + // Update move object + move.now = +new Date(); + move.pos = pos.cur + (move.now - move.lastTime) / 1000 * move.speed; + // Slide + slideTo(dragging.init ? move.pos : Math.round(move.pos)); + // Normally, this is triggered in render(), but if there + // is nothing to render, we have to do it manually here. + if (!dragging.init && pos.cur === pos.dest) { + trigger('moveEnd'); + } + // Update times for future iteration + move.lastTime = move.now; + } + + /** + * Stops continuous movement. + * + * @return {Void} + */ + self.stop = function () { + if (dragging.source === 'button') { + dragging.init = 0; + dragging.released = 1; + } + }; + + /** + * Activate previous item. + * + * @return {Void} + */ + self.prev = function () { + self.activate(rel.activeItem - 1); + }; + + /** + * Activate next item. + * + * @return {Void} + */ + self.next = function () { + self.activate(rel.activeItem + 1); + }; + + /** + * Activate previous page. + * + * @return {Void} + */ + self.prevPage = function () { + self.activatePage(rel.activePage - 1); + }; + + /** + * Activate next page. + * + * @return {Void} + */ + self.nextPage = function () { + self.activatePage(rel.activePage + 1); + }; + + /** + * Slide SLIDEE by amount of pixels. + * + * @param {Int} delta Pixels/Items. Positive means forward, negative means backward. + * @param {Bool} immediate Reposition immediately without an animation. + * + * @return {Void} + */ + self.slideBy = function (delta, immediate) { + if (!delta) { + return; + } + if (itemNav) { + self[centeredNav ? 'toCenter' : 'toStart']( + within((centeredNav ? rel.centerItem : rel.firstItem) + o.scrollBy * delta, 0, items.length) + ); + } else { + slideTo(pos.dest + delta, immediate); + } + }; + + /** + * Animate SLIDEE to a specific position. + * + * @param {Int} pos New position. + * @param {Bool} immediate Reposition immediately without an animation. + * + * @return {Void} + */ + self.slideTo = function (pos, immediate) { + slideTo(pos, immediate); + }; + + /** + * Core method for handling `toLocation` methods. + * + * @param {String} location + * @param {Mixed} item + * @param {Bool} immediate + * + * @return {Void} + */ + function to(location, item, immediate) { + // Optional arguments logic + if (type(item) === 'boolean') { + immediate = item; + item = undefined; + } + + if (item === undefined) { + slideTo(pos[location], immediate); + } else { + // You can't align items to sides of the frame + // when centered navigation type is enabled + if (centeredNav && location !== 'center') { + return; + } + + var itemPos = self.getPos(item); + if (itemPos) { + slideTo(itemPos[location], immediate, !centeredNav); + } + } + } + + /** + * Animate element or the whole SLIDEE to the start of the frame. + * + * @param {Mixed} item Item DOM element, or index starting at 0. Omitting will animate SLIDEE. + * @param {Bool} immediate Reposition immediately without an animation. + * + * @return {Void} + */ + self.toStart = function (item, immediate) { + to('start', item, immediate); + }; + + /** + * Animate element or the whole SLIDEE to the end of the frame. + * + * @param {Mixed} item Item DOM element, or index starting at 0. Omitting will animate SLIDEE. + * @param {Bool} immediate Reposition immediately without an animation. + * + * @return {Void} + */ + self.toEnd = function (item, immediate) { + to('end', item, immediate); + }; + + /** + * Animate element or the whole SLIDEE to the center of the frame. + * + * @param {Mixed} item Item DOM element, or index starting at 0. Omitting will animate SLIDEE. + * @param {Bool} immediate Reposition immediately without an animation. + * + * @return {Void} + */ + self.toCenter = function (item, immediate) { + to('center', item, immediate); + }; + + /** + * Get the index of an item in SLIDEE. + * + * @param {Mixed} item Item DOM element. + * + * @return {Int} Item index, or -1 if not found. + */ + function getIndex(item) { + return item != null ? + isNumber(item) ? + item >= 0 && item < items.length ? item : -1 : + $items.index(item) : + -1; + } + // Expose getIndex without lowering the compressibility of it, + // as it is used quite often throughout Sly. + self.getIndex = getIndex; + + /** + * Get index of an item in SLIDEE based on a variety of input types. + * + * @param {Mixed} item DOM element, positive or negative integer. + * + * @return {Int} Item index, or -1 if not found. + */ + function getRelativeIndex(item) { + return getIndex(isNumber(item) && item < 0 ? item + items.length : item); + } + + /** + * Activates an item. + * + * @param {Mixed} item Item DOM element, or index starting at 0. + * + * @return {Mixed} Activated item index or false on fail. + */ + function activate(item, force) { + var index = getIndex(item); + + if (!itemNav || index < 0) { + return false; + } + + // Update classes, last active index, and trigger active event only when there + // has been a change. Otherwise just return the current active index. + if (last.active !== index || force) { + // Update classes + $items.eq(rel.activeItem).removeClass(o.activeClass); + $items.eq(index).addClass(o.activeClass); + + last.active = rel.activeItem = index; + + updateButtonsState(); + trigger('active', index); + } + + return index; + } + + /** + * Activates an item and helps with further navigation when o.smart is enabled. + * + * @param {Mixed} item Item DOM element, or index starting at 0. + * @param {Bool} immediate Whether to reposition immediately in smart navigation. + * + * @return {Void} + */ + self.activate = function (item, immediate) { + var index = activate(item); + + // Smart navigation + if (o.smart && index !== false) { + // When centeredNav is enabled, center the element. + // Otherwise, determine where to position the element based on its current position. + // If the element is currently on the far end side of the frame, assume that user is + // moving forward and animate it to the start of the visible frame, and vice versa. + if (centeredNav) { + self.toCenter(index, immediate); + } else if (index >= rel.lastItem) { + self.toStart(index, immediate); + } else if (index <= rel.firstItem) { + self.toEnd(index, immediate); + } else { + resetCycle(); + } + } + }; + + /** + * Activates a page. + * + * @param {Int} index Page index, starting from 0. + * @param {Bool} immediate Whether to reposition immediately without animation. + * + * @return {Void} + */ + self.activatePage = function (index, immediate) { + if (isNumber(index)) { + slideTo(pages[within(index, 0, pages.length - 1)], immediate); + } + }; + + /** + * Return relative positions of items based on their visibility within FRAME. + * + * @param {Int} slideePos Position of SLIDEE. + * + * @return {Void} + */ + function getRelatives(slideePos) { + slideePos = within(isNumber(slideePos) ? slideePos : pos.dest, pos.start, pos.end); + + var relatives = {}; + var centerOffset = forceCenteredNav ? 0 : frameSize / 2; + + // Determine active page + if (!parallax) { + for (var p = 0, pl = pages.length; p < pl; p++) { + if (slideePos >= pos.end || p === pages.length - 1) { + relatives.activePage = pages.length - 1; + break; + } + + if (slideePos <= pages[p] + centerOffset) { + relatives.activePage = p; + break; + } + } + } + + // Relative item indexes + if (itemNav) { + var first = false; + var last = false; + var center = false; + + // From start + for (var i = 0, il = items.length; i < il; i++) { + // First item + if (first === false && slideePos <= items[i].start + items[i].half) { + first = i; + } + + // Center item + if (center === false && slideePos <= items[i].center + items[i].half) { + center = i; + } + + // Last item + if (i === il - 1 || slideePos <= items[i].end + items[i].half) { + last = i; + break; + } + } + + // Safe assignment, just to be sure the false won't be returned + relatives.firstItem = isNumber(first) ? first : 0; + relatives.centerItem = isNumber(center) ? center : relatives.firstItem; + relatives.lastItem = isNumber(last) ? last : relatives.centerItem; + } + + return relatives; + } + + /** + * Update object with relative positions. + * + * @param {Int} newPos + * + * @return {Void} + */ + function updateRelatives(newPos) { + $.extend(rel, getRelatives(newPos)); + } + + /** + * Disable navigation buttons when needed. + * + * Adds disabledClass, and when the button is