I yesterday did a very interesting thing, integrating ExtJs with the excellent iScroll 4 script from Matteo Spinelli that would make my ExtJs containers intuitively scrollable on Touch devices (especially iPad) giving them a more native scrolling feel.

You might be knowing already that mobile webkit (on iPhone, iPad, Android) does not provide a native way to scroll content inside a fixed width/height element. In simpler terms, this means overflow: auto or overflow: scroll does not work on mobile browsers as you are used to seeing them on PCs. When a element’s contents exceed its visible area on touch devices, scrollbars do not appear for any value of overflow css property you specify. Instead mobile users are left with having to use the extremely unfriendly 2-finger scroll to scroll contents on mobile browsers.

And Matt’s script addresses exactly this issue by attaching custom scrollbars to such “overflown” html elements. It works great stand-alone but I was tasked to find out an easy non-breaking approach to integrate iScroll into an existing large web app that leverages ExtJs extensively, preferably in a way that should not require changes to component config specifications.

Before discussing how I approached it, let’s first have a sneak-peak of an example demonstrating the behavior of what I did. Below you will find 3 “iScrolled” ExtJs components, a Panel, a GridPanel and a TreePanel. Try clicking any component and dragging it up or down to see how scrolling behaves. You would see the best results when viewing this example on a Touch device (preferable iPad or another tab device). Click here to open the demo in new window:

 

 

Now iScroll is a pretty easy library to use. For each html element to which you want to have scrollbars attached when it overflows, you need to wrap the element in another html element having “overflow: auto” and register this parent element with iscroll in a single line, as easy as that (please check the official iScroll page for more details on how to use iScroll and various options available).

So I thought well let’s hook into the rendering process for an ExtJs Container and register the container’s content area with iScroll if the Container is set to be auto-scrolled (autoScroll: true).

So after some test and trial iterations, I came up with the following code:

 

{syntaxhighlighter brush: jscript;fontsize: 100; first-line: 1; }Ext.override(Ext.Panel, {
afterRender: Ext.Panel.prototype.afterRender.createSequence(function() {
if (this.getXType() == ‘panel’) {
this._getIScrollElement = function() {
return (this.el.child(‘.x-panel-body’, true));
}
}

//Uncomment below to use iScroll only on mobile devices but use regular scrolling on PCs.
if (this.autoScroll /*&& Ext.isMobileDevice*/) {
if (this._getIScrollElement) {
this._updateIScroll();
this.on(‘afterlayout’, this._updateIScroll);
}
}
}),

_ensureIScroll: function() {
if (!this.iScroll) {
var el = this._getIScrollElement();
if (el.children.length > 0) {
this.iScroll = new iScroll(el);
this.iScrollTask = new Ext.util.DelayedTask(this._refreshIScroll, this);
}
}
},

_updateIScroll: function() {
this._ensureIScroll();
if (this.iScroll) {
this.iScrollTask.delay(1000);
}
},

_refreshIScroll: function() {
this.iScroll.refresh();
//Refresh one more time.
this.iScrollTask.delay(1000);
}
});

Ext.override(Ext.tree.TreePanel, {
_getIScrollElement: function() {
return (this.el.child(‘.x-panel-body’, true));
}
});

Ext.override(Ext.grid.GridPanel, {
_getIScrollElement: function() {
return (this.el.child(‘.x-grid3-scroller’, true));
},

afterRender: Ext.grid.GridPanel.prototype.afterRender.createSequence(function() {
//TODO: need to hook into more events and to update iScroll.
this.view.on(‘refresh’, this._updateIScroll, this);
})
});{/syntaxhighlighter}

It starts with defining afterRender sequence method for an Ext Panel. If the Panel is set to be autoScrolled (autoScroll: true), its scroll element (the main content div housing the Panel’s content) is registered with iScroll to make it scrollable on touch devices.

Every derived class of Ext.Panel is expected to provide a method called _getIScrollElement, which should either return the id of the content region for the Panel, or the dom reference for the content region (notice it should be the raw dom reference, not encapsulated in Ext.Element).

If a Panel derived class does not provide the _getIScrollElement method, instances of that class are not registered with iScroll and hence are not “iScrollable”. In the above code, Panel, GridPanel and TreePanel classes have been overridden to provide an appropriate return value by adding the _getIScrollElement method to these classes. You can easily override other Panel derived classes as required and return suitable content element that should be “iScrolled”.

This was the easy part, the major challenge was deciding when to call refresh method on the iScroll associated to the Panel’s content. As you can read in iScroll’s documentation, iScroll cannot currently detect changes in html content for the element which has been registered. But refresh method should be called each time a registered element’s html changes to enable iScroll to re-calculate the scrollbar dimensions and scrollable content.

In the above code, I have hooked into afterlayout listener to refresh iScroll for Panels. Additionally, GridPanel View’s refresh listener is also used to refresh the iScroll for that GridPanel. But this is not enough, you probably need to register additional listeners from which you should call _updateIScroll method to keep the iScroll bars synced with content changes (e.g. you would need to call this in record added/removed listeners for Grids, node added/removed listeners for Trees, resize listeners for all components etc).

I assembled the above code very quickly for this blog post, I think some enhancements can be made to it (e.g. providing a config option preventIScroll, which if true, iScroll is not registered for the component even if autoScroll is set to true). More work needs to be done in finding out a comprehensive set of listeners for each component that should be subscribed to call the _updateIScroll method. The above is “fresh” code that has been just baked. I would try to keep it updated as I improve it for my application based on usability testing. But hopefully you can see the idea how you can leverage iScroll to providing native-feel scrolling on touch devices for your Ext containers without having to change config options for each component independently.

For demonstration purpose, I have left iScroll enabled in the above example on PCs too. But on PCs, you might not find iScroll intuitive. As mentioned, you would be able to test this example better from a touch device (preferably a pad).