/*--------------------------------------------------------
* Copyright (c) 2011, The Dojo Foundation
* This software is distributed under the "Simplified BSD license",
* the text of which is available at http://www.winktoolkit.org/licence.txt
* or see the "license.txt" file for more details.
*--------------------------------------------------------*/
/**
* @fileOverview Implements a carousel
*
* @author Jerome GIRAUD, William MARKLE
*/
/**
* The event is fired when there is an item switch
*
* @name wink.ui.xy.Carousel#/carousel/events/switch
* @event
*
* @param {object} param The parameters object
* @param {integer} param.carouselId The uId of the carousel triggering the event
* @param {integer} param.currentItemIndex The current carousel item
*/
define(['../../../../_amd/core'], function(wink)
{
/**
* @class Implements a carousel
*
* Built to add a Carousel in your page. You can insert images or DOM nodes inside your Carousel.
* The navigation is handled through touch events (a gesture on the left or on the right will make it switch items).
* The carousel also handles the click events on its items.
* Note that it could also be used with the 'history' component to handle the 'back' and 'forward' buttons in a custom way
*
* The Carousel needs properties to define its behaviour and its items. As all other graphical components,
* it has a getDomNode method that should be used after the instantiation to add the carousel node to the page.
* The code sample shows how to instantiate a new carousel and to add it to a section of a webpage.
*
* @param {object} properties The properties object
* @param {integer} [properties.itemsWidth=250] The width of the items of the Carousel
* @param {integer} [properties.itemsHeight=100] The height of the items of the Carousel
* @param {string} [properties.display="horizontal"] Either vertical or horizontal
* @param {integer} [properties.displayDots=1] Whether or not to display the position indicators
* @param {integer} [properties.autoAdjust=1] Should the Carousel auto-adjust items position after each movement
* @param {integer} [properties.autoAdjustDuration=800] The transition duration for the auto adjust slide
* @param {integer} [properties.autoPlay=0] Does the Carousel automatically starts sliding
* @param {integer} [properties.autoPlayDuration=800] The time interval between two autoplays
* @param {integer} [properties.firstItemIndex=1] The item to be displayed in the center of the page at startup
* @param {integer} [properties.containerWidth=window.innerWidth] The width of the div containing the carousel
* @param {string} [properties.itemsAlign="center"] The alignment of the first item of the carousel (either "left" or "center")
* @param {array} properties.items An array containing the items of the carousel
* @param {string} properties.items.type The type of the content (can be a DOM node or a string)
* @param {string|HTMLElement} properties.items.content The content of the item
* @param {boolean} [properties.touchPropagation=true] Indicates whether the touch event on the Carousel must be propagated
*
* @example
*
* var properties =
* {
* itemsWidth: 280,
* itemsHeight: 136,
* autoAdjust: 1,
* autoAdjustDuration: 400,
* autoPlay: 1,
* autoPlayDuration: 4000,
* firstItemIndex: 2,
* items:
* [
* {'type': 'string', 'content': '<img src="../img/carousel_image_01.png" onclick="alert(1)" />'},
* {'type': 'string', 'content': '<img src="../img/carousel_image_02.png" onclick="alert(2)" />'},
* {'type': 'string', 'content': '<img src="../img/carousel_image_03.png" onclick="alert(3)"/>'}
* ]
* }
*
* carousel = new wink.ui.xy.Carousel(properties);
*
* wink.byId('output').appendChild(carousel.getDomNode());
*
* @compatibility iOS2, iOS3, iOS4, iOS5, iOS6, Android 1.5, Android 2.1, Android 2.2, Android 2.3, Android 3.0, Android 3.1, Android 4.0, Android 4.1.2, BlackBerry 6, BlackBerry 7, BB10, Bada 1.0, Windows Phone 8
*
* @see Test page
* @see Test page (vertical)
* @see Test page (add, remove items)
*/
wink.ui.xy.Carousel = function(properties)
{
/**
* Unique identifier
*
* @property uId
* @type integer
*/
this.uId = wink.getUId();
/**
* The list of carousel items
*
* @property items
* @type array
*/
this.items = [];
/**
* The item to be displayed in the center of the page at startup
*
* @property firstItemIndex
* @type integer
* @default 1
*/
this.firstItemIndex = 1;
/**
* The width of the items of the Carousel
*
* @property itemsWidth
* @type integer
* @default 250
*/
this.itemsWidth = 250;
/**
* The height of the items of the Carousel
*
* @property itemsHeight
* @type integer
* @default 100
*/
this.itemsHeight = 100;
/**
* The type of display: either vertical or horizontal
*
* @property display
* @type string
* @default horizontal
*/
this.display = this._HORIZONTAL_POSITION;
/**
* The width of the div containing the carousel
*
* @property containerWidth
* @type integer
* @default window.innerWidth
*/
this.containerWidth = window.innerWidth;
/**
* The height of the div containing the carousel
*
* @property containerHeight
* @type integer
*/
this.containerHeight = null;
/**
* Whether or not to display the position indicators
*
* @property displayDots
* @type integer
* @default 1
*/
this.displayDots = 1;
/**
* Should the Carousel auto-adjust items position after each movement
*
* @property autoAdjust
* @type integer
* @default 1
*/
this.autoAdjust = 1;
/**
* The transition duration for the auto adjust slide
*
* @property autoAdjustDuration
* @type integer
* @default 800
*/
this.autoAdjustDuration = 800;
/**
* Does the Carousel automatically starts sliding
*
* @property autoPlay
* @type integer
* @default 0
*/
this.autoPlay = 0;
/**
* The time interval between two autoplays
*
* @property autoPlayDuration
* @type integer
* @default 3000
*/
this.autoPlayDuration = 3000;
/**
* The alignment of the first item of the carousel
*
* @property itemsAlign
* @type string
* @default center
*/
this.itemsAlign = this._CENTER_POSITION;
/**
* Indicates whether the touch event on the Carousel must be propagated
*
* @property touchPropagation
* @type boolean
* @default true
*/
this.touchPropagation = true;
this._currentItemIndex = 0;
this._containerWidthSet = 0;
this._beginXY = 0;
this._currentXY = 0;
this._position = 0;
this._iD = 0;
this._cD = 0;
this._minXY = 0;
this._maxXY = 0;
this._autoPlayInterval = null;
this._autoPlayDirection = 1;
this._startEvent = null;
this._endEvent = null;
this._itemsList = [];
this._dotsList = [];
this._domNode = null;
this._headerNode = null;
this._itemsNode = null;
this._dotsNode = null;
this._footerNode = null;
wink.mixin(this, properties);
if ( this._validateProperties() === false )return;
if ( wink.isSet(properties.containerWidth) )this._containerWidthSet = 1;
this._initProperties();
this._initDom();
this._positionItems();
this._initListeners();
};
wink.ui.xy.Carousel.prototype =
{
_LEFT_POSITION: 'left',
_CENTER_POSITION: 'center',
_VERTICAL_POSITION: 'vertical',
_HORIZONTAL_POSITION: 'horizontal',
/**
* Returns the dom node containing the Carousel
*
* @returns {HTMLElement} The main dom node
*/
getDomNode: function()
{
return this._domNode;
},
/**
* Cleans the dom of the Carousel content nodes. To invoke only if Carousel no longer used.
*/
clean: function()
{
for (var i = 0; i < this._itemsList.length; i++) {
this._itemsList[i].getDomNode().innerHTML = '';
}
this._domNode.innerHTML = '';
},
/**
* Display the selected item
*
* @param {integer} index The index of the item we want to move to
*/
goToItem: function(index)
{
var l = this._itemsList.length;
for ( var i=0; i length of array)
*/
insertItem: function(type, content, index)
{
var l = this._itemsList.length,
dot,
i;
if (wink.isUndefined(index))
{
wink.log('[Carousel] The index parameter is required');
return;
}
if (index < 0 || index >= this._itemsList.length)
{
index = l;// append item
}
this._addItem(type, content, index);
if (this._itemsNode.hasChildNodes() && index < l)
{
this._itemsNode.insertBefore(this._itemsList[index].getDomNode(), this._itemsNode.childNodes[index]);
} else
{
this._itemsNode.appendChild(this._itemsList[index].getDomNode());
}
this._dotsList = [];
while (this._dotsNode.firstChild)
{
this._dotsNode.removeChild(this._dotsNode.firstChild);
}
for ( i=0; i <= l; i++)//go through old and new items and fix up classes and indexes
{
this._itemsList[i].index = i - this.firstItemIndex;
dot = document.createElement('div');
dot.className = 'ca_dot';
if ( i == l )
{
dot.style.clear = 'both';
}
this._dotsList.push(dot);
this._dotsNode.appendChild(dot);
}
this._positionItems();
this.goToItem(index);
},
/**
* Remove an item from an existing Carousel
*
* @param {string} index The position of the node to remove
*/
removeItem: function(index)
{
var l = this._itemsList.length - 1,
dot,
i;
if (wink.isUndefined(index))
{
wink.log('[Carousel] The index parameter is required');
return;
}
if (index < 0 || index >= this._itemsList.length)
{
wink.log('[Carousel] The index parameter must be a positive integer from 0 up to the number of nodes.');
return;
}
this._itemsList.splice(index, 1);
this._itemsNode.removeChild(this._itemsNode.childNodes[index]);
this._dotsList = [];
while (this._dotsNode.firstChild)
{
this._dotsNode.removeChild(this._dotsNode.firstChild);
}
for ( i=0; i < l; i++)//go through items and fix up classes and indexes
{
this._itemsList[i].index = i - this.firstItemIndex;
dot = document.createElement('div');
dot.className = 'ca_dot';
if ( i == (l - 1) )
{
dot.style.clear = 'both';
}
this._dotsList.push(dot);
this._dotsNode.appendChild(dot);
}
if (l > 0)
{
this._positionItems();
this.goToItem(index == 0 ? 0 : index - 1);
}
},
/**
* Add a new item in the Carousel
*
* @param {string} type The type of the content ("node" or "string")
* @param {string|HTMLElement} content The content of the item
* @param {integer} index The position of the item in the carousel
*/
_addItem: function(type, content, index)
{
var node, item;
if ( type == 'node' )
{
node = content;
} else
{
node = document.createElement('div');
node.innerHTML = content;
}
item = new wink.ui.xy.Carousel.Item({'width': this.itemsWidth, 'height': this.itemsHeight, 'node': node, 'index': (index-this.firstItemIndex)});
this._itemsList.splice(index, 0, item);
},
/**
* Listen to the start events
*
* @param {wink.ux.Event} event The start event
*/
_touchStart: function(event)
{
this._startEvent = event;
var _c;
if (this.display == this._HORIZONTAL_POSITION)
{
_c = event.x;
} else
{
_c = event.y;
}
if (this.touchPropagation == false)
{
this._startEvent.stopPropagation();
}
if ( this._autoPlayInterval != null )
{
clearInterval(this._autoPlayInterval);
this._autoPlayInterval = null;
}
this._beginXY = _c;
wink.fx.apply(this._itemsNode, {
"transition-duration": '',
"transition-timing-function": ''
});
},
/**
* Listen to the move events
*
* @param {wink.ux.Event} event The move event
*/
_touchMove: function(event)
{
var _c;
if (this.display == this._HORIZONTAL_POSITION)
{
_c = event.x;
} else
{
_c = event.y;
}
// If the auto adjust parameter is not set, stop the movement at both ends of the carousel
if ( (this.autoAdjust == 0) && (((this._currentXY + _c - this._beginXY) > this._minXY) || ((this._currentXY + _c - this._beginXY)< this._maxXY )))
{
return;
}
// Update items positions
var l = this._itemsList.length;
for ( var i=0; i= (this._itemsList.length-1) )
this._autoPlayDirection = -1;
if ( this._currentItemIndex <= 0 )
this._autoPlayDirection = 1;
if ( this._autoPlayDirection == 1 )
{
this.goToItem(this._currentItemIndex+1);
} else
{
this.goToItem(this._currentItemIndex-1);
}
},
/**
* Initialize the 'touch' and orientation change listeners
*/
_initListeners: function()
{
wink.ux.touch.addListener(this._itemsNode, "start", { context: this, method: "_touchStart", arguments: null }, { preventDefault: true, touchAction: "double-tap-zoom" });
wink.ux.touch.addListener(this._itemsNode, "move", { context: this, method: "_touchMove", arguments: null }, { preventDefault: true });
wink.ux.touch.addListener(this._itemsNode, "end", { context: this, method: "_touchEnd", arguments: null }, { preventDefault: true });
window.addEventListener("orientationchange", wink.bind(function(){this._updateItemsPosition();}, this), false);
},
/**
* Initialize the Carousel DOM nodes
*/
_initDom: function()
{
this._domNode = document.createElement('div');
if ( this.display == this._HORIZONTAL_POSITION )
{
this._domNode.className = 'ca_container';
} else
{
this._domNode.className = 'ca_container ca_vertical';
}
this._headerNode = document.createElement('div');
this._headerNode.className = 'ca_header';
this._itemsNode = document.createElement('div');
this._itemsNode.className = 'ca_items';
this._itemsNode.style.height = this.itemsHeight + 'px';
this._dotsNode = document.createElement('div');
this._dotsNode.className = 'ca_dots';
this._footerNode = document.createElement('div');
this._footerNode.className = 'ca_footer';
var l = this._itemsList.length;
for ( var i=0; i