PDA

View Full Version : Walk through for AJAX Back button support with Ext & YUI History



jerrybrown5
16 Oct 2007, 9:05 PM
I don't have much time to write better documentation so please forgive me in advance. (While you are at it forgive me for the lack of documentation in my two other recent submissions as well)

Here is a quick walk through of how to demystify the back button on Ext using Yahoo History which in my opinion provides the best back button support of any toolkit (Supports: FF, IE, Safari; nothing supports Opera). Initially this only works with the tab panel but as you will see in the code, the framework is designed with only minimal effort needed to facilitate this feature on other components. I did not yet currently design it so that points in time (or hid, history id) can be bookmarked.

Step 1: Base HTML Page
Make sure your html document contains the following. Follow the framework closely because YUI History is pretty rigid.



<html>
<head>

<!-- The usual suspects when using ext-yui-adapter plus history //-->
<link rel="stylesheet" type="text/css" href="javascript/3rd_party/extjs/resources/css/ext-all.css"/>
<script type="text/javascript" src="javascript/3rd_party/yui/build/utilities/utilities.js"></script>
<script type="text/javascript" src="javascript/3rd_party/yui/build/history/history-beta.js"></script>
<script type="text/javascript" src="javascript/3rd_party/extjs/adapter/yui/ext-yui-adapter.js"></script>
<script type="text/javascript" src="javascript/3rd_party/extjs/ext-all-debug.js"></script>

</head>
<body>

<script>
YAHOO.util.History.register( "hid", '', function( state ) {
/*Module registration MUST take place before calling YAHOO.util.History.initialize.*/
/*also, no var can be used without being registered prior to init*/
Ext.jx.History.onNavigate(state);
} );

YAHOO.util.History.initialize(); /*this needs to be right after the body tag to avoid spacing probs*/

</script>

</body>
</html>

Step 2: My library

Since I'm working on so many ext modules, I am collecting them in the Ext.jx scope and not the Ext.ux.jx scope. (Sorry Jack, I can't manage the extra typing :-)

Here is the related portion of that library.



Ext.jx={
plugins:{},
History:{
hids:[],
disabled:false,
pause:false,
registered:false,
onNavigate: function( hid ) {
var History=Ext.jx.History;
if (!History.paused && !History.disabled){
var o=History.hids[Number(hid)];
if (o){
History.paused=true;
Ext.getCmp(o.id).setHistoryValue(o.value);
setTimeout(function(){
History.paused=false;
});
}
}
},

changedPage: function (o){
var History=Ext.jx.History;
if (!History.paused && !History.disabled){
History.hids.push({id:o.id, value:o.getHistoryValue() });
History.paused=true;
try{
YAHOO.util.History.navigate('hid', String(History.hids.length-1) )
}catch(e){
setTimeout(function(){
try{
YAHOO.util.History.navigate('hid', String(History.hids.length-1) )
}catch(e){
History.disabled=true;
}

});

}
History.paused=false;
}
}
}
}

Ext.jx.plugins.History={
tabpanel:{
prototype:{
getHistoryValue: function(){
return this.getActiveTab();
},

setHistoryValue: function(value){
this.setActiveTab(value);
}
},
init:function(o){
o.on('tabchange', function(){
Ext.jx.History.changedPage(this);
});
}
},

init:function(o){
var mod=Ext.jx.plugins.History[o.xtype];
if (!mod){
alert('programmer error: this xtype('+o.xtype+') is not configured yet to use the history.');
}else{
Ext.apply(o, mod.prototype);
mod.init(o);
}
}

}



Step 3: Finish
The implementation could not be easier.



xtype:'tabpanel',
plugins:Ext.jx.plugins.History,

Ronaldo
17 Oct 2007, 1:08 AM
Thanks! So for every component that can change the page, write a plugin and let it call the history component. Sweet!

Ronald

jerrybrown5
17 Oct 2007, 1:22 AM
yep, pretty much... I made some more notes for you. Feel free to submit the code of other components and I'll include it in the main post.



Ext.jx.plugins.History={
tabpanel:{ /*combo.xtype of other component*/
prototype:{
getHistoryValue: function(){
... /*this needs to return an object or value that setHistoryValue will use*/
},

setHistoryValue: function(value){
...
}
},
init:function(o){
o.on('tabchange', function(){ /*subscribe to the right event here*/
Ext.jx.History.changedPage(this);
});
}
},
/*place the code for other components here in the same way that tabpanel is defined above*/

init:function(o){
var mod=Ext.jx.plugins.History[o.xtype];
if (!mod){
alert('programmer error: this xtype('+o.xtype+') is not configured yet to use the history.');
}else{
Ext.apply(o, mod.prototype);
mod.init(o);
}
}

}

galdaka
17 Oct 2007, 3:39 AM
Live example?

Thanks in advance,

jerrybrown5
17 Oct 2007, 8:37 AM
I already apologized for the lack of documentation, which means that I should be off of the hook for providing more?! :)

durlabh
12 Dec 2007, 5:56 PM
The code in jx.js is


Ext.jx={
plugins:{},
History:{
hids:[],
disabled:false,
pause:false,
registered:false,
onNavigate: function( hid ) {
var History=Ext.jx.History;
if (!History.paused && !History.disabled){
var o=History.hids[Number(hid)];
if (o){
History.paused=true;
Ext.getCmp(o.id).setHistoryValue(o.value);
setTimeout(function(){
History.paused=false;
});
}
}
},

changedPage: function (o){
var History=Ext.jx.History;
if (!History.paused && !History.disabled){
History.hids.push({id:o.id, value:o.getHistoryValue() });
History.paused=true;
try{
YAHOO.util.History.navigate('hid', String(History.hids.length-1) )
}catch(e){
setTimeout(function(){
try{
YAHOO.util.History.navigate('hid', String(History.hids.length-1) )
}catch(e){
History.disabled=true;
}
});

}
History.paused=false;
}
}
}
}
Ext.jx.plugins.History={
tabpanel:{
prototype:{
getHistoryValue: function(){
return this.getActiveTab();
},

setHistoryValue: function(value){
this.setActiveTab(value);
}
},
init:function(o){
o.on('tabchange', function(){
Ext.jx.History.changedPage(this);
});
}
},

init:function(o){
var mod=Ext.jx.plugins.History[o.getXType()];
if (!mod){
alert('programmer error: this xtype('+o.getXType()+') is not configured yet to use the history.');
}else{
Ext.apply(o, mod.prototype);
mod.init(o);
}
}
}


I just changed to use getXType() method instead of relying on xtype property. In cases where xtype is not defined and a constructor is used, xtype property isn't available.

Now, in the main page, the head tag has the usual suspects + jx.js:


<link rel="stylesheet" type="text/css" href="extJS/resources/css/ext-all.css" />
<script type="text/javascript" src="yui/utilities.js"></script>
<script type="text/javascript" src="yui/history.js"></script>
<script type="text/javascript" src="extJS/adapter/yui/ext-yui-adapter.js"></script>
<script type="text/javascript" src="extJS/ext-all-debug.js"></script>
<script type="text/javascript" src="js/jx.js"></script>


Then just at the start of the body tag I have


<body>
<iframe id="yui-history-iframe" src="App_Themes/Default/Images/logo.gif" style="position:absolute; top:0; left:0; width:1px; height:1px; visibility:hidden;"></iframe>
<input id="yui-history-field" type="hidden" />
<script language="javascript" type="text/javascript">
YAHOO.util.History.register( "hid", '', function( state ) {
/*Module registration MUST take place before calling YAHOO.util.History.initialize.*/
/*also, no var can be used without being registered prior to init*/
Ext.jx.History.onNavigate(state);
} );
YAHOO.util.History.initialize("yui-history-field", "yui-history-iframe"); /*this needs to be right after the body tag to avoid spacing probs*/
</script>


Now, when using TAB, you can go ahead and specify the plugin as


plugins:Ext.jx.plugins.History,


I've tested this with extJS 2.0 and yui build 2.4.0. I'm working on writing additional plugins but the original code has made it so much easier for me! Thanks again.

sethladd
14 Dec 2007, 3:05 AM
Any chance anyone has written a plugin for the Grid component? My users would really want to use the back button and the grid component.

durlabh
14 Dec 2007, 6:23 AM
Hi sethladd (http://extjs.com/forum/member.php?u=19826),

Attached is something that may help. This is far from tested. I would like to give you a couple of pointers:

By default, when an entry is added in history, YUI history again navigates to the current state. So, in case of grid, the best way to put the history logic was on datastore load and this resulted in endless loop. I've a workaround for that working right now but it needs to be improved.
In onNavigate method of Ext.jx.History, I've added a temporary logic to not to navigate if the current position is the last position. This works in my initial tests but we need to resize the array also to check the position next time.Let me know how it goes.



Ext.jx={
plugins:{},
History:{
hids:[],
disabled:false,
pause:false,
registered:false,
onNavigate: function( hid ) {
var History=Ext.jx.History;
if (!History.paused && !History.disabled){
var hNumber = Number(hid);
var o=History.hids[Number(hid)];
if (o){
History.paused=true;
if(hNumber != History.hids.length-1) {
Ext.getCmp(o.id).setHistoryValue(o.value);
}
setTimeout(function(){
History.paused=false;
});
}
}
},

changedPage: function (o, options){
var History=Ext.jx.History;
if (!History.paused && !History.disabled){
History.hids.push({id:o.id, value:o.getHistoryValue(options) });
History.paused=true;
try{
YAHOO.util.History.navigate('hid', String(History.hids.length-1) )
}catch(e){
setTimeout(function(){
try{
YAHOO.util.History.navigate('hid', String(History.hids.length-1) )
}catch(e){
History.disabled=true;
}

});

}
History.paused=false;
}
}
}
}

Ext.jx.plugins.History={
tabpanel:{
prototype:{
getHistoryValue: function(){
return this.getActiveTab();
},

setHistoryValue: function(value){
this.setActiveTab(value);
}
},
init:function(o){
o.on('tabchange', function(){
Ext.jx.History.changedPage(this);
});
}
},

grid:{
prototype:{
getHistoryValue: function(options){
var ds = this.getStore();
ds.oldOptions = options;
return options;
},

setHistoryValue: function(value){
var ds = this.getStore();
if(ds.oldOptions != value) {
ds.loadingFromHistory = true;
ds.load(value);
}
}
},

init:function(o){
var store = o.getStore();
store.on('load', function(ds, records, options){
if(!ds.loadingFromHistory) {
Ext.jx.History.changedPage(o, options);
}
ds.loadingFromHistory = false;
});
}
},

paging:{
prototype:{
getHistoryValue: function(options){
alert('not supported');
},

setHistoryValue: function(value){
alert('not supported');
}
},
init:function(o){
// nothing
}
},


init:function(o){
var mod=Ext.jx.plugins.History[o.getXType()];
if (!mod){
alert('programmer error: this xtype('+o.xtype+') is not configured yet to use the history.');
}else{
Ext.apply(o, mod.prototype);
mod.init(o);
}
}

}

cherrymaktob
5 Jun 2008, 5:01 AM
I don't have much time to write better documentation so please forgive me in advance. (While you are at it forgive me for the lack of documentation in my two other recent submissions as well)

Here is a quick walk through of how to demystify the Ajax back button (http://www.ajaxprojects.com/ajax/newsdetails.php?itemid=150) on Ext using Yahoo History which in my opinion provides the best back button support of any toolkit (Supports: FF, IE, Safari; nothing supports Opera). Initially this only works with the tab panel but as you will see in the code, the framework is designed with only minimal effort needed to facilitate this feature on other components. I did not yet currently design it so that points in time (or hid, history id) can be bookmarked.

Step 1: Base HTML Page
Make sure your html document contains the following. Follow the framework closely because YUI History is pretty rigid.



<html>
<head>

<!-- The usual suspects when using ext-yui-adapter plus history //-->
<link rel="stylesheet" type="text/css" href="javascript/3rd_party/extjs/resources/css/ext-all.css"/>
<script type="text/javascript" src="javascript/3rd_party/yui/build/utilities/utilities.js"></script>
<script type="text/javascript" src="javascript/3rd_party/yui/build/history/history-beta.js"></script>
<script type="text/javascript" src="javascript/3rd_party/extjs/adapter/yui/ext-yui-adapter.js"></script>
<script type="text/javascript" src="javascript/3rd_party/extjs/ext-all-debug.js"></script>

</head>
<body>

<script>
YAHOO.util.History.register( "hid", '', function( state ) {
/*Module registration MUST take place before calling YAHOO.util.History.initialize.*/
/*also, no var can be used without being registered prior to init*/
Ext.jx.History.onNavigate(state);
} );

YAHOO.util.History.initialize(); /*this needs to be right after the body tag to avoid spacing probs*/

</script>

</body>
</html>

Step 2: My library

Since I'm working on so many ext modules, I am collecting them in the Ext.jx scope and not the Ext.ux.jx scope. (Sorry Jack, I can't manage the extra typing :-)

Here is the related portion of that library.



Ext.jx={
plugins:{},
History:{
hids:[],
disabled:false,
pause:false,
registered:false,
onNavigate: function( hid ) {
var History=Ext.jx.History;
if (!History.paused && !History.disabled){
var o=History.hids[Number(hid)];
if (o){
History.paused=true;
Ext.getCmp(o.id).setHistoryValue(o.value);
setTimeout(function(){
History.paused=false;
});
}
}
},

changedPage: function (o){
var History=Ext.jx.History;
if (!History.paused && !History.disabled){
History.hids.push({id:o.id, value:o.getHistoryValue() });
History.paused=true;
try{
YAHOO.util.History.navigate('hid', String(History.hids.length-1) )
}catch(e){
setTimeout(function(){
try{
YAHOO.util.History.navigate('hid', String(History.hids.length-1) )
}catch(e){
History.disabled=true;
}

});

}
History.paused=false;
}
}
}
}

Ext.jx.plugins.History={
tabpanel:{
prototype:{
getHistoryValue: function(){
return this.getActiveTab();
},

setHistoryValue: function(value){
this.setActiveTab(value);
}
},
init:function(o){
o.on('tabchange', function(){
Ext.jx.History.changedPage(this);
});
}
},

init:function(o){
var mod=Ext.jx.plugins.History[o.xtype];
if (!mod){
alert('programmer error: this xtype('+o.xtype+') is not configured yet to use the history.');
}else{
Ext.apply(o, mod.prototype);
mod.init(o);
}
}

}



Step 3: Finish
The implementation could not be easier.



xtype:'tabpanel',
plugins:Ext.jx.plugins.History,


i tried it...