/** * @class Ext.carousel.Carousel * @extends Ext.Panel * * Carousels, like [tabs](#!/guide/tabs), are a great way to allow the user to swipe through multiple full-screen pages. * A Carousel shows only one of its pages at a time but allows you to swipe through with your finger. * * Carousels can be oriented either horizontally or vertically and are easy to configure - they just work like any other * Container. Here's how to set up a simple horizontal Carousel: * * @example * Ext.create('Ext.Carousel', { * fullscreen: true, * * defaults: { * styleHtmlContent: true * }, * * items: [ * { * html : 'Item 1', * style: 'background-color: #5E99CC' * }, * { * html : 'Item 2', * style: 'background-color: #759E60' * }, * { * html : 'Item 3' * } * ] * }); * * We can also make Carousels orient themselves vertically: * * @example preview * Ext.create('Ext.Carousel', { * fullscreen: true, * direction: 'vertical', * * defaults: { * styleHtmlContent: true * }, * * items: [ * { * html : 'Item 1', * style: 'background-color: #759E60' * }, * { * html : 'Item 2', * style: 'background-color: #5E99CC' * } * ] * }); * * ### Common Configurations * * {@link #ui} defines the style of the carousel * * {@link #direction} defines the direction of the carousel * * {@link #indicator} defines if the indicator show be shown * * ### Useful Methods * * {@link #next} moves to the next card * * {@link #previous} moves to the previous card * * {@link #setActiveItem} moves to the passed card * * ## Further Reading * * For more information about Carousels see the [Carousel guide](#!/guide/carousel). */ Ext.define('Ext.carousel.Carousel', { extend: 'Ext.dataview.ComponentView', xtype: 'carousel', alternateClassName: 'Ext.Carousel', requires: ['Ext.carousel.Indicator', 'Ext.util.SizeMonitor'], config: { /** * @cfg {String} baseCls * The base CSS class to apply to the Carousel's element * @accessor */ baseCls: Ext.baseCSSPrefix + 'carousel', /** * @cfg {String} itemCls * The CSS class to apply each card in the carousel * @accessor */ itemCls: Ext.baseCSSPrefix + 'carousel-item', /** * @cfg {String} ui * Style options for Carousel. Default is 'dark'. 'light' is also available. * @accessor */ ui: 'dark', /** * @cfg {Boolean} indicator * Provides an indicator while toggling between child items to let the user * know where they are in the card stack. * @accessor */ indicator: true, /** * @cfg {String} direction * The direction of the Carousel. Default is 'horizontal'. 'vertical' also available. * @accessor */ direction: 'horizontal', scrollable: false }, // @private initialize: function() { this.callParent(); this.element.on({ drag: 'onDrag', dragstart: 'onDragStart', dragend: 'onDragEnd', scope: this }); this.on({ painted: 'onPainted', activeitemchange: 'onActiveItemChange', scope: this }); }, // @private updateCardSize: function() { this.currentSize = this.element.getSize(); this.currentScroll = { x: 0, y: 0 }; var cards = this.getInnerItems(), ln = cards.length, i, card; for (i = 0; i < ln; i++) { card = cards[i]; if (this.isIndexInRange(i)) { this.updateCardPosition(card); } } }, // @private onPainted: function() { this.updateCardSize(); if (!this.sizeMonitor) { // @TODO: this should be working but somehow its not this.sizeMonitor = new Ext.util.SizeMonitor({ element: this.element, callback: this.updateCardSize, scope: this }); } else { this.sizeMonitor.refresh(); } this.onActiveItemChange(this, this.getActiveItem()); }, // @private onAdd: function(item, index) { if (!item.isInnerItem()) { return this.callParent(arguments); } item.element.addCls(this.getItemCls()); item.hide(); this.callParent(arguments); var isCardInRange = this.isCardInRange(item), activeItem = this.getActiveItem(), indicator = this.getIndicator(); if (isCardInRange) { if (this.isPainted() && activeItem !== item) { this.updateCardPosition(item); } item.show(); } if (indicator) { indicator.addIndicator(); } }, // @private onRemove: function(item, index) { if (item.isInnerItem()) { if (!this.isCardInRange(item)) { item.show(); } item.element.removeCls(this.getItemCls()); } this.callParent(arguments); var indicator = this.getIndicator(); if (indicator) { indicator.removeIndicator(); } }, // @private updateCardPosition: function(card, index, activeIndex) { card.element.dom.style.webkitTransform = this.getCardTransform(this.getCardOffset(card, index, activeIndex)); }, // @private getCardTransform: function(offset) { if (this.getDirection() === 'horizontal') { return 'translate3d(' + offset + 'px, 0px, 0px)'; } else { return 'translate3d(0px, ' + offset + 'px, 0px)'; } }, /** * Returns the amount of pixels from the current drag to a card. * @private */ getCardOffset: function(card, index, activeIndex) { var cardOffset = this.getCardIndexOffset(card, index, activeIndex), currentSize = this.currentSize || { width: 0, height: 0 }, currentScroll = this.currentScroll; return this.getDirection() === 'horizontal' ? (cardOffset * currentSize.width) + currentScroll.x : (cardOffset * currentSize.height) + currentScroll.y; }, /** * Returns the difference between the index of the active card and the passed card. * @private */ getCardIndexOffset: function(card, index, activeIndex) { if (index === undefined) { index = this.getInnerItems().indexOf(card); } if (activeIndex === undefined) { activeIndex = this.getActiveIndex(); } return index - activeIndex; }, /** * Returns true if the passed card is within 2 cards from the active card. * @private */ isCardInRange: function(card) { return Math.abs(this.getCardIndexOffset(card)) <= 1; }, // @private isIndexInRange: function(index, activeIndex) { if (activeIndex === undefined) { activeIndex = this.getActiveIndex(); } return Math.abs(index - activeIndex) <= 1; }, /** * Returns the index of the currently active card. * @return {Number} The index of the currently active card. */ getActiveIndex: function() { return this.getInnerItems().indexOf(this.getActiveItem()); }, // @private updateItemCls: function(newItemCls, oldItemCls) { if (oldItemCls) { // @TODO: update all the child item cls's } }, // @private updateDirection: function(direction) { // @TODO: implement logic to update the direction of the carousel }, // @private applyIndicator: function(indicator) { return Ext.factory(indicator ? {direction: this.getDirection()} : null, Ext.carousel.Indicator, this.getIndicator()); }, // @private updateIndicator: function(indicator) { if (indicator) { this.add(indicator); var activeIndex = this.getActiveIndex(); if (activeIndex !== -1) { indicator.setActiveIndex(activeIndex); } indicator.setUi(this.getUi()); indicator.on({ next: 'next', previous: 'previous', scope: this }); } }, // @private onDrag: function(e, t) { var activeIndex = this.getActiveIndex(), cards = this.getInnerItems(), ln = cards.length, deltaX = e.deltaX, deltaY = e.deltaY, i, card; this.currentScroll = { x: deltaX, y: deltaY }; // Slow the drag down in the bounce // If this is a horizontal carousel if (this.getDirection() == 'horizontal') { if ( // And we are on the first card and dragging left (activeIndex == 0 && deltaX > 0) || // Or on the last card and dragging right (activeIndex == cards.length - 1 && deltaX < 0) ) { // Then slow the drag down this.currentScroll.x = deltaX / 2; } } // If this is a vertical carousel else { if ( // And we are on the first card and dragging up (activeIndex == 0 && deltaY > 0) || // Or on the last card and dragging down (activeIndex == cards.length - 1 && deltaY < 0) ) { // Then slow the drag down this.currentScroll.y = deltaY / 2; } } // This will update all the cards to their correct position based on the current drag for (i = 0; i < ln; i++) { if (this.isIndexInRange(i, activeIndex)) { this.updateCardPosition(cards[i]); } } }, // @private onDragStart: function(e) { e.stopPropagation(); }, // @private onDragEnd: function(e, t) { var cards = this.getInnerItems(), activeIndex = this.getActiveIndex(), previousDelta, deltaOffset; if (this.getDirection() === 'horizontal') { deltaOffset = e.deltaX; previousDelta = e.previousDeltaX; } else { deltaOffset = e.deltaY; previousDelta = e.previousDeltaY; } // We have gone to the right if (deltaOffset < 0 && Math.abs(deltaOffset) > 3 && previousDelta <= 0 && cards[activeIndex+1]) { this.next(); } // We have gone to the left else if (deltaOffset > 0 && Math.abs(deltaOffset) > 3 && previousDelta >= 0 && cards[activeIndex-1]) { this.previous(); } else { // drag back to current active card this.onActiveItemChange(this, this.getActiveItem()); } }, // @private onActiveItemChange: function(carousel, activeItem) { var cards = this.getInnerItems(), ln = cards.length, activeIndex = cards.indexOf(activeItem), indicator = this.getIndicator(), i, card; if (activeIndex == -1) { return; } this.currentScroll = { x: 0, y: 0 }; for (i = 0; i < ln; i++) { card = cards[i]; if (this.isIndexInRange(i, activeIndex)) { this.updateCardPosition(card, i, activeIndex); card.show(); } else { card.hide(); } } if (indicator) { indicator.setActiveIndex(activeIndex); } }, /** * Switches to the next card * @return {Ext.carousel.Carousel} this */ next: function() { var next = this.getInnerItems()[this.getActiveIndex()+1]; if (next) { this.setActiveItem(next); } return this; }, /** * Switches to the previous card * @return {Ext.carousel.Carousel} this */ previous: function() { var prev = this.getInnerItems()[this.getActiveIndex()-1]; if (prev) { this.setActiveItem(prev); } return this; }, // @private destroy: function() { if (this.sizeMonitor) { this.sizeMonitor.destroy(); } this.callParent(); } }, function() { //<deprecated product=touch since=2.0> /** * Switches to the previous card * @member Ext.carousel.Carousel * @method prev * @deprecated */ Ext.deprecateClassMethod(this, 'prev', 'previous'); /** * Returns true if the Carousel indicators are arranged horizontally * @member Ext.carousel.Carousel * @method isHorizontal * @deprecated */ Ext.deprecateClassMethod(this, 'isHorizontal', function() { return this.getDirection() == 'horizontal'; }, 'isHorizontal is deprecated, please use this.getDirection()'); /** * Returns true if the Carousel indicators are arranged vertically * @member Ext.carousel.Carousel * @method isVertical * @deprecated */ Ext.deprecateClassMethod(this, 'isVertical', function() { return this.getDirection() == 'vertical'; }, 'isVertical is deprecated, please use this.getDirection()'); //</deprecated> });