Sometime earlier, I wrote a blog post demonstrating a Ticker component for ExtJs to which items can be added dynamically (here). The client for whom I originally wrote that Ticker component changed the specification recently, now requesting the ability for the ticker items to stay on screen for a configurable duration of time.

After having a look at the code for the original Ticker, I felt it cannot be customized directly to support the new specification. So I instead took the approach of creating a new component (which I call Slider).

I think a demonstration of the component would be appropriate before discussing it (click here to open the demo in new window):

 

 

 

Apart from the normal stuff, the following are the more interesting points:

  1. Being a regular Ext component, the Slider can participate in Ext layouts.
  2. itemDuration (the amount of time a particular item stays on screen) is configurable per item. This in fact was the major reason the client wanted this new component, ability to keep more important ticker items on screen for longer durations of time.

    Please see the code in extjs-slider.htm on how to add individual items with different durations (basically if a particular item’s html has an element with the class “itemDuration” applied to it, that element’s value is used as the duration for that item).
    The sample uses random values for item duration on alternate slider items, you would obviously use a more meaningful way to specify these durations.

  3. If itemDuration is not specified for an individual item, the default value for the same config option pass to the Slider is used.
  4. Scrolling pauses on mouse hover based on config option pauseOnHover.
  5. Individual items are clickable and you can listen to the itemclick event for the same.

The html, css and js for the component is attached with this blog post. For completeness sake, I would reproduce the js code for the Slider component here:

 

{syntaxhighlighter brush: jscript;fontsize: 100; first-line: 1; }Ext.ux.Slider = Ext.extend(Ext.BoxComponent, {
baseCls: ‘x-slider’,
autoEl: {
tag: ‘div’,
cls: ‘x-slider-wrap’,
children: {
tag: ‘div’,
cls: ‘x-slider-body’
}
},
body: null,
_onMouseOut: null,
_currentItemIndex: 0,
_hasMouseOver: false,
_waitingForNextItem: false,

constructor: function(config) {
Ext.applyIf(config, {
itemDuration: 5000,
pauseOnHover: true
});

Ext.ux.Slider.superclass.constructor.call(this, config);

this.addEvents(‘itemclick’);
},

afterRender: function() {
this.body = this.el.first(‘.x-slider-body’);

this.taskCfg = {
interval: 10,
run: this.doScroll,
scope: this
};

Ext.ux.Slider.superclass.afterRender.call(this);

if (this.pauseOnHover) {
this.el.on(‘mouseover’, this.onMouseOver, this);
this.el.on(‘mouseout’, this.onMouseOut, this);
this.el.on(‘click’, this.onMouseClick, this);
}

this._start();
},

doScroll: function() {
var items = this._getItems();
if (items.length > 0) {
var item = items[this._currentItemIndex];
var top = Ext.fly(item).getTop(true);
top = top – 2;
if (top < 0) {
top = 0;
this._stop();

var me = this;
me._waitingForNextItem = true;

var itemDuration = Ext.DomQuery.selectNumber(‘.itemDuration’, item);
itemDuration = itemDuration == 0 ? this.itemDuration : itemDuration;

setTimeout(function() {
if (!me._hasMouseOver || !me.pauseOnHover) {
Ext.fly(item).setTop(‘100%’);
} else {
me._onMouseOut = function() {
Ext.fly(item).setTop(‘100%’);
}
}

var items = me._getItems();
if (items.length > 0) {
me._currentItemIndex++;
if (me._currentItemIndex >= items.length) {
me._currentItemIndex = 0;
}

if (!me._hasMouseOver) {
me._start();
}
me._waitingForNextItem = false;
}
}, itemDuration);
}

Ext.fly(item).setTop(top + ‘%’);
}
},

add: function(o) {
var dom = Ext.DomHelper.createDom(o);
this.body.appendChild(Ext.fly(dom).addClass(‘x-slider-item’));
},

onDestroy: function() {
this._stop();
Ext.ux.Slider.superclass.onDestroy.call(this);
},

onMouseOver: function() {
this._hasMouseOver = true;
this._stop();
},

onMouseClick: function(e, t, o) {
var item = Ext.fly(t).up(‘.x-slider-item’);
if (item) {
this.fireEvent(‘itemclick’, item, e, t, o);
}
},

onMouseOut: function() {
this._hasMouseOver = false;
if (this._onMouseOut) {
this._onMouseOut();
this._onMouseOut = null;
}
if (!this._waitingForNextItem) {
this._start();
}
},

_start: function() {
if (!this.task) {
this.task = Ext.apply({}, this.taskCfg);
Ext.TaskMgr.start(this.task);
}
},

_stop: function() {
if (this.task) {
Ext.TaskMgr.stop(this.task);
delete this.task;
}
},

_getItems: function() {
return (Ext.DomQuery.select(‘.x-slider-item’, this.body.dom));
}
});{/syntaxhighlighter}