📜 ⬆️ ⬇️


For unclear reasons, the post disappeared from the official blog a few hours after publication, and the content from the demo site was also removed. Later, the blog entry appeared again, but with the message that the application will be available soon

Today, the Sencha development team announces the release of a new demo application based on their library: ShopStyle . ShopStyle is a classic iPhone and iPad app that is part of the PopSugar portal. The presented application was developed at the Sencha Touch library and uses one of the innovations: Touch Carousel. In particular, there is the “endless carousel”, one of the new features that is part of the Sencha Touch. The most important feature of this component is the active DOM management which allows the application to fit into the memory limitations existing on the iPad.

Of course, you can explore the source code on the site, and in this article the most interesting technical points in the creation of this application will be considered. The main problem is that in one category there may be thousands of products. Downloading all this data into the browser’s memory at once is a guaranteed file.

Creating a carousel

A carousel can process thousands of items using a sliding viewport that displays data. Three areas with content are created for the current page: current, previous and next. While the user scrolls through the corresponding areas are created and destroyed, which ensures that the content matches the current state. This ensures that at any point in the carousel there is a fairly small fixed number of elements, despite the fact that their actual number can be huge. In the diagram below, the sliding area consists of three pages. Initially the third page is focused and pages two through four are initialized. When the user moves to the fourth page, the active area is shifted to the right, and the second page is also deleted and the fifth page is created.

Initialization of the carousel is elementary. Instead of directly using the items parameter, a createItem is defined that accepts a function. when it is necessary to initialize the element, the function with the index of the element will be called. The function creates and returns an element. To demonstrate this, there is a small piece of code with a normal and infinite carousel.

// Normal carousel
var carousel = new Ext.Carousel({
items: [{
html: '1'
html: '2'
}, {
html: '3'

// Infinite carousel
var carousel = new SS.BufferedCarousel({
itemCount: 3,
createItem: function (index) {
return {html: (i+1)+ '' };

In principle, the sliding area is quite simple. At initialization and each time when the scope changes, the sliding area is updated.

bufferCards: function (index) {
// Quick return if there is nothing to do
if ( this .lastBufferedIndex == index) { return ; }
this .lastBufferedIndex = index;

// Initialize variables
// 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

// 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 end) {
this .remove(item, true );
changed = true ;
else {

// 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= 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);

Data Acquisition and Caching

ShopStyle provides an API that allows you to receive and filter products from a category. There is also the possibility of obtaining parts of the data that is necessary for the implementation of paging. The developers wanted to have direct access to the data, as well as to avoid problems caused by receiving data from the network, which meant caching images.

For these purposes, a DataCache class has been created to simplify the retrieval of data and the caching of product data. At its core, DataCache contains a simple “getItems” function that accepts a range of products as well as a callback function to process data about goods after they are received, it also has some simple processing to avoid identical requests and directly returns data that has already been received. As a result, caching becomes elementary — all that is needed is to call getItems with a range of products. This process is depicted in the diagram:

When getItems is called for the first 100 items, a request is sent to ShopStyle. In subsequent getItems requests for elements 1-10 and 11-20, the application determines that this data is already being requested and does not send additional requests to the API. When a reply comes, the corresponding callback function is called. The subsequent call to getItems for elements 21-30 immediately calls the inverse function, since the data has already been received.


When displaying products, each page displays 8 or 9 (depending on orientation) elements in the grid layout. In most cases, floating elements are used, but the developers wanted to go a little different way and use the features of CSS3:

SS.PagedCarousel.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.PagedCarousel.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);


Mobile Internet

The touch version of the application is not a complete copy of the original application — for example, the search capability was implemented while the original application uses a filter mechanism. All this demonstrates the advantages of Sencha Touch when creating application analogs available on mobile platforms, and since this is still an Internet application, it will work on both WebOS and Android (right after developers finish the corresponding library support) out of the box.

Well, in conclusion, the author of the original article thanks Brian Sugar and Trey Matteson from Sugar for providing access to the API, as well as advising on relevant issues and wishes them everything.

Source: https://habr.com/ru/post/101439/

All Articles