Tickers used to be an essential part of any website during the earlier days of web, but are no longer considered that much useful today, right? Well I atleast thought so until a few days back, when I received a feature request for a scrolling ticker in a corporate intranet app (which uses ExtJs heavily, in fact with a completely ExtJs based UI). And on some inspection, the request seemed to be reasonable.

Those people have lots of news feeds emerging from various parts of the corporation and wanted the feeds to be shown on user’s Dashboard and updated dynamically as the feeds change. Fair enough feature request and lets now build an ExtJs Ticker, I said to myself.

There are lots of free Ticker scripts available on the web as you can see, and in fact I had written one such script myself sometime back for my Scrolling Announcement module for DotNetNuke. But I did not want to use any of these (sometimes bulky) scripts and wanted to have a clean solution for ExtJs.

I thought many people might have required such an ExtJs Ticker, and I actually found a couple, but they did not suit my requirements exactly. Both the tickers I encountered allowed scrolling static content, meaning that once they start scrolling, you cannot manipulate the scrolled content dynamically. What I needed was the ability to add items dynamically to the Ticker that are fetched in background through Ajax calls. So, as I was to start writing such a Ticker myself, I found this comment on Sencha forums which gave sample code for a basic ExtJs Ticker. I found the sample promising and decided to use it as the base for creating my custom ExtJs Ticker.

Before seeing and discussing the code, let’s first see the Ticker in action (click here to open the demo in new window):

 

 

Try entering any feed url in the textbox and click “Add Url feed” button and notice the feed items appended dynamically to the Ticker with no flicker or affect on existing scroll.

Here is a quick list of features of this ticker (that was obviously based on specs for the app for which this ticker was created in the first place):

  1. Ability to add items dynamically, this was the most important and crucial feature.
  2. Ability to scroll in any direction (left to right, right to left, top to bottom or bottom to top).
    Although my immediate need was strictly a bottom to top scroll, I decided to keep room for scrolling in any direction to avoid rework in future in case a different scroll direction was needed.
  3. Configurable speed – This again was dictated by client needs. Users desired the ability to control scrolling speed on an individual basis.
  4. Pause scrolling on hover – needless to say, client requirement.
  5. Clickable items – clicking on items needed to bring the details up, so individual items needed to be clickable (try clicking an item in the scrolling Ticker above).
  6. Css customizability – the design team dictated this. The ticker provides you classes that would allow you to set css differently based on scrolling direction if needed.
  7. Light-weight – who needs tons of lines of javascript when ExtJs provides so much out of the box.

Based on these specifications, following is the ticker code I came up with (which is what is working in the sample above):

 

{syntaxhighlighter brush: jscript;fontsize: 100; first-line: 1; }Ext.ux.Ticker = Ext.extend(Ext.BoxComponent, {
baseCls: ‘x-ticker’,
autoEl: {
tag: ‘div’,
cls: ‘x-ticker-wrap’,
children: {
tag: ‘div’,
cls: ‘x-ticker-body’
}
},
body: null,

constructor: function(config) {
Ext.applyIf(config, {
direction: ‘up’,
speed: 1,
pauseOnHover: true
});
if (config.speed < 1) config.speed = 1;
else if (config.speed > 20) config.speed = 20;

Ext.applyIf(config, {
refreshInterval: parseInt(10 / config.speed * 15)
});
config.unitIncrement = 1;

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

this.addEvents(‘itemclick’);
},

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

this.taskCfg = {
interval: this.refreshInterval,
scope: this
};

var posInfo, body = this.body;
switch (this.direction) {
case “left”:
case “right”:
posInfo = { left: body.getWidth() };
this.taskCfg.run = this.scroll.horz;
break;
case “up”:
case “down”:
posInfo = { top: body.getHeight() };
this.taskCfg.run = this.scroll.vert;
break;
}
posInfo.position = ‘relative’;

body.setPositioning(posInfo);
Ext.ux.Ticker.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.task = Ext.apply({}, this.taskCfg);
Ext.TaskMgr.start(this.task);
},

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

onDestroy: function() {
if (this.task) {
Ext.TaskMgr.stop(this.task);
}

Ext.ux.Ticker.superclass.onDestroy.call(this);
},

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

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

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

scroll: {
horz: function() {
var body = this.body;
var bodyLeft = body.getLeft(true);
if (this.direction == ‘left’) {
var bodyWidth = body.getWidth();
if (bodyLeft <= -bodyWidth) {
bodyLeft = this.el.getWidth(true);
} else {
bodyLeft -= this.unitIncrement;
}
} else {
var elWidth = this.el.getWidth(true);
if (bodyLeft >= elWidth) {
bodyLeft = -body.getWidth(true);
} else {
bodyLeft += this.unitIncrement;
}
}
body.setLeft(bodyLeft);
},

vert: function() {
var body = this.body;
var bodyTop = body.getTop(true);
if (this.direction == ‘up’) {
var bodyHeight = body.getHeight(true);
if (bodyTop <= -bodyHeight) {
bodyTop = this.el.getHeight(true);
} else {
bodyTop -= this.unitIncrement;
}
} else {
var elHeight = this.el.getHeight(true);
if (bodyTop >= elHeight) {
bodyTop = -body.getHeight(true);
} else {
bodyTop += this.unitIncrement;
}
}
body.setTop(bodyTop);
}
}
});{/syntaxhighlighter}

The code basically sets up a custom autoEl for the BoxComponent-derived Ticker, and starts an Ext.Task to scroll the contents regularly.

The code in the comment on the Sencha forum (referenced above) provided a very good foundation for my Ticker requirements and my code maintains its original soul from the comment.

A very interesting point to note above is that the Ticker (Ext.ux.Ticker) is a BoxComponent derived class. Which means it can be added directly as an item to a container (as in the example above where it has been added to ‘east’ panel of a viewport) or it can be placed directly in yout html somewhere. Lazy instantization (for which you need to asign Ticker an xtype), participating in Layout calls and other ExtJs Component features come natively to the Ticker.

I have tested it with Chrome, Chrome Frame, IE 9 and FF 3, but I think it should work fine with any modern browser.

You can find the javascript code and css for the ticker (providing empty classes to give you an idea on what is available so you can customize the look and feel) as well as the html file for reproducing the above example attached below.