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).
Great job on this ! You saved my life with this post. i got it to work but it doesnt work for all grids.
is there a way to make any grid on the page have an iscroll scrollbar? do you happen to have an example?
I’m not very advanced in ext so I really appreciate your help with this
Irena
Could you comment on how to get this working on ExtJS 4?
I’m having problems doing the same thing
Find a solution to ExtJS4?
Please show me who came to apply for iScroll ExtJs4.
hi
Can you please upgrade your demo to allow when u click in something it dynamically load content with scrollbar assigned to iT?
I test and play with it over hours but it not work, if you can fix it for dynamic content it would be great.
wait for your answer
thank you
Correct me if I’m wrong, but you don’t need any special scripts to get scrolling within containers to work. a two-finger swipe will scroll within containers naitvely on an iPad. I have this working on my ExtJS 3 applications with no extra code.
hi rahul,
do u work on assigning dynamic content with isCroll refresh for ajax content ? we have try but can’t handle it to work
thank you for usefull website.
Firstly, great article!! Thanks for this Rahul.
My question is how to apply the iScroll to a Panel which is dynamically adding a managed iFrame as an item.
Your suggested solution with iScroll and your implementation across panels, grids and trees is fantastic but Im seriously struggling to get a dynamically created iFrame rendering html to allow the iScroll to work on the underlying iframe elements.
Any help??
Thanks in advance.
Rob
Anyone has adapted iScroll to wotrrj on Extjs 4.0.7 version ?
Thanks
I know you’ve worked with extensible in the past. I’m trying to get the iScroll to work and I’ve tried overriding Ext.ensible.cal.CalendarPanel with no luck. It removes the standard scroll bar but does not replace it with an iScroll. I don’t see any errors in the FB console so I’m not sure. I think the issue is which class to override to take control of the ext-cal-body-ct DIV where the actual calendar is that needs to scroll.
Any ideas?
I see you have this in your code:
Ext.isMobileDevice
I can’t find this in any Ext documentation?
Hi,
Above thanks for this extension. IT’S GREAT… Exactly what I search.
But i have strange effect after scrolling. When I scroll down then i click on the last row, all rows disapear !!
I use extjs 3.3.1
Do you have an idea for fix that
Thanks for your help
TDI
Hi rahul,
Thanks for your quick response.
But I need to add this functionality to an existing application. I have a new user that use a sreen touch 19 inch.
I will appreciate if you can test your demo with extjs 3.3 … Maybe you will see what is the problem quickly than me.
If somewone else have already resolve that, thanks for sharing
Thanks
TDI
Hi
After making the changes as per above comment when i call ext.getcmp(‘grid’).view.refrresh(); it pops out with an error …can any one help me out on this .
Hi rahul,
Could you share wich alternative do you have choose ?\
Thanks
Karim
Hi Rahul, Nice work on this! Do you know how to implement this for Sencha Touch 2. I tried your code above but can’t seem to get it working. I tried this:
initialize: function() {
this.callParent();
debugger;
this.on({
painted: ‘doDelay’,
scope: this
});
},
doDelay:function(){
debugger;
var scope = this;
var id = setTimeout(function(){
clearTimeout(id);
scope.onAfterRender();
},200,scope,id);
},
onAfterRender:function(){
debugger;
if (this.getBaseCls() == ‘x-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(‘painted’, 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);
}
But it crashes at ” if (el.children.length > 0) {” because el is null. – thanks -Mike
Hey Rahul ,
I am ExtJS Developer. I am facing some problem on Item click event of Tree panel in iPad browser. When i am trying to click on any node , its clicking on whole treepanel and its working fine on other devices and desktop.
Please help me out to sort out this issue.