PDA

View Full Version : Infinite carousel



DirectX
20 Jul 2010, 9:48 PM
Hi everyone!

I'm trying to write app that must display dozens of articles in carousel (~50). Adding them at once causes to very poor performance. I decided to use only three cards at a time, preload next when last card is displayed and remove first one (for forward scroll).

I added handling event 'activate' on last (of three) carousel's item. In that event I added new item. But new item was with worng dimensions so doLayout was needed. The problem was that calling doLayout() caused recursive firing of 'activate', so firstly I unregister event (and encounter such a bug (http://www.sencha.com/forum/showthread.php?104383-OPEN-145-remove-event-listener-from-panel-failed)), add new item, doLayout(), register 'activate' for it.

This part work fine - when I scroll to last item new will appear ahead. But when I added removing of first card odd behaivor now take place - there is no smooth scroll of last card. The next card shows but visually it like a bouncing.


var card0 = new Ext.Panel({html: 'card 0'});
var card1 = new Ext.Panel({html: 'card 1'});
var card2 = new Ext.Panel({html: 'card 2'});

var carousel = new Ext.Carousel({
direction: 'vertical',
items: [card0, card1, card2],
});

card2.on('activate', getNext);

function getNext(e)
{
e.un('activate', getNext);
var newCard = new Ext.Panel({html: 'some fetched text'});
carousel.add(newCard);
carousel.remove(0);
carousel.doLayout();
newCard.on('activate', getNext);
return true;
}

The task like this was found in this thread: http://www.sencha.com/forum/showthread.php?104788-Need-help-dynamically-adding-cards-to-a-carousel. But even there I haven't seen deletion of old items. Is it possible seamlessly?

DirectX
29 Jul 2010, 3:42 AM
SOLVED!

This code assumes a function that fetch one of N cards [0...N-1]. Only three of them are being displayed at a time. No cards looping provided (this can be easily fixed in fetchCard function by using '% N'). Any improvements are welcome.

virtual-carousel.js


Ext.ns('Ext.ux');

Ext.ux.VirtualCarousel = Ext.extend(Ext.Carousel, {
firstCardOffset: 0, // Absolute card index in cardflow
ui: 'none', // Needs defining style: .x-carousel-indicator-none { display: none; }

initComponent : function() {
Ext.ux.VirtualCarousel.superclass.initComponent.call(this);

// Adding initial items
this.add(this.fetchCard(0));
this.add(this.fetchCard(1));
this.doComponentLayout();
},

onCardSwitch: function (card) {
Ext.ux.VirtualCarousel.superclass.onCardSwitch.call(this, card);

var currentCardIndex = this.items.indexOf(card);

if (this.items.length - currentCardIndex <= 1) {
this.scroller.on('scrollend', function () {
var firstTime = this.items.length < 3;
var nextCard = this.fetchCard(this.firstCardOffset + currentCardIndex + 1);
if (nextCard) {
this.add(nextCard);
this.doComponentLayout();
if (this.items.length >= 3 && !firstTime)
{
this.remove(0);
this.firstCardOffset++;
this.selectCard(1);
}
}
}, this, { single: true });
}

if (currentCardIndex < 1) {
this.scroller.on('scrollend', function () {
var previousCard = this.fetchCard(this.firstCardOffset + currentCardIndex - 1);
if (previousCard) {
this.insert(0, previousCard);
this.doComponentLayout();
this.selectCard(1);
if (this.items.length >= 3)
{
this.remove(this.items.length - 1);
this.firstCardOffset--;
}
}

}, this, { single: true });
}
},

// setCard() equivalent without animation and firing events
selectCard: function (card) {
card = this.items.items[card];
var cardPos = { x: 0, y: 0 };
if (this.direction == 'horizontal') {
cardPos.x = card.el.dom.offsetLeft;
}
else {
cardPos.y = card.el.dom.offsetTop;
}
this.scroller.scrollTo(cardPos, 0, 'ease-out');
},

// Virtual, needs override
fetchCard: function (pageIndex) {
return null;
},
});


Using:


var carousel = new Ext.ux.VirtualCarousel({
direction: 'vertical',
fetchCard: function(index) {
return index >= 0 && index < 10 ?
new Ext.Panel({html: 'card - ' + index.toString()}) :
null; },
});

freshface
30 Aug 2010, 2:32 PM
Hi DirectX

Is it possible to post a zip of your code working?
I have the same problem.

Regards

DirectX
2 Sep 2010, 11:12 PM
The previous post contains working example (not perfect but better then none for start point). Maybe this article be more useful: http://www.sencha.com/blog/2010/08/10/infinite-ajax-carousel-shopstyle-demo-app/

lteti77
4 Sep 2010, 7:31 AM
i tried to run your code but for me it doesn't work... it doesn't load new panels when switching cards...
i can't understand what it's wrong...

DirectX
5 Sep 2010, 12:07 AM
I'm still using 0.91b and with new version it seems not to work :( I'll figure it out next week to make code compatible.

psramkumar
28 Jan 2011, 12:16 AM
Hi DirectX,
i appreciate your work, i tried the same it works fine for me, but i wanted to know how do we add card dynamically using data store.
now i can able to add in side the virtual-carosel.js like below
this.add(this.fetchCard(0));
this.add(this.fetchCard(1));
this.add(this.fetchCard(2));
this.add(this.fetchCard(3)); up to n.

do u have any other way to adding datastore right a way inside

Thanks in Advance

DirectX
29 Jan 2011, 6:22 AM
Hi, psramkumar,

Currntly I was moving aside from described above to Buffered Carousel (http://www.sencha.com/blog/2010/08/10/infinite-ajax-carousel-shopstyle-demo-app/). Here is the full code which i'm slightly modified (modification concerned behaviour in case of nested carousel - e.g. horizontal inside vertical - original design have had issues):

buffered-carousel.js

Ext.ns("SS");

SS.BufferedCarousel = Ext.extend(Ext.Carousel, {
constructor: function(options) {
options = Ext.apply({}, options, {
bufferSize: 2,
busy: false,
});
SS.BufferedCarousel.superclass.constructor.call(this, options);
},

initComponent: function() {
this.items = [];
var indicator = this.indicator;

SS.BufferedCarousel.superclass.initComponent.apply(this, arguments);

if (indicator) {
this.indicator = new SS.BufferedCarousel.Indicator({
carousel: this
});
}
},

afterRender: function() {
SS.BufferedCarousel.superclass.afterRender.apply(this, arguments);
if (this.handleCardSwitch)
this.mon(this, 'cardswitch', this.handleCardSwitch, this);
this.bufferCards(this.initialCarouselPosition || 0);
this.el.repaint();
},

onBeforeCardSwitch : function(newCard, oldCard, newIndex, animated) {
SS.BufferedCarousel.superclass.onBeforeCardSwitch.apply(this, arguments);
var me = this.body;
me.un('touchstart');
me.on('touchstart', function(e) {
e.stopEvent();
});
},

onCardSwitch : function(newCard, oldCard, newIndex, animated) {
SS.BufferedCarousel.superclass.onCardSwitch.apply(this, arguments);
this.bufferCards(newCard.carouselPosition);
var me = this.body;
me.un('touchstart');
},

/**
* Creates/removes cards to the left and right of the current card
*/
bufferCards: function(index) {
// Quick return if there is nothing to do
if (this.lastBufferedIndex == index) { return; }
this.lastBufferedIndex = index;

this.fireEvent('buffer', this, index);

// Initialize variables
var
// size of the window
bufferSize = this.bufferSize,
// constrained start index of the window
start = (index-bufferSize).constrain(0, this.itemCount-1),
// constrained end index of the window
end = (index+bufferSize).constrain(0, this.itemCount-1),
items = this.items,
// flag to determine if any items were added/removed
changed = false,
// will be set to the item where its position == index
activeCard;

// make sure the index is within bounds
index = index.constrain(0, this.itemCount-1);

// cull existing items
var i = 0;
while (i < items.length) {
var item = items.get(i),
itemIndex = item.carouselPosition;

if (itemIndex < start || itemIndex > end) {
this.remove(item, true);
changed = true;
}
else {
i++;
}
}

// function to create a card and add to the carousel
var createCard = function(carouselPos, layoutPos) {
var card = this.createItem(i);
if (card) {
card.carouselPosition = carouselPos;
if (layoutPos != null) {
this.insert(layoutPos, card);
}
else {
this.add(card);
}
if (carouselPos == index) {
activeCard = card;
}
changed = true;
}
};

// add new items
if (items.length) { // if existing items, add to the left and right
var first = items.first().carouselPosition,
last = items.last().carouselPosition;
for (var i = first-1; i>=start; i--) {
if (i >= 0) {
createCard.call(this, i, 0);
}
}

for (var i = last+1; i<=end; i++) {
createCard.call(this, i);
}
}
else { // if no existing items, just add cards
for (var i = start; i<=end; i++) {
if (i >= 0) {
createCard.call(this, i);
}
}
}

// if changed, make sure the layout is updated
// also, update the active item if needed
if (changed) {
this.doLayout();

var activeItem = this.layout.getActiveItem();
if (activeCard && activeItem != activeCard) {
this.layout.setActiveItem(activeCard);
}
}
},

getIndex: function() {
var activeItem = this.layout.getActiveItem();
return activeItem ? activeItem.carouselPosition : -1;
},
});

SS.BufferedCarousel.Indicator = Ext.extend(Ext.Component, {
baseCls: "ss-pagedCarousel-indicator",

initComponent: function() {
if (this.carousel.rendered) {
this.render(this.carousel.body);
}
else {
this.carousel.on('render', function() {
this.render(this.carousel.body);
}, this, {single: true});
}
},

onRender: function() {
SS.BufferedCarousel.Indicator.superclass.onRender.apply(this, arguments);

this.positionIndicator = this.el.createChild({tag: 'span'});
},

onBeforeCardSwitch: function(carousel, card) {
if (card) {
var position = card.carouselPosition/(this.carousel.itemCount-1),
position = isNaN(position) ? 0 : position*100,
el = this.el;

this.positionIndicator[this.carousel.direction=='vertical'?'setTop':'setLeft'](position.toFixed(2)+"%");

el.setStyle('opacity', '1');

if (this.hideTimeout != null) {
clearTimeout(this.hideTimeout);
}

this.hideTimeout = setTimeout(function() {
el.setStyle('opacity', '0');
}, 1500);
}
},
});




var carousel = new SS.BufferedCarousel({
initialCarouselPosition: initialIndex,
itemCount: 5,
indicator: false,
handleCardSwitch: function(carousel, card) {
setTimeout(--SOME ACTION--, 300);
},
createItem: function(index) {
return [{html: (index).toString()}];
}
}
I don't use datastore right now but i think you must modify createItem to retrieve necessary item from datastore and create view for it.

ed.canas
6 Feb 2011, 4:05 PM
Do you have working sample which I can take a look at, I tried adding the code above to my project but gets errors. Such as constrain is not a valid function which can be easily fixed, also variable activeCard not found. even after fixing those I still get Result of expression 'a' undefined is not an object.

DirectX
7 Feb 2011, 12:27 AM
Working example is in attachment.

ed.canas
7 Feb 2011, 7:01 AM
Thanks, for the quick reply, sample works great. However I was trying to get it running under 1.0.2, and for the most part is working fine. Except I had to comment out the following line #138 of code:



if (activeCard && activeItem != activeCard) {
//this.layout.setActiveItem(activeCard);
}


If I leave it uncommented I get the following error:
TypeError: Result of expression 'comp.getItemId' [undefined] is not a function.

I also get the same error on the original sample, but that one continues to work even with the error with 1.0 it does not stops working.

I've added a store by moving the functions around and have it working fine, just would like to know if the line that I commented out is used for anything and if it can be safely left out.

Another issue with 1.0 is that it seems to respond much quicker to swipe events as a result going too fast will break it and fails to load the next card.

Thanks,
Ed

kgilb
7 Feb 2011, 12:42 PM
Thanks for your work, I was able to get the attached code to run (with a few modifications) but would like to know how to make it scroll back to index 0 when it reaches the end of the cards. Can I simply change the incoming index to 0 on bufferCards(index) when the index = count -1?

DirectX
7 Feb 2011, 11:07 PM
ed.canas,

I haven't test it yet with recent version, so thanks for fix. It seems normal that some API parts could change.

kgilb,

One idea:


var carousel = new SS.BufferedCarousel({
fullscreen: true,
initialCarouselPosition: 0,
itemCount: 10,
indicator: false,
createItem: function(index) {
return {html: '<h1>Card ' + (index + 1).toString() + '</h1>'};
}
});

...can be transformed to...


var carousel = new SS.BufferedCarousel({
fullscreen: true,
initialCarouselPosition: 0,
itemCount: 1000,
indicator: false,
createItem: function(index) {
index %= 10; // index -> [0...9]
return {html: '<h1>Card ' + (index + 1).toString() + '</h1>'};
}
});

This is not true infinite but quite close.

marcelsnews
16 Apr 2012, 7:31 AM
Hi,
Just want to know if there is a working version of 'Ext.ux.BufferedCarousel' for Senchatouch 2 ?
The infinite Carousel inSenchatouch 2 doc seems to have a problem :-?.
- It doesn't stop sending request event when there is no more data to fetch.So we can scroll indefinetly showing blank pages and sending requests (increasing 'start' index of the store):s
- When you scroll back it also sends request increasing 'start' index of its store

I've tested the 'touchStyle example (http://docs.sencha.com/touch/2-0/#!/example/production/touchstyle/index.html)' in the official senchatouch doc.

Is there anything that i'm doing wrong ?