UNPKG

formstone

Version:

Library of modular front end components.

1,500 lines (1,208 loc) 40.8 kB
/* global define */ (function(factory) { if (typeof define === "function" && define.amd) { define([ "jquery", "./core", "./mediaquery", "./touch" ], factory); } else { factory(jQuery, Formstone); } }(function($, Formstone) { "use strict"; /** * @method private * @name resize * @description Handles window resize */ function resize(windowWidth) { Functions.iterate.call($Instances, resizeInstance); } /** * @method private * @name cacheInstances * @description Caches active instances */ function cacheInstances() { $Instances = $(Classes.base); } /** * @method private * @name construct * @description Builds instance. * @param data [object] "Instance data" */ function construct(data) { var i; data.didPan = false; data.carouselClasses = [ RawClasses.base, data.theme, data.customClass, (data.rtl ? RawClasses.rtl : RawClasses.ltr) ]; data.maxWidth = (data.maxWidth === Infinity ? "100000px" : data.maxWidth); data.mq = "(min-width:" + data.minWidth + ") and (max-width:" + data.maxWidth + ")"; data.customControls = ($.type(data.controls) === "object" && data.controls.previous && data.controls.next); data.customPagination = ($.type(data.pagination) === "string"); data.id = this.attr("id"); if (data.id) { data.ariaId = data.id; } else { data.ariaId = data.rawGuid; this.attr("id", data.ariaId); } // Legacy browser support if (!Formstone.support.transform) { data.useMargin = true; } // Build controls and pagination var controlsHtml = '', paginationHtml = '', controlPrevClasses = [RawClasses.control, RawClasses.control_previous].join(" "), controlNextClasses = [RawClasses.control, RawClasses.control_next].join(" "); if (data.controls && !data.customControls) { data.labels.controls = data.labels.controls.replace('{guid}', data.numGuid); controlsHtml += '<div class="' + RawClasses.controls + '" aria-label="' + data.labels.controls + '" aria-controls="' + data.ariaId + '">'; controlsHtml += '<button type="button" class="' + controlPrevClasses + '" aria-label="' + data.labels.previous + '">' + data.labels.previous + '</button>'; controlsHtml += '<button type="button" class="' + controlNextClasses + '" aria-label="' + data.labels.next + '">' + data.labels.next + '</button>'; controlsHtml += '</div>'; } if (data.pagination && !data.customPagination) { data.labels.pagination = data.labels.pagination.replace('{guid}', data.numGuid); paginationHtml += '<div class="' + RawClasses.pagination + '" aria-label="' + data.labels.pagination + '" aria-controls="' + data.ariaId + '" role="navigation">'; paginationHtml += '</div>'; } if (data.autoHeight) { data.carouselClasses.push(RawClasses.auto_height); } if (data.contained) { data.carouselClasses.push(RawClasses.contained); } if (data.single) { data.carouselClasses.push(RawClasses.single); } // Modify dom this.addClass(data.carouselClasses.join(" ")) .wrapInner('<div class="' + RawClasses.wrapper + '" aria-live="polite"><div class="' + RawClasses.container + '"><div class="' + RawClasses.canister + '"></div></div></div>') .append(controlsHtml) .wrapInner('<div class="' + RawClasses.viewport + '"></div>') .append(paginationHtml); data.$viewport = this.find(Classes.viewport).eq(0); data.$container = this.find(Classes.container).eq(0); data.$canister = this.find(Classes.canister).eq(0); data.$pagination = this.find(Classes.pagination).eq(0); data.$controlPrevious = data.$controlNext = $(''); if (data.customControls) { data.$controls = $(data.controls.container).addClass([RawClasses.controls, RawClasses.controls_custom].join(" ")); data.$controlPrevious = $(data.controls.previous).addClass(controlPrevClasses); data.$controlNext = $(data.controls.next).addClass(controlNextClasses); } else { data.$controls = this.find(Classes.controls).eq(0); data.$controlPrevious = data.$controls.find(Classes.control_previous); data.$controlNext = data.$controls.find(Classes.control_next); } data.$controlItems = data.$controlPrevious.add(data.$controlNext); if (data.customPagination) { data.$pagination = $(data.pagination).addClass([RawClasses.pagination]); } data.$paginationItems = data.$pagination.find(Classes.page); data.index = 0; data.enabled = false; data.leftPosition = 0; data.autoTimer = null; data.resizeTimer = null; // live query for linked to avoid missing new elements var linked = this.data(Namespace + "-linked"); data.linked = linked ? '[data-' + Namespace + '-linked="' + linked + '"]' : false; // force paged if linked, keeps counts accurate if (data.linked) { data.paged = true; } // live query for controlled to avoid missing new elements var subordinate = this.data(Namespace + "-controller-for") || ''; data.$subordinate = $(subordinate); if (data.$subordinate.length) { data.controller = true; } // Responsive count handling if ($.type(data.show) === "object") { var show = data.show, cache = [], keys = []; for (i in show) { if (show.hasOwnProperty(i)) { keys.push(i); } } keys.sort(Functions.sortAsc); for (i in keys) { if (keys.hasOwnProperty(i)) { cache.push({ width: parseInt(keys[i]), count: show[keys[i]], mq: window.matchMedia("(min-width: " + parseInt(keys[i]) + "px)") }); } } data.show = cache; } cacheValues(data); // Media Query support $.fsMediaquery("bind", data.rawGuid, data.mq, { enter: function() { enable.call(data.$el, data); }, leave: function() { disable.call(data.$el, data); } }); cacheInstances(); data.carouselClasses.push(RawClasses.enabled); data.carouselClasses.push(RawClasses.animated); } /** * @method private * @name destruct * @description Tears down instance. * @param data [object] "Instance data" */ function destruct(data) { Functions.clearTimer(data.autoTimer); Functions.clearTimer(data.resizeTimer); disable.call(this, data); $.fsMediaquery("unbind", data.rawGuid); if (data.id !== data.ariaId) { this.removeAttr("id"); } data.$controlItems.removeClass([Classes.control, RawClasses.control_previous, Classes.control_next, Classes.visible].join(" ")) .off(Events.namespace); data.$images.off(Events.namespace); data.$canister.fsTouch("destroy"); data.$items.removeClass([RawClasses.item, RawClasses.visible, Classes.item_previous, Classes.item_next].join(" ")) .unwrap() .unwrap() .unwrap() .unwrap(); if (data.controls && !data.customControls) { data.$controls.remove(); } if (data.customControls) { data.$controls.removeClass([RawClasses.controls, RawClasses.controls_custom, RawClasses.visible].join(" ")); } if (data.pagination && !data.customPagination) { data.$pagination.remove(); } if (data.customPagination) { data.$pagination.html("").removeClass([RawClasses.pagination, RawClasses.visible].join(" ")); } this.removeClass(data.carouselClasses.join(" ")); cacheInstances(); } /** * @method * @name disable * @description Disables instance of plugin * @example $(".target").carousel("disable"); */ function disable(data) { if (data.enabled) { Functions.clearTimer(data.autoTimer); data.enabled = false; data.$subordinate.off(Events.update); this.removeClass([RawClasses.enabled, RawClasses.animated].join(" ")) .off(Events.namespace); data.$canister.fsTouch("destroy") .off(Events.namespace) .attr("style", "") .css(TransitionProperty, "none"); data.$items.css({ width: "", height: "" }).removeClass([RawClasses.visible, Classes.item_previous, Classes.item_next].join(" ")); data.$images.off(Events.namespace); data.$controlItems.off(Events.namespace); data.$pagination.html("").off(Events.namespace); hideControls(data); if (data.useMargin) { data.$canister.css({ marginLeft: "" }); } else { data.$canister.css(TransformProperty, ""); } data.index = 0; } } /** * @method * @name enable * @description Enables instance of plugin * @example $(".target").carousel("enable"); */ function enable(data) { if (!data.enabled) { data.enabled = true; this.addClass(RawClasses.enabled); data.$controlItems.on(Events.click, data, onAdvance); data.$pagination.on(Events.click, Classes.page, data, onSelect); data.$items.on(Events.click, data, onItemClick); data.$subordinate.on(Events.update, data, onSubordinateUpdate); onSubordinateUpdate({ data: data }, 0); data.$canister.fsTouch({ axis: "x", pan: true, swipe: true }).on(Events.panStart, data, onPanStart) .on(Events.pan, data, onPan) .on(Events.panEnd, data, onPanEnd) .on(Events.swipe, data, onSwipe) .on(Events.focusIn, data, onItemFocus) .css(TransitionProperty, ""); cacheValues(data); // Watch Images data.$images.on(Events.load, data, onImageLoad); // Auto timer if (data.autoAdvance) { data.autoTimer = Functions.startTimer(data.autoTimer, data.autoTime, function() { autoAdvance(data); }, true); } resizeInstance.call(this, data); } } /** * @method * @name resize * @description Resizes instance * @example $(".target").carousel("resize"); */ /** * @method private * @name resizeInstance * @description Resizes each instance * @param data [object] "Instance data" */ function resizeInstance(data) { if (data.enabled) { var h, i, j, k, w, $items, $first, width, height, left; data.count = data.$items.length; if (data.count < 1) { // avoid empty carousels hideControls(data); data.$canister.css({ height: "" }); return; } this.removeClass(RawClasses.animated); data.containerWidth = data.$container.outerWidth(false); data.visible = calculateVisible(data); data.perPage = data.paged ? 1 : data.visible; data.itemMarginLeft = parseInt(data.$items.eq(0).css("marginLeft")); data.itemMarginRight = parseInt(data.$items.eq(0).css("marginRight")); data.itemMargin = data.itemMarginLeft + data.itemMarginRight; if (isNaN(data.itemMargin)) { data.itemMargin = 0; } data.itemWidth = (data.containerWidth - (data.itemMargin * (data.visible - 1))) / data.visible; data.itemHeight = 0; data.pageWidth = data.paged ? data.itemWidth : data.containerWidth; data.pageCount = Math.ceil(data.count / data.perPage); data.canisterWidth = data.single ? data.containerWidth : ((data.pageWidth + data.itemMargin) * data.pageCount); data.$canister.css({ width: (data.matchWidth) ? data.canisterWidth : 1000000, height: "" }); data.$items.css({ width: (data.matchWidth) ? data.itemWidth : "", height: "" }).removeClass([RawClasses.visible, RawClasses.item_previous, RawClasses.item_next].join(" ")); // initial page data.pages = []; for (i = 0, j = 0; i < data.count; i += data.perPage) { $items = data.$items.slice(i, i + data.perPage); width = 0; height = 0; if ($items.length < data.perPage) { if (i === 0) { $items = data.$items; } else { $items = data.$items.slice(data.$items.length - data.perPage); } } $first = data.rtl ? $items.eq($items.length - 1) : $items.eq(0); left = $first.position().left; // if (data.autoHeight) { for (k = 0; k < $items.length; k++) { w = $items.eq(k).outerWidth(true); h = $items.eq(k).outerHeight(); width += w; if (h > height) { height = h; } } // } else { // height = $first.outerHeight(); // } data.pages.push({ left: data.rtl ? left - (data.canisterWidth - width) : left, height: height, width: width, $items: $items }); if (height > data.itemHeight) { data.itemHeight = height; } j++; } if (data.paged) { data.pageCount -= (data.count % data.visible); } if (data.pageCount <= 0) { data.pageCount = 1; } data.maxMove = -data.pages[data.pageCount - 1].left; // auto / match height if (data.autoHeight) { data.$canister.css({ height: data.pages[0].height }); } else if (data.matchHeight) { data.$items.css({ height: data.itemHeight }); } // Reset Page Count var html = ''; for (i = 0; i < data.pageCount; i++) { html += '<button type="button" class="' + RawClasses.page + '">' + (i + 1) + '</button>'; } data.$pagination.html(html); // update pagination if (data.pageCount <= 1) { hideControls(data); } else { showControls(data); } data.$paginationItems = data.$pagination.find(Classes.page); positionCanister(data, data.index, false); setTimeout(function() { data.$el.addClass(RawClasses.animated); }, 5); } } /** * @method private * @name cacheValues * @description Caches internal values after item change * @param data [object] "Instance data" */ function cacheValues(data) { // Cache vaules data.$items = data.$canister.children().not(":hidden").addClass(RawClasses.item); data.$images = data.$canister.find("img"); data.totalImages = data.$images.length; } /** * @method * @name reset * @description Resets instance after item change * @example $(".target").carousel("reset"); */ /** * @method private * @name resetInstance * @description Resets instance after item change * @param data [object] "Instance data" */ function resetInstance(data) { if (data.enabled) { updateItems.call(this, data, false); } } /** * @method * @name update * @description Updates carousel items * @example $(".target").carousel("update", "..."); */ /** * @method private * @name updateItems * @description Updates carousel items for each instance * @param data [object] "Instance data" * @param html [string] "New carousel contents" */ function updateItems(data, html) { data.$images.off(Events.namespace); if (html !== false) { data.$canister.html(html); } data.index = 0; cacheValues(data); resizeInstance.call(this, data); } /** * @method * @name jumpPage * @description Jump instance of plugin to specific page * @example $(".target").carousel("jumpPage", 1); * @param index [int] "New index" * @param silent [boolean] "Flag to prevent triggering update event" */ /** * @method * @name jump * @description Jump instance of plugin to specific page; Alias of `jumpPage` * @example $(".target").carousel("jump", 1); * @param index [int] "New index" * @param silent [boolean] "Flag to prevent triggering update event" */ /** * @method private * @name jumpPage * @description Jump instance of plugin to specific page * @param data [object] "Instance data" * @param index [int] "New index" * @param silent [boolean] "" * @param animated [boolean] "" */ function jumpPage(data, index, silent, fromLinked, animated) { if (data.enabled) { Functions.clearTimer(data.autoTimer); if (typeof animated === "undefined") { animated = true; } positionCanister(data, index - 1, animated, silent, fromLinked); } } /** * @method * @name previousPage * @description Move to the previous page * @example $(".target").carousel("previousPage"); */ /** * @method * @name previous * @description Move to the previous page; Alias of `previousPage` * @example $(".target").carousel("previous"); */ /** * @method private * @name previousPage * @description Move to previous page * @param data [object] "Instance data" */ function previousPage(data) { var index = data.index - 1; if (data.infinite && index < 0) { index = data.pageCount - 1; } positionCanister(data, index); } /** * @method * @name nextPage * @description Move to next page * @example $(".target").carousel("nextPage"); */ /** * @method * @name next * @description Move to next page; Alias of `nextPage` * @example $(".target").carousel("next"); */ /** * @method private * @name nextPage * @description Move to next page * @param data [object] "Instance data" */ function nextPage(data) { var index = data.index + 1; if (data.infinite && index >= data.pageCount) { index = 0; } positionCanister(data, index); } /** * @method * @name jumpItem * @description Jump instance of plugin to specific item * @example $(".target").carousel("jumpItem", 1); * @param index [int] "New item index" * @param silent [boolean] "Flag to prevent triggering update event" */ /** * @method private * @name jumpItem * @description Jump instance of plugin to specific page * @param data [object] "Instance data" * @param index [int] "New index" * @param silent [boolean] "" * @param animated [boolean] "" */ function jumpItem(data, index, silent, fromLinked, animated) { if (data.enabled) { Functions.clearTimer(data.autoTimer); var $active = data.$items.eq(index - 1); if (typeof animated === "undefined") { animated = true; } for (var i = 0; i < data.pageCount; i++) { if (data.pages[i].$items.is($active)) { positionCanister(data, i, animated, silent, fromLinked); break; } } } } /** * @method private * @name onImageLoad * @description Handles child image load * @param e [object] "Event data" */ function onImageLoad(e) { var data = e.data; data.resizeTimer = Functions.startTimer(data.resizeTimer, 1, function() { resizeInstance.call(data.$el, data); }); } /** * @method private * @name autoAdvance * @description Handles auto advancement * @param data [object] "Instance data" */ function autoAdvance(data) { var index = data.index + 1; if (index >= data.pageCount) { index = 0; } positionCanister(data, index); } /** * @method private * @name onAdvance * @description Handles item advancement * @param e [object] "Event data" */ function onAdvance(e) { Functions.killEvent(e); var data = e.data, index = data.index + ($(e.currentTarget).hasClass(RawClasses.control_next) ? 1 : -1); Functions.clearTimer(data.autoTimer); positionCanister(data, index); } /** * @method private * @name onSelect * @description Handles item select * @param e [object] "Event data" */ function onSelect(e) { Functions.killEvent(e); var data = e.data, index = data.$paginationItems.index($(e.currentTarget)); Functions.clearTimer(data.autoTimer); positionCanister(data, index); } /** * @method private * @name position * @description Handles updating instance position * @param data [object] "Instance data" * @param index [int] "Item index" */ function positionCanister(data, index, animate, silent, fromLinked) { if (index < 0) { index = (data.infinite) ? data.pageCount - 1 : 0; } if (index >= data.pageCount) { index = (data.infinite) ? 0 : data.pageCount - 1; } if (data.count < 1) { return; } if (data.pages[index]) { data.leftPosition = -data.pages[index].left; } data.leftPosition = checkPosition(data, data.leftPosition); if (data.useMargin) { data.$canister.css({ marginLeft: data.leftPosition }); } else { if (animate === false) { data.$canister.css(TransitionProperty, "none") .css(TransformProperty, "translateX(" + data.leftPosition + "px)"); // Slight delay before adding transitions back setTimeout(function() { data.$canister.css(TransitionProperty, ""); }, 5); } else { data.$canister.css(TransformProperty, "translateX(" + data.leftPosition + "px)"); } } // Update classes data.$items.removeClass([RawClasses.visible, RawClasses.item_previous, RawClasses.item_next].join(" ")); for (var i = 0, count = data.pages.length; i < count; i++) { if (i === index) { data.pages[i].$items.addClass(RawClasses.visible).attr("aria-hidden", "false"); } else { data.pages[i].$items.not(data.pages[index].$items).addClass((i < index) ? RawClasses.item_previous : RawClasses.item_next).attr("aria-hidden", "true"); } } // Auto Height if (data.autoHeight) { data.$canister.css({ height: data.pages[index].height }); } if (animate !== false && silent !== true && index !== data.index && (data.infinite || (index > -1 && index < data.pageCount))) { data.$el.trigger(Events.update, [index]); } data.index = index; // Linked if (data.linked && fromLinked !== true) { $(data.linked).not(data.$el)[NamespaceClean]("jumpPage", data.index + 1, true, true); } updateControls(data); } /** * @method private * @name hideControls * @description Hides instance controls * @param data [object] "Instance data" */ function hideControls(data) { data.$controls.removeClass(RawClasses.visible); data.$controlItems.removeClass(RawClasses.visible); data.$pagination.removeClass(RawClasses.visible); } /** * @method private * @name showControls * @description Shows instance controls * @param data [object] "Instance data" */ function showControls(data) { data.$controls.addClass(RawClasses.visible); data.$controlItems.addClass(RawClasses.visible); data.$pagination.addClass(RawClasses.visible); } /** * @method private * @name updateControls * @description Handles updating instance controls * @param data [object] "Instance data" */ function updateControls(data) { data.$paginationItems.removeClass(RawClasses.active) .eq(data.index) .addClass(RawClasses.active); if (data.infinite) { data.$controlItems.addClass(RawClasses.visible); } else if (data.pageCount < 1) { data.$controlItems.removeClass(RawClasses.visible); } else { data.$controlItems.addClass(RawClasses.visible); if (data.index <= 0) { data.$controlPrevious.removeClass(RawClasses.visible); } else if (data.index >= data.pageCount - 1 || (!data.single && data.leftPosition === data.maxMove)) { data.$controlNext.removeClass(RawClasses.visible); } } } /** * @method private * @name calculateVisible * @description Determines how many items should show at screen width * @param data [object] "Instance data" * @return [int] "New visible count" */ function calculateVisible(data) { var show = 1; if (data.single) { return show; } else if ($.type(data.show) === "array") { for (var i in data.show) { if (data.show.hasOwnProperty(i)) { if (Formstone.support.matchMedia) { if (data.show[i].mq.matches) { show = data.show[i].count; } } else { // Fallback, grab the first breakpoint that's large enough if (data.show[i].width < Formstone.fallbackWidth) { show = data.show[i].count; } } } } } else { show = data.show; } return (data.fill && data.count < show) ? data.count : show; } /** * @method private * @name onPanStart * @description Handles pan start event * @param e [object] "Event data" */ function onPanStart(e, fromLinked) { var data = e.data; Functions.clearTimer(data.autoTimer); if (!data.single) { if (data.useMargin) { data.leftPosition = parseInt(data.$canister.css("marginLeft")); } else { var matrix = data.$canister.css(TransformProperty).split(","); data.leftPosition = parseInt(matrix[4]); // ? } data.$canister.css(TransitionProperty, "none") .css("will-change", "transform"); onPan(e); // Linked if (data.linked && fromLinked !== true) { var percent = e.deltaX / data.pageWidth; if (data.rtl) { percent *= -1; } $(data.linked).not(data.$el)[NamespaceClean]("panStart", percent); } } data.isTouching = true; } /** * @method private * @name onPan * @description Handles pan event * @param e [object] "Event data" */ function onPan(e, fromLinked) { var data = e.data; if (!data.single) { data.touchLeft = checkPosition(data, data.leftPosition + e.deltaX); if (data.useMargin) { data.$canister.css({ marginLeft: data.touchLeft }); } else { data.$canister.css(TransformProperty, "translateX(" + data.touchLeft + "px)"); } // Linked if (data.linked && fromLinked !== true) { var percent = e.deltaX / data.pageWidth; if (data.rtl) { percent *= -1; } $(data.linked).not(data.$el)[NamespaceClean]("pan", percent); } } } /** * @method private * @name onPanEnd * @description Handles pan end event * @param e [object] "Event data" */ function onPanEnd(e, fromLinked) { var data = e.data, delta = Math.abs(e.deltaX), increment = getIncrement(data, e), index = false; data.didPan = false; if (!data.single) { var i, count, left = Math.abs(data.touchLeft), page = false, dir = (data.rtl) ? "right" : "left"; if (e.directionX === dir) { // Left (RTL Right) for (i = 0, count = data.pages.length; i < count; i++) { page = data.pages[i]; if (left > Math.abs(page.left) + 20) { index = i + 1; } } } else { // Right (RTL Left) for (i = data.pages.length - 1, count = 0; i >= count; i--) { page = data.pages[i]; if (left < Math.abs(page.left)) { index = i - 1; } } } } if (index === false) { index = (delta < 50) ? data.index : data.index + increment; } if (index !== data.index) { data.didPan = true; } // Linked if (data.linked && fromLinked !== true) { $(data.linked).not(data.$el)[NamespaceClean]("panEnd", index); } endTouch(data, index); } /** * @method private * @name linkedPanStart * @description Handles linked pan start * @param data [object] "Instance data" * @param percent [float] "Percentage moved" */ function linkedPanStart(data, percent) { Functions.clearTimer(data.autoTimer); if (!data.single) { if (data.rtl) { percent *= -1; } if (data.useMargin) { data.leftPosition = parseInt(data.$canister.css("marginLeft")); } else { var matrix = data.$canister.css(TransformProperty).split(","); data.leftPosition = parseInt(matrix[4]); // ? } data.$canister.css(TransitionProperty, "none"); var e = { data: data, deltaX: (data.pageWidth * percent) }; onPan(e, true); } data.isTouching = true; } /** * @method private * @name linkedPan * @description Handles linked pan * @param data [object] "Instance data" * @param percent [float] "Percentage moved" */ function linkedPan(data, percent) { if (!data.single) { if (data.rtl) { percent *= -1; } var delta = (data.pageWidth * percent); data.touchLeft = checkPosition(data, data.leftPosition + delta); if (data.useMargin) { data.$canister.css({ marginLeft: data.touchLeft }); } else { data.$canister.css(TransformProperty, "translateX(" + data.touchLeft + "px)"); } } } /** * @method private * @name linkedPanEnd * @description Handles linked pan end * @param data [object] "Instance data" * @param index [int] "New Index" */ function linkedPanEnd(data, index) { endTouch(data, index, true); } /** * @method private * @name onSwipe * @description Handles swipe event * @param e [object] "Event data" */ function onSwipe(e, fromLinked) { var data = e.data, increment = getIncrement(data, e), index = data.index + increment; // Linked if (data.linked && fromLinked !== true) { $(data.linked).not(data.$el)[NamespaceClean]("swipe", e.directionX); } endTouch(data, index); } /** * @method private * @name linkedSwipe * @description Handles swipe event * @param data [object] "Instance data" * @param direction [string] "Swipe direction" */ function linkedSwipe(data, direction) { var e = { data: data, directionX: direction }; onSwipe(e, true); } /** * @method private * @name endTouch * @description Cleans up touch interactions * @param data [object] "Instance data" * @param index [object] "New index" */ function endTouch(data, index) { data.$canister.css(TransitionProperty, "") .css("will-change", ""); positionCanister(data, index); data.isTouching = false; } /** * @method private * @name onItemClick * @description Handles click to item * @param e [object] "Event data" */ function onItemClick(e) { var data = e.data, $target = $(e.currentTarget); if (!data.didPan) { $target.trigger(Events.itemClick); if (data.controller) { var index = data.$items.index($target); onSubordinateUpdate(e, index); data.$subordinate[NamespaceClean]("jumpPage", index + 1, true); } } } /** * @method private * @name onItemFocus * @description Handles focus to item/element in item * @param e [object] "Event data" */ function onItemFocus(e) { var data = e.data; if (data.enabled && !data.isTouching) { Functions.clearTimer(data.autoTimer); data.$container.scrollLeft(0); var $target = $(e.target), $active; if ($target.hasClass(RawClasses.item)) { $active = $target; } else if ($target.parents(Classes.item).length) { $active = $target.parents(Classes.item).eq(0); } for (var i = 0; i < data.pageCount; i++) { if (data.pages[i].$items.is($active)) { positionCanister(data, i); break; } } } } /** * @method private * @name onSubordinateUpdate * @description Handles update from subordinate * @param e [object] "Event data" * @param index [int] "Index" */ function onSubordinateUpdate(e, index) { var data = e.data; if (data.controller) { var $active = data.$items.eq(index); data.$items.removeClass(RawClasses.active); $active.addClass(RawClasses.active); for (var i = 0; i < data.pageCount; i++) { if (data.pages[i].$items.is($active)) { positionCanister(data, i, true, true); break; } } } } /** * @method private * @name checkPosition * @description Checks if left pos is in range * @param data [object] "Event data" * @param e [object] "Event data" * @return [int] "Corrected left position" */ function checkPosition(data, pos) { if (isNaN(pos)) { pos = 0; } else if (data.rtl) { if (pos > data.maxMove) { pos = data.maxMove; } if (pos < 0) { pos = 0; } } else { if (pos < data.maxMove) { pos = data.maxMove; } if (pos > 0) { pos = 0; } } return pos; } /** * @method private * @name getIncrement * @description Returns touch increment * @param data [object] "Instance data" * @param e [object] "Event data" * @return [int] "Target direction" */ function getIncrement(data, e) { return data.rtl ? ((e.directionX === "right") ? 1 : -1) : ((e.directionX === "left") ? 1 : -1); } /** * @plugin * @name Carousel * @description A jQuery plugin for simple content carousels. * @type widget * @main carousel.js * @main carousel.css * @dependency jQuery * @dependency core.js * @dependency mediaquery.js * @dependency touch.js */ var Plugin = Formstone.Plugin("carousel", { widget: true, /** * @options * @param autoAdvance [boolean] <false> "Flag to auto advance items" * @param autoHeight [boolean] <false> "Flag to adjust carousel height to visible item(s)" * @param autoTime [int] <8000> "Auto advance time" * @param contained [boolean] <true> "Flag for 'overflow: visible'" * @param controls [boolean or object] <true> "Flag to draw controls OR object containing container, next and previous control selectors (Must be fully qualified selectors)" * @param customClass [string] <''> "Class applied to instance" * @param fill [boolean] <false> "Flag to fill viewport if item count is less then show count" * @param infinite [boolean] <false> "Flag for looping items" * @param labels.next [string] <'Next'> "Control text" * @param labels.previous [string] <'Previous'> "Control text" * @param labels.controls [string] <'Carousel {guid} Controls'> "Controls aria label; {guid} replaced with instance GUID" * @param labels.pagination [string] <'Carousel {guid} Pagination'> "Pagination aria label; {guid} replaced with instance GUID" * @param matchHeight [boolean] <false> "Flag to match item heights" * @param matchWidth [boolean] <true> "Flag to match item widths; Requires CSS widths if false" * @param maxWidth [string] <'Infinity'> "Width at which to auto-disable plugin" * @param minWidth [string] <'0'> "Width at which to auto-disable plugin" * @param paged [boolean] <false> "Flag for paged items" * @param pagination [boolean or string] <true> "Flag to draw pagination OR string containing pagination target selector (Must be fully qualified selector)" * @param rtl [boolean] <false> "Right to Left display" * @param show [int / object] <1> "Items visible per page; Object for responsive counts" * @param single [boolean] <false> "Flag to display single item at a time" * @param theme [string] <"fs-light"> "Theme class name" * @param useMargin [boolean] <false> "Use margins instead of css transitions (legacy browser support)" */ defaults: { autoAdvance: false, autoHeight: false, autoTime: 8000, contained: true, controls: true, customClass: "", fill: false, infinite: false, labels: { next: "Next", previous: "Previous", controls: "Carousel {guid} Controls", pagination: "Carousel {guid} Pagination" }, matchHeight: false, matchWidth: true, maxWidth: Infinity, minWidth: '0px', paged: false, pagination: true, rtl: false, show: 1, single: false, theme: "fs-light", useMargin: false }, classes: [ "ltr", "rtl", "viewport", "wrapper", "container", "canister", "item", "item_previous", "item_next", "controls", "controls_custom", "control", "control_previous", "control_next", "pagination", "page", "animated", "enabled", "visible", "active", "auto_height", "contained", "single" ], /** * @events * @event itemClick.carousel "Item clicked; Triggered on carousel item" * @event update.carousel "Carousel position updated" */ events: { itemClick: "itemClick", update: "update" }, methods: { _construct: construct, _destruct: destruct, _resize: resize, disable: disable, enable: enable, // Backwards compat? jump: jumpPage, previous: previousPage, next: nextPage, // Pages jumpPage: jumpPage, previousPage: previousPage, nextPage: nextPage, // Items jumpItem: jumpItem, reset: resetInstance, resize: resizeInstance, update: updateItems, panStart: linkedPanStart, pan: linkedPan, panEnd: linkedPanEnd, swipe: linkedSwipe } }), // Localize References Namespace = Plugin.namespace, NamespaceClean = Plugin.namespaceClean, Classes = Plugin.classes, RawClasses = Classes.raw, Events = Plugin.events, Functions = Plugin.functions, $Instances = [], TransformProperty = Formstone.transform, TransitionProperty = Formstone.transition; }) );