/**
* XAContext
*
* XA.component.carousels module.
*
* Exposes register (selector, options) method that initializes Carousel
* for each element that matches the provided selector, with its given options.
* Element used as a carousel must contain a <ul class="slides"> tag.
* That tag must be filled with <li> elements. Each <li> element will be considered as a slide.
* For more details read the documentation of the register(selector, options) method.
*
* @requires jQuery JavaScript Library v1.4.4 or higher
*/
/* global Hammer:true */
XA.component.carousels = (function($) {
var pub = {},
NavigationBuilder = null,
SliderInitializer = null;
/**
* Timer class saves time stamp and counts time difference between
* current time (update() method) and saved time stamp.
* @constructor
*/
function Timer() {
/**
* Time difference between last update() call and last set() call
*
* @member Timer
*/
this.elapsed = 0;
/**
* Time stamp
*
* @member Timer
*/
this.stamp = null;
}
/**
* Counts time difference between current time and saved time stamp
*
* @member Timer
*/
Timer.prototype.update = function() {
if (this.stamp !== null) {
this.elapsed += this.newStamp() - this.stamp;
}
};
/**
* Saves new time stamp
*
* @member Timer
*/
Timer.prototype.set = function() {
this.stamp = this.newStamp();
};
/**
* Counts time difference between current time and saved time stamp
*
* @member Timer
* @return stamp of a current time
* @type Number
*/
Timer.prototype.newStamp = function() {
return new Date().valueOf();
};
/**
* Resets elapsed time.
*
* @member Timer
*/
Timer.prototype.reset = function() {
this.elapsed = 0;
};
/**
* End of Timer class
*/
/**
* TimeIndicator class extends Timer class
* @constructor
* @param {Object} $ a jQuery object
* @param {XA.component.carousels.IndicatorViews.View} view a time indicator view.
* @param {Number} timeout a view timeout
* @base Timer
*/
function TimeIndicator($, view, timeout) {
/**
* A time indicator view.
*
* @member TimerIndicator
*/
this.view = view;
this.view.init(timeout);
}
TimeIndicator.prototype = new Timer();
TimeIndicator.constructor = Timer;
/**
* Plays the attached view.
*
* @member TimerIndicator
*/
TimeIndicator.prototype.play = function() {
this.view.play();
};
/**
* Pauses the attached view.
*
* @member TimerIndicator
*/
TimeIndicator.prototype.pause = function() {
this.view.pause();
};
/**
* Resets elapsed time and resets the attached view.
*
* @member TimerIndicator
*/
TimeIndicator.prototype.reset = function() {
Timer.prototype.reset.call(this);
this.view.reset();
};
/**
* Counts time difference between current time and saved time stamp
* and updates the attached view
*
* @member TimerIndicator
*/
TimeIndicator.prototype.update = function() {
Timer.prototype.update.call(this);
this.view.update(this.elapsed);
};
/**
* end of TimeIndicator class
*/
/**
* NavigationBuilder object creates navigation items
*/
NavigationBuilder = (function($) {
function createItem(settings) {
var text = settings.text,
$navItem = null;
settings.text = '';
settings.href = '#';
$navItem = $('<a>', settings);
$('<span>', {
'text': text
}).appendTo($navItem);
return $navItem;
}
return {
/**
* Returns the element(s) that match the provided selector or null
*
* @param {String} selector a selector used for searching
* @return a jQuery object containing element(s) that match the provided selector or null if there is no element match
* @type Object
*/
getElement: function(selector) {
var $element = $(selector);
if ($element.length === 0) {
$element = null;
}
return $element;
},
/**
* Creates navigation items and appends them to $container.
*
* @param {Object} $container navigation items container (jQuery object)
* @param {String} label navigation items label template
* @param {Number} count number of navigation items to create
*/
createItems: function($container, label, count) {
var i,
pattern = /#\{index\}/g,
settings;
for (i = 0; i < count; i++) {
settings = {
'text': label.replace(pattern, i + 1)
};
createItem(settings).appendTo($container);
}
},
/**
* Creates or selects 'previous'/'next' navigation item.
* Mode of action depends on settings object:
* var settings = {
* isContainer: Boolean value,
* label: String value,
* selector: String value,
* method: String value
* };
*
* If settings.selector === null or if settings.selector !== null and settings.isContainer == true
* navigation item is created and attached to $element.first() (using settings.method).
* If settings.selector !== null and settings.isContainer == false
* $element.first() is selected as navigation item
*
* @param {Object} settings settings of navigation item
* @param {Object} itemSettings html attributes of navigation item
* @param {Object} $element navigation items container (jQuery object)
* @return The 'prev'/'next' navigation item
* @type Object
*/
createPrevNextItem: function(settings, itemSettings, $element) {
var $item = null,
isSelectorExists = settings.selector !== null;
if (isSelectorExists) {
$element = this.getElement(settings.selector);
}
if ($element !== null) {
if (settings.isContainer || !isSelectorExists) {
$item = createItem($.extend({}, {
'text': settings.label
}, itemSettings));
($element.first())[settings.method]($item);
} else {
$item = $element.first();
}
}
return $item;
}
};
}($));
/**
* end of NavigationBuilder object
*/
/**
* Navigation class that contains navigation items.
* @constructor
*/
function Navigation($, $wrapper, options) {
var defaults = {
'isEnabled': true,
'hasPrevNextItems': true,
'item': {
'label': '#{index}',
'selector': null
},
'prevItem': {
'label': '<',
'selector': null,
'isContainer': false
},
'nextItem': {
'label': '>',
'selector': null,
'isContainer': false
},
'slidesCount': 0
},
settings = $.extend(true, {}, defaults, options);
/**
* jQuery object
*
* @member Navigation
*/
this.$ = $;
/**
* Navigation items (one for each slide if navigation is enabled).
*
* @member Navigation
*/
this.$items = $();
/**
* Navigation 'next' item (if prev/next items are enabled).
*
* @member Navigation
*/
this.$nextItem = $();
/**
* Navigation 'previous' item (if prev/next items are enabled).
*
* @member Navigation
*/
this.$prevItem = $();
/**
* Selected item (one from this.$items or null).
*
* @member Navigation
*/
this.$selectedItem = null;
//initialization
if (settings.isEnabled) {
var self = this;
setTimeout(function() {
self.prepareItems(settings.item, settings.slidesCount, $wrapper);
//select first item on init
self.selectItem(0)
}, 1000);
$wrapper.find('.nav').trigger('navigation-created', this);
}
}
/**
* CSS class used to mark selected item.
*
* @member Navigation
*/
Navigation.prototype.selectedItemClass = 'active';
/**
* Mark navigation item with given index as selected.
*
* @member Navigation
* @param {Number} index index of navigation item
*/
Navigation.prototype.selectItem = function (index) {
if (this.$selectedItem !== null) {
this.$selectedItem.removeClass(this.selectedItemClass);
}
this.$selectedItem = this.$items.eq(index).addClass(this.selectedItemClass);
};
/**
* Creates or selects navigation items container and fills it with navigation items.
* If navigation items container is not empty, child elements are adopted as navigation items.
* Mode of action depends on settings object:
* var settings = {
* 'label': '#{index}',
* 'selector': null
* };
* If settings.selector === null navigation item container is created.
* If settings.selector !== null element that match settings.selector is adapted as navigagation items container.
*
* @member Navigation
* @param {Object} settings settings of navigation items
* @param {Number} count number of navigation items
* @param {Object} $wrapper wrapper of navigation items container (jQuery object)
* @return The container filled with navigation items.
* @type Object
*/
Navigation.prototype.prepareItems = function(settings, count, $wrapper) {
var $container = null;
if (settings.selector !== null) {
$container = NavigationBuilder.getElement(settings.selector);
} else {
$container = $('<div/>', {
'class': 'nav'
});
$container.appendTo($wrapper);
}
if ($container !== null) {
if ($container.children().length === 0) {
NavigationBuilder.createItems($container, settings.label, count);
}
this.$items = $container.children().slice(0, count);
}
return $container;
};
/**
* Creates or selects navigation 'previous' and 'next' navigation items (@see NavigationBuilder.createPrevNextItem()).
* If navigation items container is not empty, child elements are adopted as navigation items.
*
* @member Navigation
* @param {Object} settings settings of navigation
* @param {Object} $container navigation items container (jQuery object)
*/
Navigation.prototype.preparePrevNextItems = function(settings, $container) {
var $item;
settings.prevItem.method = 'prepend';
$item = NavigationBuilder.createPrevNextItem(settings.prevItem, {
'class': 'prev'
}, $container);
this.$prevItem = ($item === null) ? this.$prevItem : $item;
settings.nextItem.method = 'append';
$item = NavigationBuilder.createPrevNextItem(settings.nextItem, {
'class': 'next'
}, $container);
this.$nextItem = ($item === null) ? this.$nextItem : $item;
};
/**
* end of Navigation class
*/
/**
* SliderContext class is binded to a Slider class instance
* and contains its properties.
* @constructor
*/
function SliderContext() {
/**
* Flag that determines if slide can or cannot be changed (for user request)
*
* @member SliderContext
*/
this.canChangeSlide = true;
/**
* Default Slider setting object (@see Slider)
*
* @member SliderContext
*/
this.defaults = null;
/**
* Navigation object (@see Navigation)
*
* @member SliderContext
*/
this.navigation = null;
/**
* Slider object that this context is attached to.
*
* @member SliderContext
*/
this.owner = null;
/**
* Flag that determines if change of a slide can or can not be scheduled.
*
* @member SliderContext
*/
this.preventScheduling = false;
/**
* Slider setting object
*
* @member SliderContext
*/
this.settings = null;
/**
* Instance of Timer class (@see Timer) that is used to store how much
* time is left to change the slide.
*
* @member SliderContext
*/
this.slideTimer = new Timer();
/**
* Instance of TimeIndicator class (@see TimeIndicator)
*
* @member SliderContext
*/
this.timeIndicator = null;
/**
* Timers array.
*
* @member SliderContext
*/
this.timers = [];
/**
* Last timeout identifier.
*
* @member SliderContext
*/
this.timeoutId = null;
/**
* Transition (@see XA.component.carousels.Transitions.Transition) used while changing slides
*
* @member SliderContext
*/
this.transition = null;
/**
* Transition settings object (@see XA.component.carousels.Transitions.TransitionSettings)
*
* @member SliderContext
*/
this.transitionSettings = new XA.component.carousels.Transitions.TransitionSettings();
/**
* jQuery object that contains all slides
*
* @member SliderContext
*/
this.$slides = null;
/**
* jQuery object that contains the element that wraps slides
*
* @member SliderContext
*/
this.$wrapper = null;
/**
* Reference to method that should change current slide
*
* @member SliderContext
*/
this.changeCurrentSlide = null;
/**
* Reference to method that should return jQuery object with current slide
*
* @member SliderContext
*/
this.getCurrentSlide = null;
this.timers.push(this.slideTimer);
}
/**
* end of SliderContext class
*/
/**
* Slider class manages changing of slides.
*
* Options object:
* {
* 'navigation': Object value, //navigation settings object
* 'timeout': Number value, //time between slides changing
* 'transition': String value, //name of transition class (@see XA.component.carousels.Transitions.Transition)
* 'isPauseEnabled': Boolean value, //flag that determines if pause feature is enabled
* 'timeIndicator': {
* 'isEnabled': Boolean value, //flag that determines if time indicator is enabled
* 'selector': String value, //selector of time indicator html element
* 'view': String value, //name of view class (@see XA.component.carousels.IndicatorViews.View
* 'options': Object value //object with options that will be passed to a time indicator view
* }
* }
* @constructor
* @param {Object} $ the jQuery object
* @param {SliderContext} context instance of SliderContext class; must be separate for each instance (@see SliderContext)
* @param {Object} options Slider options object
*/
function Slider($, context, options) {
var defaults = {
'navigation': {},
'timeout': 10000,
'transition': 'BasicTransition',
'isPauseEnabled': true,
'timeIndicator': {
'isEnabled': false,
'selector': null,
'view': 'View',
'options': {}
}
};
/**
* Instance of SliderContext class (@see SliderContext)
*
* @member Navigation
*/
this.context = context;
context.owner = this;
context.defaults = defaults;
context.settings = $.extend(true, {}, defaults, options);
context.transitionSettings.$slides = context.$slides;
}
/**
* Executes method with given methodName on each Timer in context.timers (@see Timer)
*
* @member Slider
* @param {String} methodName name of a method that will be executed on timers
* @param {SliderContext} context current context
*/
Slider.prototype.executeOnTimers = function(methodName, context) {
var i = null,
timer = null,
timers = context.timers;
for (i = 0; i < timers.length; i++) {
timer = timers[i];
if (typeof(timer[methodName]) === 'function') {
timer[methodName]();
}
}
};
/**
* Deschedules change of a slide, by clearing timeout previously set by setTimeout() function.
*
* @member Slider
* @param {SliderContext} context current context
*/
Slider.prototype.descheduleSlide = function(context) {
if (context.timeoutId !== null) {
clearTimeout(context.timeoutId);
context.timeoutId = null;
}
};
/**
* Schedules change of a slide, by setting timeout (using setTimeout() function) if it can.
* If cannot, it tries again after short period of time.
*
* @member Slider
* @param {SliderContext} context current context
*/
Slider.prototype.scheduleSlide = function(context) {
var owner = context.owner;
owner.descheduleSlide(context);
if (!context.preventScheduling && context.$slides.length > 1) {
owner.executeOnTimers('set', context);
if (context.canChangeSlide) {
if (context.timeIndicator !== null) {
context.timeIndicator.play();
}
context.timeoutId = setTimeout(function() {
owner.changeCurrentSlideBy(1, context);
}, context.settings.timeout - context.slideTimer.elapsed);
} else {
setTimeout(function() {
owner.scheduleSlide(context);
}, 100);
}
}
};
/**
* Changes current slide by offset performing context.transition
* Any offset value is save (if it is an integer).
*
* @member Slider
* @param {Number} offset the offset to an index of a new slide
* @param {SliderContext} context current context
*/
Slider.prototype.changeCurrentSlideBy = function(offset, context) {
var settings = context.transitionSettings,
$currentSlide = context.getCurrentSlide(),
$slides = context.$slides;
if ((offset % $slides.length) !== 0) {
context.canChangeSlide = false;
settings.offset = offset;
settings.$currentSlide = $currentSlide;
settings.$nextSlide = $slides.eq(($currentSlide.index() + offset) % $slides.length);
context.changeCurrentSlide(settings.$nextSlide);
context.transition.perform(context.transitionSettings, offset);
context.owner.executeOnTimers('reset', context);
}
var $carousel = $(context.$wrapper).parents(".carousel");
$carousel.trigger("slide-changed");
};
/**
* Starts automatic slide changing
*
* @member Slider
*/
Slider.prototype.run = function() {
this.executeOnTimers('reset', this.context);
this.scheduleSlide(this.context);
};
/**
* Cancel the default action of event and changes slides manually.
*
* @member Slider
* @param {Object} event the event
* @param {Number} offset the offset to an index of a new slide
* @param {SliderContext} context current context
*/
Slider.prototype.onChangeCurrentSlide = function(event, offset, context) {
var owner = context.owner;
event.preventDefault();
if (context.canChangeSlide) {
owner.descheduleSlide(context);
owner.changeCurrentSlideBy(offset, context);
}
};
/**
* Selects navigation item with given index.
*
* @member Slider
* @param {Number} index the index of navigation item
*/
Slider.prototype.selectNavigationItem = function(index) {
this.context.navigation.selectItem(index);
};
/**
* end of Slider class
*/
/**
* SliderInitializer object sets up instances of Slider class
*/
SliderInitializer = (function($) {
var DIRECTION = {
PREV: -1,
NEXT: 1
};
/**
* Attaches events to navigation items.
*
* @param {SliderContext} context a context of a Slider class instance
*/
function attachNavigationEvents(context) {
var owner = context.owner,
$navLinks = context.$wrapper.find(".nav-items"),
$prevItemTxt = context.$wrapper.find('.prev-text'),
$nextItemTxt = context.$wrapper.find('.next-text'),
$carousel = context.$wrapper.parents(".carousel");
context.navigation.$items.each(function (index) {
var $slide = context.$slides.eq(index);
$(this).click(function (event) {
owner.onChangeCurrentSlide(event, $slide.index() - context.getCurrentSlide().index(), context);
});
});
$prevItemTxt.click(function (event) {
switchSlide(event, context, DIRECTION.PREV);
});
$nextItemTxt.click(function (event) {
switchSlide(event, context, DIRECTION.NEXT);
});
context.navigation.$prevItem.click(function (event) {
switchSlide(event, context, DIRECTION.PREV);
});
context.navigation.$nextItem.click(function (event) {
switchSlide(event, context, DIRECTION.NEXT);
});
$navLinks.on("keydown", function (event) {
switch (event.keyCode) {
case 37: //Letf arrow
switchSlide(event, context, DIRECTION.PREV);
$(this).parent().find(".active").focus();
break;
case 39: //Right arrow
switchSlide(event, context, DIRECTION.NEXT);
$(this).parent().find(".active").focus();
break;
case 13: //Enter
focusFirstFocusableElement(owner.context.getCurrentSlide());
}
});
$carousel.on("slide-changed", function(event){
var $activeLink = $navLinks.find(".active");
$activeLink.attr('tabindex', '0');
if($navLinks.find(document.activeElement).length){
$activeLink.focus();
}
$navLinks.find(">div").not('.active').attr('tabindex', '-1');
});
}
/**
* Switches slides back and foreward
*
* @param {Event} event an event that caused switch slide action
* @param {SliderContext} context a context of a Slider class instance
* @param {number} direction switch slide direction. -1 - switch to prev slide. 1 - switch to the next slide
*/
function switchSlide(event, context, direction) {
var owner = context.owner,
navLinks$ = context.$wrapper.find(".nav-items");
navLinks$.find(".active").attr('tabindex', '-1');
owner.onChangeCurrentSlide(event, direction, context);
navLinks$.find(".active").attr('tabindex', '0');
}
/**
* Focus the first focusable element inside container.
*
* @param {Container} container a container element where the first focusable element will be focused
*/
function focusFirstFocusableElement(container) {
var focusableElementsString = 'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, [tabindex="0"], [contenteditable]';
// Finds all focusable children
var focusableElements = container.find(focusableElementsString);
// If there are focusable elements - select the first one.
if (focusableElements.length !== 0) {
var firstTabStop = focusableElements[0];
// Focus first
firstTabStop.focus();
}
}
/**
* Attaches pause event to navigation wrapper.
*
* @param {SliderContext} context a context of a Slider class instance
*/
function attachPauseEvent(context) {
var owner = context.owner,
$element = context.$wrapper,
$navLinks = $element.find(".nav a");
var callbackMouseenter = function() {
context.preventScheduling = true;
owner.descheduleSlide(context);
owner.executeOnTimers('update', context);
if (context.timeIndicator !== null) {
context.timeIndicator.pause();
}
}
$element.mouseenter(callbackMouseenter);
$navLinks.on("focus", callbackMouseenter);
var callbackMouseLeave = function() {
context.preventScheduling = false;
owner.scheduleSlide(context);
}
$element.mouseleave(callbackMouseLeave);
$navLinks.on("blur", callbackMouseLeave);
}
/**
* Creates time indicator view
*
* @param {SliderContext} context a context of a Slider class instance
*/
function createIndicator(context) {
var settings = context.settings,
timeIndicator = null,
ViewConstructor = XA.component.carousels.IndicatorViews[settings.timeIndicator.view],
view = null;
if (ViewConstructor !== null) {
view = new ViewConstructor(settings.timeIndicator.selector, settings.timeIndicator.options);
timeIndicator = new TimeIndicator($, view, settings.timeout);
context.timeIndicator = timeIndicator;
context.timers.push(timeIndicator);
}
}
/**
* Attaches events to navigation.
*
* @param {SliderContext} context a context of a Slider class instance
*/
function initializeNavigation(context) {
context.settings.navigation.slidesCount = context.$slides.length;
context.navigation = new Navigation($, context.$wrapper, context.settings.navigation);
setTimeout(function() {
attachNavigationEvents(context);
}, 1000);
if (context.settings.isPauseEnabled) {
attachPauseEvent(context);
}
}
/**
* Initializes transition for a given context.
*
* @param {SliderContext} context a context of a Slider class instance
*/
function initializeTransition(context) {
var TransitionConstructor = null,
owner = context.owner;
context.transitionSettings.callback = function() {
context.canChangeSlide = true;
if (context.preventScheduling) {
owner.executeOnTimers('reset', context);
} else {
owner.scheduleSlide(context);
}
};
TransitionConstructor = XA.component.carousels.Transitions[context.settings.transition];
if ((TransitionConstructor === null) || (TransitionConstructor === undefined)) {
TransitionConstructor = XA.component.carousels.Transitions[context.defaults.transition];
}
context.transition = new TransitionConstructor();
context.transition.init(context.transitionSettings);
}
return {
/**
* Initializes Slider object with a given context.
*
* @param {SliderContext} context a context of a Slider class instance
*/
initialize: function(context) {
initializeNavigation(context);
initializeTransition(context);
if (context.settings.timeIndicator.isEnabled) {
createIndicator(context);
}
}
};
}($));
/**
* end of SliderInitializer
*/
/**
* Carousel class displays one slide (some content) at the time.
* Its behavior depends on settings (passed to init() method).
* For more details @see Slider, @see SliderInitializer, @see Navigation,
* @see NavigationBuilder, @see XA.component.carousels.Transitions.Transition,
* @see XA.component.carousels.IndicatorViews.View
*
* @constructor
*/
function Carousel() {
var context = this;
/**
* Instance of a Slider class (@see Slider).
*
* @member Carousel
*/
this.slider = null;
/**
* Displayed slide (jQuery object).
*
* @member Carousel
*/
this.$currentSlide = null;
/**
* All slides (jQuery object).
*
* @member Carousel
*/
this.$slides = null;
/**
* Changes current slide to a $newSlide.
*
* @member Carousel
* @param {Object} $newSlide the new slide (jQuery) object.
*/
this.changeCurrentSlide = function($newSlide) {
context.$currentSlide = $newSlide;
context.slider.selectNavigationItem($newSlide.index());
};
/**
* Returns current slide.
*
* @member Carousel
* @return current slide (jQuery) object.
* @type Object
*/
this.getCurrentSlide = function() {
return context.$currentSlide;
};
this.data = {};
}
/**
* Resets the carousel.
*
* @member Carousel
*/
Carousel.prototype.reset = function() {
this.$slides.each(function() {
$(this).hide();
});
this.changeCurrentSlide(this.$slides.first());
this.$currentSlide.show();
};
/**
* Initializes the carousel.
* Wraps carousel content with <div class="wrapper" /> element,
* selects slides (<li /> elements) from <ul class="slides" /> element,
* initializes Slider class instance, resets the carousel, and starts automatic slides changing.
*
* @member Carousel
* @param {Object} $container jQuery object with a main html element of the carousel
* @param {Object} options carousel options object
*/
Carousel.prototype.init = function($container, options) {
var $wrapper = $('<div/>', {
'class': 'wrapper'
}),
sliderContext = null;
$wrapper.append($container.children().detach());
$container.append($wrapper);
this.$slides = $container.find('.slides li.slide');
sliderContext = new SliderContext();
sliderContext.changeCurrentSlide = this.changeCurrentSlide;
sliderContext.getCurrentSlide = this.getCurrentSlide;
sliderContext.$slides = this.$slides;
sliderContext.$wrapper = $wrapper;
this.slider = new Slider($, sliderContext, options);
SliderInitializer.initialize(sliderContext);
this.reset();
this.slider.run();
};
/**
* end of Carousel class
*/
/* Carousel touch support */
Carousel.prototype.swipeSlide = function() {
var self = this,
$wrapper = this.slider.context.$wrapper,
hammer = new Hammer($wrapper[0]);
hammer.get('pan').set({ direction: Hammer.DIRECTION_HORIZONTAL });
hammer.on("swipeleft", function(e) {
self.slider.context.owner.onChangeCurrentSlide(e, 1, self.slider.context);
});
hammer.on("swiperight", function(e) {
self.slider.context.owner.onChangeCurrentSlide(e, -1, self.slider.context);
});
};
Carousel.prototype.maxSlideInfoHeight = function(){
var maxHeight = 0,
slideInfo = this.$slides[0].querySelector('.slide-info')
if (slideInfo){
maxHeight = slideInfo.offsetHeight;
};
for (var i = 1; i < this.$slides.length; i++){
var currSlideInfoHeight = this.$slides[i].querySelector('.slide-info').offsetHeight
if (maxHeight <currSlideInfoHeight ){
maxHeight = currSlideInfoHeight;
}
}
return maxHeight;
}
Carousel.prototype.resizeWrapper = function() {
var slideInfoHeight = this.maxSlideInfoHeight(),
wrapper = this.slider.context.$wrapper;
wrapper.find('.slide-info').css({
'min-height': slideInfoHeight
});
};
pub.initInstance = function(component){
var properties = component.data("properties"),
component = component.find(".carousel-inner"),
id = component.attr("data-id"),
self = component.eq(0),
querySlideIndex = parseInt(XA.queryString.getQueryParam(component.closest('.component.carousel').attr("id") || "carousel")),
validQueryParam = typeof querySlideIndex !== "undefined" && !isNaN(querySlideIndex) && querySlideIndex < component.find(".sxa-numbers").length,
context, settings, $currentSlide, $slides;
if (properties) {
if (!properties.navigation) {
properties.navigation = {
item: {},
prevItem: {},
nextItem: {},
isEnabled: validQueryParam ? false: true
};
}
properties.navigation.item.selector = "[data-id='" + id + "'] .nav-items"; //properties.navigation.item.selector;
properties.navigation.prevItem.selector = "#" + id + " .nav";
properties.navigation.nextItem.selector = "#" + id + " .nav";
var carousel = new Carousel($);
carousel.init(component, properties);
$(document).ready(function() {
carousel.resizeWrapper();
});
if (validQueryParam) {
context = carousel.slider.context,
settings = context.transitionSettings,
$currentSlide = context.getCurrentSlide(),
$slides = context.$slides;
context.canChangeSlide = false;
settings.$currentSlide = $currentSlide;
settings.$nextSlide = $($slides[querySlideIndex]);
context.changeCurrentSlide(settings.$nextSlide);
context.transition.perform(context.transitionSettings);
context.owner.executeOnTimers('reset', context);
context.navigation.prepareItems(properties.navigation.item, $slides.length, $(context.$wrapper));
context.navigation.selectItem(querySlideIndex);
} else {
carousel.swipeSlide();
}
}
}
pub.init = function () {
$(".carousel:not(.initialized)").each(function () {
pub.initInstance($(this))
$(this).addClass("initialized");
});
};
return pub;
}(jQuery));
/**
* end of XA.component.carousels module.
*/
XA.register("carousels", XA.component.carousels);
XA.component.carousels.Transitions = (function() {
var pub = {};
/**
* TransitionSettings class contains data needed for changing slides
* @constructor
*/
function TransitionSettings() {}
/**
* Current slide (currently displayed) jQuery object
*
* @member TransitionSettings
*/
TransitionSettings.prototype.$currentSlide = null;
/**
* Next slide jQuery object
*
* @member TransitionSettings
*/
TransitionSettings.prototype.$nextSlide = null;
/**
* All slides (jQuery object)
*
* @member TransitionSettings
*/
TransitionSettings.prototype.$slides = null;
/**
* Offset between $currentSlide and $nextSlide
*
* @member TransitionSettings
*/
TransitionSettings.prototype.offset = 0;
/**
* Callback function that must be called after animation.
*
* @member TransitionSettings
*/
TransitionSettings.prototype.callback = null;
/**
* end of TransitionSettings class
*/
pub.TransitionSettings = TransitionSettings;
/**
* Transition class is a base class of all transitions.
*
* Important: settings.callback() (@see TransitionSettings) have to be called at the end of execution the perform() method.
*
* @constructor
*/
function Transition() {}
Transition.prototype = new Transition();
/**
* Initializes a transition with given settings (@see TransitionSettings).
*
* @member Transition
* @param {TransitionSettings} settings transition settings
*/
/* eslint-disable */
Transition.prototype.init = function(settings) {
//implementation using settings
};
/* eslint-enable */
/**
* Returns a factor associated with settings.offset (@see TransitionSettings) that
* can be usefull for some animations.
*
* @member Transition
* @param {TransitionSettings} settings transition settings
* @return The factor; 1 if offset is higher than 0, -1 otherwise
* @type Number
*/
Transition.prototype.factor = function(settings) {
return (settings.offset > 0) ? 1 : -1;
};
/**
* This method is called used by carousels to change slides.
*
* Important: settings.callback() (@see TransitionSettings) have to be called at the end of this method execution.
*
* @member Transition
* @param {TransitionSettings} settings transition settings
*/
Transition.prototype.perform = function(settings) {
settings.callback();
};
/**
* End of Transition class
*/
pub.Transition = Transition;
/**
* BasicTransition class changes slides without any animation.
* Do not remove this class, because carousels use it by default,
* if requested transition cannot be found.
* @constructor
*/
function BasicTransition() {}
BasicTransition.prototype = new Transition();
BasicTransition.constructor = BasicTransition;
/**
* @member BasicTransition
*/
BasicTransition.prototype.perform = function(settings) {
settings.$currentSlide.hide();
settings.$nextSlide.show();
settings.callback();
};
/**
* End of BasicTransition class
*/
pub.BasicTransition = BasicTransition;
/**
* FadeInTransition class changes slides by fading in new slide.
* This class can be removed if effect is not used.
* @constructor
*/
function FadeInTransition() {}
FadeInTransition.prototype = new Transition();
FadeInTransition.constructor = FadeInTransition;
/**
* @member FadeInTransition
*/
FadeInTransition.prototype.init = function() {
/*settings.$slides.css({
'position': 'absolute',
'top': '0px',
'left': '0px'
});*/
};
/**
* @member FadeInTransition
*/
FadeInTransition.prototype.perform = function(settings) {
var zIndexAttrib = 'z-index',
$parent = settings.$slides.parent(),
$nextSlide = settings.$nextSlide;
$parent.css(zIndexAttrib, 0);
$nextSlide.css(zIndexAttrib, 1);
settings.$slides.css({
'position': 'relative'
});
settings.$nextSlide.css({
'position': 'absolute',
'top': '0',
'bottom': '0',
'left': '0',
'right': '0'
});
$nextSlide.fadeIn(function() {
settings.$currentSlide.hide();
settings.$nextSlide.css({
'position': 'relative',
'left': '0',
'transform': 'none'
});
$nextSlide.css(zIndexAttrib, '');
$parent.css(zIndexAttrib, '');
settings.callback();
});
};
/**
* End of FadeInTransition class
*/
pub.FadeInTransition = FadeInTransition;
/**
* SlideHorizontallyTransition class changes slides by sliding them horizontally.
* This class can be removed if effect is not used.
* If offset (difference between index of currently displayed slide and index of slide that will be shown)
* is greater than 0, slides are sliding to the left. Otherwise (offset is lower than 0), slides are sliding
* to the right.
* When slide change is raised automatically, slides are always sliding to right (offset == 1).
* If user raises slides change, slides are sliding:
* - to left, if offset is greater than 0 (slide with higher index will be shown or user clicks 'next' navigation item),
* - to right, if offset is lower than 0 (slide with lower index will be shown or user clicks 'prev' navigation item).
* @constructor
*/
function SlideHorizontallyTransition() {}
SlideHorizontallyTransition.prototype = new Transition();
SlideHorizontallyTransition.constructor = SlideHorizontallyTransition;
/**
* @member SlideHorizontallyTransition
*/
SlideHorizontallyTransition.prototype.onAnimationComplete = function(settings, $parent) {
settings.$currentSlide.hide();
settings.$nextSlide.css({
'top': '',
'bottom': '',
'left': '',
'position': '',
'width': ''
});
$parent.css({
'left': ''
});
$parent.css('margin-left', '');
settings.callback();
};
/**
* @member SlideHorizontallyTransition
*/
SlideHorizontallyTransition.prototype.perform = function (settings, offset) {
var factor = this.factor(settings),
onAnimationComplete = this.onAnimationComplete,
width = settings.$currentSlide.width(),
$parent = settings.$slides.parent();
settings.$nextSlide.css({
'top': '0',
'bottom': '0',
'left': '100%',
'position': 'absolute',
'width': '100%'
});
if (offset > 0) {
settings.$nextSlide.css({
'left': '100%',
})
} else {
settings.$nextSlide.css({
'left': '-100%',
})
}
settings.$nextSlide.show();
$parent.animate({
'left': -factor * width + 'px',
}, function() {
onAnimationComplete(settings, $parent);
});
};
/**
* End of SlideHorizontallyTransition class
*/
pub.SlideHorizontallyTransition = SlideHorizontallyTransition;
/**
* SlideVerticallyTransition class changes slides by sliding them vertically.
* This class can be removed if this effect is not used.
* If offset (difference between index of currently displayed slide and index of next slide)
* is greater than 0, slides are sliding from up to down. Otherwise (offset is lower than 0), slides are sliding
* from down to up.
* When slide change is raised automatically, slides are always sliding from up to down (offset == 1).
* If user raises slides change, slides are sliding:
* - from up to down, if offset is greater than 0 (slide with higher index will be shown or user clicks 'next' navigation item),
* - from down to up, if offset is lower than 0 (slide with lower index will be shown or user clicks 'prev' navigation item).
* @constructor
*/
function SlideVerticallyTransition() {}
SlideVerticallyTransition.prototype = new Transition();
SlideVerticallyTransition.constructor = SlideVerticallyTransition;
/**
* @member SlideVerticallyTransition
*/
SlideVerticallyTransition.prototype.onAnimationComplete = function(settings, $parent) {
settings.$currentSlide.hide();
settings.$nextSlide.css({
'top': '',
'bottom': '',
'left': '',
'right': '',
'position': '',
'width': ''
});
$parent.css('top', '');
$parent.css('margin-top', '');
settings.callback();
};
/**
* @member SlideVerticallyTransition
*/
SlideVerticallyTransition.prototype.perform = function (settings, offset) {
var factor = this.factor(settings),
onAnimationComplete = this.onAnimationComplete,
height = settings.$currentSlide.height(),
$parent = settings.$slides.parent();
settings.$nextSlide.css({
'left': '0',
'right': '0',
'position': 'absolute',
'width': '100%'
});
if (offset > 0) {
settings.$nextSlide.css({
'top': '-100%',
'bottom': '100%',
})
} else {
settings.$nextSlide.css({
'top': '100%',
'bottom': '-100%',
})
}
settings.$nextSlide.show();
$parent.animate({
'top': factor * height
}, function() {
onAnimationComplete(settings, $parent);
});
};
/**
* SlideVerticallyTransition class
*/
pub.SlideVerticallyTransition = SlideVerticallyTransition;
return pub;
}(jQuery));
/**
* end of XA.component.carousels.Transitions module.
*/
/**
* XAContext
*
* XA.component.carousels.IndicatorViews module.
*
* Exposes View class, which is a base class of all 'time indicator' views used by carousels (@see XA.component.carousels, @see View).
* Also exposes implementation of these 'time indicator' views:
* - ProgressBarView - shows the progress of time with a progress bar,
* - RotatorView - shows the progress of time with the filling up ring.
* Carousel takes the class constructor by name from this module (it is given in options object of carousel; @see XA.component.carousels).
* If there is a need to implement the new 'time indicator' view, create a new class
* by inheriting from View class and add it to this module. During implementation of a new 'time indicator'
* view the XA.component.carousels.IndicatorsView.Tools namespace. It contains:
* - isCssSupported(props) function, which checks if any of CSS properties is supported (props - array of properties),
* - isTransformSupported() function, which checks if CSS 3 'transform' is supported.
*
* Example view implementation (opacity increases with a progress of the time):
* (function() {
* var View = XA.component.carousels.IndicatorViews.View;
*
* function SampleView (selector, options) {
* //base constructor have to be called
* View.call(this, selector);
* }
*
* SampleView.prototype = new View();
* SampleView.constructor = SampleView;
*
* SampleView.prototype.init = function (timeout) {
* //base method have to be called
* View.prototype.init.call(this, timeout);
*
* //initialization code - not needed in this example
* };
*
* SampleView.prototype.reset = function () {
* //base method have to be called
* View.prototype.reset.call(this);
*
* //reseting 'time indicator' view
* this.$indicator.stop(true);
* this.$indicator.css('opacity', 0);
* };
*
* SampleView.prototype.pause = function () {
* //stoping 'time indicator' view animation
* this.$indicator.stop(true);
* };
*
* SampleView.prototype.play = function () {
* //starting/resuming 'time indicator' view animation
* this.$indicator.animate({
* 'opacity': 1
* }, this.timeout - this.timeElapsed, 'linear');
* };
*
* XA.component.carousels.IndicatorViews.SampleView = SampleView;
* }());
*
*/
XA.component.carousels.IndicatorViews = (function($) {
var pub = {},
supportElementStyle = null;
/**
* Checks if the CSS properties are supported.
*
* @param {Array} props array of properties to be checked
* @return true if any of CSS properties is supported, false otherwise
* @type Boolean
*/
function isCssSupported(props) {
var i,
isSupported = false;
if (supportElementStyle === null) {
supportElementStyle = document.createElement('supportElement').style;
}
for (i = 0; i < props.length; i++) {
if (supportElementStyle[props[i]] !== undefined) {
isSupported = true;
break;
}
}
return isSupported;
}
/**
* Checks if CSS 3 'transform' is supported.
*
* @return true if CSS 3 'transform' is supported, false otherwise
* @type Boolean
*/
function isTransformSupported() {
return isCssSupported(['transformProperty', 'WebkitTransform', 'MozTransform', 'OTransform', 'msTransform']);
}
/**
* XA.component.carousels.IndicatorViews.Tools namespace
*
This namespace can be removed if RotatorView class is not used (@see RotatorView)
*/
pub.Tools = {};
pub.Tools.isCssSupported = isCssSupported;
pub.Tools.isTransformSupported = isTransformSupported;
/**
* End of namespace
*/
/**
* View class is a base class of all 'time indicator' views
* @constructor
* @param {String} selector a selector used for searching
* @param {Object} options View options object
*/
/* eslint-disable */
function View(selector, options) {
/**
* A jQuery object containing element that will be animated to show time progress.
*
* @member View
*/
this.$indicator = $(selector);
}
/* eslint-enable */
/**
* Initializes the view.
*
* @member Viewion
* @param {Number} timeout a timeout to the end of whole indicator animation
*/
View.prototype.init = function(timeout) {
this.timeElapsed = 0;
this.timeout = timeout;
};
/**
* Resets the view
*
* @member View
*/
View.prototype.reset = function() {
this.timeElapsed = 0;
};
/**
* Starts/Renews the view animation.
*
* @member View
*/
View.prototype.play = function() {};
/**
* Pauses the view animation.
*
* @member View
*/
View.prototype.pause = function() {};
/**
* Updates the time that elapsed since animation began.
*
* @member View
*/
View.prototype.update = function(timeElapsed) {
this.timeElapsed = timeElapsed;
};
/**
* end of View class
*/
pub.View = View;
/**
* ProgressBarView class provides a logic for animate a 'time indicator',
* that looks like progress bar.
*
* Options object:
* {
* 'opacity': Number value //between 0 and 1; default is 0.8
* }
*
* This class can be removed if this kind of view is not used.
*
* @constructor
* @param {String} selector a selector used for searching
* @param {Object} options View options object
*/
function ProgressBarView(selector, options) {
View.call(this, selector);
var defaults = {
'opacity': 0.8
};
this.settings = $.extend(true, {}, defaults, options);
this.$container = this.$indicator;
this.$indicator = this.$indicator.find('div');
}
ProgressBarView.prototype = new View();
ProgressBarView.constructor = ProgressBarView;
/**
* @member ProgressBarView
*/
ProgressBarView.prototype.init = function(timeout) {
View.prototype.init.call(this, timeout);
var opacity = this.settings.opacity;
this.$container.css('opacity', opacity);
this.$indicator.css('opacity', opacity);
};
/**
* @member ProgressBarView
*/
ProgressBarView.prototype.reset = function() {
View.prototype.reset.call(this);
this.$indicator.stop(true);
this.$indicator.css('width', '0px');
};
/**
* @member ProgressBarView
*/
ProgressBarView.prototype.pause = function() {
this.$indicator.stop(true);
};
/**
* @member ProgressBarView
*/
ProgressBarView.prototype.play = function() {
this.$indicator.animate({
'width': '100%'
}, this.timeout - this.timeElapsed, 'linear');
};
/**
* end of ProgressBarView class
*/
pub.ProgressBarView = ProgressBarView;
/**
* RotatorView class provides a logic for animate a 'time indicator',
* that shows the progress of time with the filling up ring
*
* Options object:
* {
* 'opacity': Number value //between 0 and 1; default is 0.5
* }
*
* This class can be removed if this kind of view is not used.
*
* @constructor
* @param {String} selector a selector used for searching
* @param {Object} options View options object
*/
function RotatorView(selector, options) {
View.call(this, selector);
var defaults = {
'opacity': 0.5
};
this.settings = $.extend(true, {}, defaults, options);
this.$mask = this.$indicator.find('span.mask').first();
this.$rotator = this.$indicator.find('span.rotator').first();
function rotateCss(deg) {
var value = 'rotate(' + deg + 'deg)';
return {
'-webkit-transform': value,
'-moz-transform': value,
'-o-transform': value,
'-ms-transform': value,
'transform': value
};
}
function animateRotator($rotator, options) {
var defaults = {
'fontSize': '180px',
'animateOptions': {
'step': function(now) {
$rotator.css(rotateCss(now));
},
'easing': 'linear'
}
},
settings = $.extend(true, {}, defaults, options);
$rotator.animate({
'font-size': settings.fontSize
},
settings.animateOptions);
}
function phaseTwoAnimation($mask, $rotator, duration) {
$rotator.addClass('half');
$mask.addClass('half');
animateRotator($rotator, {
'fontSize': '360px',
'animateOptions': {
'duration': duration
}
});
}
function phaseOneAnimation($mask, $rotator, duration, phaseTwoDuration) {
animateRotator($rotator, {
'animateOptions': {
'duration': duration,
'complete': function() {
phaseTwoAnimation($mask, $rotator, phaseTwoDuration);
}
}
});
}
this.resetCss = function() {
this.$rotator.removeClass('half');
this.$rotator.css(rotateCss(0));
this.$rotator.css('font-size', '0px');
this.$mask.removeClass('half');
};
this.playPhaseOne = function() {
var duration = this.timeout / 2 - this.timeElapsed;
phaseOneAnimation(this.$mask, this.$rotator, duration, this.timeout / 2);
};
this.playPhaseTwo = function() {
var duration = this.timeout - this.timeElapsed;
phaseTwoAnimation(this.$mask, this.$rotator, duration);
};
}
RotatorView.prototype = new View();
RotatorView.constructor = RotatorView;
/**
* @member RotatorView
*/
RotatorView.prototype.init = function(timeout) {
View.prototype.init.call(this, timeout);
if (isTransformSupported()) {
this.$indicator.show();
this.$mask.css('opacity', this.settings.opacity);
}
};
/**
* @member RotatorView
*/
RotatorView.prototype.reset = function() {
View.prototype.reset.call(this);
this.$rotator.stop(true);
this.resetCss();
};
/**
* @member RotatorView
*/
RotatorView.prototype.pause = function() {
this.$rotator.stop(true);
};
/**
* @member RotatorView
*/
RotatorView.prototype.play = function() {
var phase = this.playPhaseOne;
if (this.timeElapsed >= this.timeout / 2) {
phase = this.playPhaseTwo;
}
phase.call(this);
};
/**
* end of RotatorView class
*/
pub.RotatorView = RotatorView;
return pub;
}(jQuery));
/**
* end of XA.component.carousels.IndicatorViews module.
*/