PDA

View Full Version : Buffering Http State Provider



Pages : [1] 2

jsakalos
31 Jan 2008, 5:02 PM
Hi all,

I've just finished coding and first tests of HttpProvider (Ext.ux.HttpProvider) that saves state data on a server not in cookies. It buffers changes for a configurable time and then saves them to a server with Ajax request. There is no client/server traffic if there are no changes.

Give it a try, however, testing this extension is not for beginners.



// vim: ts=4:sw=4:nu:fdc=2:nospell
/*global Ext, console */
/**
* @class Ext.ux.state.HttpProvider
* @extends Ext.state.Provider
*
* Buffering state provider that sends and receives state information to/from server
*
* @author Ing. Jozef Sakáloš
* @copyright (c) 2008, Ing. Jozef Sakáloš
* @version 1.2
* @revision $Id: Ext.ux.state.HttpProvider.js 728 2009-06-16 16:31:16Z jozo $
* @depends Ext.ux.util
*
* @license Ext.ux.state.HttpProvider is licensed under the terms of
* the Open Source LGPL 3.0 license. Commercial use is permitted to the extent
* that the code/component(s) do NOT become part of another Open Source or Commercially
* licensed development library or toolkit without explicit permission.
*
* <p>License details: <a href="http://www.gnu.org/licenses/lgpl.html"
* target="_blank">http://www.gnu.org/licenses/lgpl.html</a></p>
*
* @forum 24970
* @demo http://cellactions.extjs.eu
*
* @donate
* <form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_blank">
* <input type="hidden" name="cmd" value="_s-xclick">
* <input type="hidden" name="hosted_button_id" value="3430419">
* <input type="image" src="https://www.paypal.com/en_US/i/btn/x-click-butcc-donate.gif"
* border="0" name="submit" alt="PayPal - The safer, easier way to pay online.">
* <img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1">
* </form>
*/

Ext.ns('Ext.ux.state');

/**
* Creates new HttpProvider
* @constructor
* @param {Object} config Configuration object
*/
// {{{
Ext.ux.state.HttpProvider = function(config) {

this.addEvents(
/**
* @event readsuccess
* Fires after state has been successfully received from server and restored
* @param {HttpProvider} this
*/
'readsuccess'
/**
* @event readfailure
* Fires in the case of an error when attempting to read state from server
* @param {HttpProvider} this
*/
,'readfailure'
/**
* @event savesuccess
* Fires after the state has been successfully saved to server
* @param {HttpProvider} this
*/
,'savesuccess'
/**
* @event savefailure
* Fires in the case of an error when attempting to save state to the server
* @param {HttpProvider} this
*/
,'savefailure'
);

// call parent
Ext.ux.state.HttpProvider.superclass.constructor.call(this);

Ext.apply(this, config, {
// defaults
delay:750 // buffer changes for 750 ms
,dirty:false
,started:false
,autoStart:true
,autoRead:true
,user:'user'
,id:1
,session:'session'
,logFailure:false
,logSuccess:false
,queue:[]
,url:'.'
,readUrl:undefined
,saveUrl:undefined
,method:'post'
,saveBaseParams:{}
,readBaseParams:{}
,paramNames:{
id:'id'
,name:'name'
,value:'value'
,user:'user'
,session:'session'
,data:'data'
}
}); // eo apply

if(this.autoRead) {
this.readState();
}

this.dt = new Ext.util.DelayedTask(this.submitState, this);
if(this.autoStart) {
this.start();
}
}; // eo constructor
// }}}

Ext.extend(Ext.ux.state.HttpProvider, Ext.state.Provider, {

// localizable texts
saveSuccessText:'Save Success'
,saveFailureText:'Save Failure'
,readSuccessText:'Read Success'
,readFailureText:'Read Failure'
,dataErrorText:'Data Error'

// {{{
/**
* Initializes state from the passed state object or array.
* This method can be called early during page load having the state Array/Object
* retrieved from database by server.
* @param {Array/Object} state State to initialize state manager with
*/
,initState:function(state) {
if(state instanceof Array) {
Ext.each(state, function(item) {
this.state[item.name] = this.decodeValue(item[this.paramNames.value]);
}, this);
}
else {
this.state = state ? state : {};
}
} // eo function initState
// }}}
// {{{
/**
* Sets the passed state variable name to the passed value and queues the change
* @param {String} name Name of the state variable
* @param {Mixed} value Value of the state variable
*/
,set:function(name, value) {
if(!name) {
return;
}

this.queueChange(name, value);

} // eo function set
// }}}
// {{{
/**
* Starts submitting state changes to server
*/
,start:function() {
this.dt.delay(this.delay);
this.started = true;
} // eo function start
// }}}
// {{{
/**
* Stops submitting state changes
*/
,stop:function() {
this.dt.cancel();
this.started = false;
} // eo function stop
// }}}
// {{{
/**
* private, queues the state change if state has changed
*/
,queueChange:function(name, value) {
var o = {};
var i;
var found = false;

// see http://extjs.com/forum/showthread.php?p=344233
var lastValue = this.state[name];
for(i = 0; i < this.queue.length; i++) {
if(this.queue[i].name === name) {
lastValue = this.decodeValue(this.queue[i].value);
}
}
var changed = undefined === lastValue || lastValue !== value;

if(changed) {
o[this.paramNames.name] = name;
o[this.paramNames.value] = this.encodeValue(value);
for(i = 0; i < this.queue.length; i++) {
if(this.queue[i].name === o.name) {
this.queue[i] = o;
found = true;
}
}
if(false === found) {
this.queue.push(o);
}
this.dirty = true;
}
if(this.started) {
this.start();
}
return changed;
} // eo function bufferChange
// }}}
// {{{
/**
* private, submits state to server by asynchronous Ajax request
*/
,submitState:function() {
if(!this.dirty) {
this.dt.delay(this.delay);
return;
}
this.dt.cancel();

var o = {
url:this.saveUrl || this.url
,method:this.method
,scope:this
,success:this.onSaveSuccess
,failure:this.onSaveFailure
,queue:Ext.ux.util.clone(this.queue)
,params:{}
};

var params = Ext.apply({}, this.saveBaseParams);
params[this.paramNames.id] = this.id;
params[this.paramNames.user] = this.user;
params[this.paramNames.session] = this.session;
params[this.paramNames.data] = Ext.encode(o.queue);

Ext.apply(o.params, params);

// be optimistic
this.dirty = false;

Ext.Ajax.request(o);
} // eo function submitState
// }}}
// {{{
/**
* Clears the state variable
* @param {String} name Name of the variable to clear
*/
,clear:function(name) {
this.set(name, undefined);
} // eo function clear
// }}}
// {{{
/**
* private, save success callback
*/
,onSaveSuccess:function(response, options) {
var o = {};
try {o = Ext.decode(response.responseText);}
catch(e) {
if(true === this.logFailure) {
this.log(this.saveFailureText, e, response);
}
this.dirty = true;
return;
}
if(true !== o.success) {
if(true === this.logFailure) {
this.log(this.saveFailureText, o, response);
}
this.dirty = true;
}
else {
Ext.each(options.queue, function(item) {
if(!item) {
return;
}
var name = item[this.paramNames.name];
var value = this.decodeValue(item[this.paramNames.value]);

if(undefined === value || null === value) {
Ext.ux.state.HttpProvider.superclass.clear.call(this, name);
}
else {
// parent sets value and fires event
Ext.ux.state.HttpProvider.superclass.set.call(this, name, value);
}
}, this);
if(false === this.dirty) {
this.queue = [];
}
else {
var i, j, found;
for(i = 0; i < options.queue.length; i++) {
found = false;
for(j = 0; j < this.queue.length; j++) {
if(options.queue[i].name === this.queue[j].name) {
found = true;
break;
}
}
if(true === found && this.encodeValue(options.queue[i].value) === this.encodeValue(this.queue[j].value)) {
this.queue.remove(this.queue[j]);
}
}
}
if(true === this.logSuccess) {
this.log(this.saveSuccessText, o, response);
}
this.fireEvent('savesuccess', this);
}
} // eo function onSaveSuccess
// }}}
// {{{
/**
* private, save failure callback
*/
,onSaveFailure:function(response, options) {
if(true === this.logFailure) {
this.log(this.saveFailureText, response);
}
this.dirty = true;
this.fireEvent('savefailure', this);
} // eo function onSaveFailure
// }}}
// {{{
/**
* private, read state callback
*/
,onReadFailure:function(response, options) {
if(true === this.logFailure) {
this.log(this.readFailureText, response);
}
this.fireEvent('readfailure', this);

} // eo function onReadFailure
// }}}
// {{{
/**
* private, read success callback
*/
,onReadSuccess:function(response, options) {
var o = {}, data;
try {o = Ext.decode(response.responseText);}
catch(e) {
if(true === this.logFailure) {
this.log(this.readFailureText, e, response);
}
return;
}
if(true !== o.success) {
if(true === this.logFailure) {
this.log(this.readFailureText, o, response);
}
}
else {
data = o[this.paramNames.data];
if(!(data instanceof Array) && true === this.logFailure) {
this.log(this.dataErrorText, data, response);
return;
}
Ext.each(data, function(item) {
this.state[item[this.paramNames.name]] = this.decodeValue(item[this.paramNames.value]);
}, this);
this.queue = [];
this.dirty = false;
if(true === this.logSuccess) {
this.log(this.readSuccessText, data, response);
}
this.fireEvent('readsuccess', this);
}
} // eo function onReadSuccess
// }}}
// {{{
/**
* Reads saved state from server by sending asynchronous Ajax request and processing the response
*/
,readState:function() {
var o = {
url:this.readUrl || this.url
,method:this.method
,scope:this
,success:this.onReadSuccess
,failure:this.onReadFailure
,params:{}
};

var params = Ext.apply({}, this.readBaseParams);
params[this.paramNames.id] = this.id;
params[this.paramNames.user] = this.user;
params[this.paramNames.session] = this.session;

Ext.apply(o.params, params);
Ext.Ajax.request(o);
} // eo function readState
// }}}
// {{{
/**
* private, logs errors or successes
*/
,log:function() {
if(console) {
console.log.apply(console, arguments);
}
} // eo log
// }}}

}); // eo extend

// eof

galdaka
31 Jan 2008, 11:15 PM
Sound interesting!!

Will be a demo in future?

Thanks in advance,

jsakalos
1 Feb 2008, 12:38 AM
Well, this is not very easy to "show" or "demonstrate" as it has no UI to be shown and everything runs in background. Anyway, should there be a big demand for a demo, I'd think of some and create.

sigaref
5 Feb 2008, 8:24 AM
Thank you for this code.

I managed to store the state from a grid in a DB, and to read the state back into this.state when loading the page.

But how can I set the grid state now with the settings from this.state? I have set the grid's stateId and stateful to true. Do I have to specify some kind of callback or call a special grid function?

Thanks in advance!

jsakalos
5 Feb 2008, 9:45 AM
I'd say that grid.initState() should be enough but if that changes column widths or another visual parts you may need to refresh view, doLayout or similar.

jsakalos
5 Feb 2008, 9:53 AM
One more remark: Provider request state from server upon construction. If you try to set grid state before it was received from server you see no effect as state is still empty.

I've also faced this problem so I updated HttpProvider code a bit and I send the initial state with initial page load. The rendered html of main page contains:



<script type="text/javascript">
Ext.state.Manager.setProvider(new Ext.ux.HttpProvider({
url:'/request.php?#state'
,user:'jozo@default'
,session:'Perseus.Stat'
,id:'1'
,readBaseParams:{cmd:'readState'}
,saveBaseParams:{cmd:'saveState'}
,autoRead:false
// ,logFailure:true
// ,logSuccess:true
}));
Ext.state.Manager.getProvider().initState([{"name":"client-mod-client-grid","value":"..."},{"name":"client-mod-win","value":"..."}]);
</script>

Of course, to create the initial state you need to iterate through your db server-side and to construct the above argument with states array for the provider.

Don't forget to grab the updated code from the first post.

devnull
5 Feb 2008, 3:01 PM
Very cool stuff, I may start experimenting with it too at some point.
One way to get around the the initial state issue I can think of is to have use a callback function with the initial state load. The typical app would then have most of its init code in this callback instead of in onReady.

jsakalos
5 Feb 2008, 3:14 PM
Well, I have already solved the initial state problem. First, state system doesn't need to run in onReady block as it doesn't need any DOM. Therefore, I generate the initial state very early in the page generation process, I create code for creation and initialization of the state manager and I output it in the header in <script> tag. See one post above.

This way the state is available even before the body is loaded and w/o any extra client server round trip.

swagner
13 Feb 2008, 4:30 AM
Your custom Provider looks very good, but i still don't know in detail what it does.

I also encountered the initial state problem while writing my own Provider. But in my project it does not seem to work the way you solved it (to set the initial setting at the start of the page). I think its because my page gets rendered into another page and the head of that page is already done (maybe i should use a delay-function after requesting the data from the server). How do you generate your initial state on top of your page? (inside the {})
Ext.state.Manager.getProvider().initState([{"name":"client-mod-client-grid","value":"..."}
Do you use an AjaxRequest to get this data? Probably not. You creat it using php or an other script language, right?

swagner
13 Feb 2008, 5:52 AM
Yeah :D, when i use a php-script to fetch the gridSettings from the DB i don't need an Ajax-Request cause the DB is requested (from the server-side) while the page gets rendered. And i can call the initState-Methode of my Provider right after i ordered Ext to use my Provider. All code is executed synchronously this way, which means i do not have to wait for an Ajax-Response.

swagner
13 Feb 2008, 7:05 AM
@jsakalos: I would like to know how your created javascript code at the start of the page looks exactly like when you write the settings for a grid. Can you give me an example-line of code for your line (i am interessted especially in the columns-part) :

Ext.state.Manager.getProvider().initState([{"name":"client-mod-client-grid","value":"..."}

jsakalos
13 Feb 2008, 10:12 AM
The logic is simple: There is a server side script that retrieves saved state from database and generates the whole output enclosed in <script></script> tags together with initial state json.

What "columns-part" do you mean, I don't see it there?

jack.slocum
13 Feb 2008, 12:01 PM
Did you guys take a look at the hybrid provider in the examples/state folder? It uses server side state (for the the example its in the session, but could go into a DB ).

Unlike fetching/saving of state over HTTP, it uses cookies with state, which the server collects and removes. Using this hybrid approach, there are no extra requests or processing, no big cookies being stored (they are removed by the server), the state is stored on the server and is available immediately when the page loads.

jsakalos
13 Feb 2008, 1:05 PM
Thanks for info Jack, I haven't seen it before.

Anyway, I'll keep HttpProvider in my application(s) because I've run several times into the problem when my Apache server complained about size of cookies when I was using CookieProvider. The only way how to run out of that problem was to delete cookies at client.

Therefore, I've completely dropped the idea of cookies holding state information and the example also uses cookies, with less risk of crossing a boundary of size though.

DigitalSkyline
13 Feb 2008, 1:36 PM
Jack - that actually sounds more in line with how I'd like to handle things, going to have to look in to it. Thanks for the suggestion!

swagner
14 Feb 2008, 12:43 AM
Yes, good to know. My custom Provider is actually working fine (thanks to jsakalos (and his example code)) and the data goes as hash into my database instead into a cookie. While transfering the encodeValued hash, this hash is loosing it's urlencoding. So i urlencode my hash from the database when i get it and write it into the initState-method (of my Provider) at the start of my page (as jsakalos does). I decodeValue this hash in my Provider and receive a state object which i set as my state then. The grid gets rendered with the settings from the database afterwards.

iwtrading
21 Feb 2008, 1:48 AM
Hi Jozef,

Thank you for your work.

I try HttpProvider to restore a window position and size, but it doesn't work.
I don't know where my error is.



var prv = Ext.state.Manager.getProvider();
prv.on('readsuccess', function() {
var w = Ext.getCmp('mywindow');
w.initState();
// w.render();
// w.doLayout();
w.show();
});
prv.readState();
...

jsakalos
21 Feb 2008, 2:22 AM
Do you initialize provider as described here http://extjs.com/forum/showthread.php?p=119791#post119791 ?

iwtrading
21 Feb 2008, 5:55 AM
Do you initialize provider as described here http://extjs.com/forum/showthread.php?p=119791#post119791 ?

Yes I do.

I have seen that invoking more times the method:


var prv = Ext.state.Manager.getProvider();
prv.submitState();

The new window position is appended on http request:


[http params]
cmd=saveState&data=%5B%7B%22
name%22%3A%22mywindow%22%2C%22value%22%3A%22o%253Awidth%253Dn%25253A700%255Eheight%253Dn%25253A302%255Ex%253Dn%25253A290%255Ey%253Dn%25253A175%22%7D%2C%7B%22
name%22%3A%22mywindow%22%2C%22value%22%3A%22o%253Awidth%253Dn%25253A356%255Eheight%253Dn%25253A302%255Ex%253Dn%25253A290%255Ey%253Dn%25253A175%22%7D%2C%7B%22
name%22%3A%22mywindow%22%2C%22value%22%3A%22o%253Awidth%253Dn%25253A356%255Eheight%253Dn%25253A302%255Ex%253Dn%25253A290%255Ey%253Dn%25253A175%22%7D%5D&id=1

Should "mywindow" params appear only once?

iwtrading
21 Feb 2008, 6:25 AM
Is this a possible solution?


queueChange:function(name, value) {
var changed = undefined === this.state[name] || this.state[name] !== value;
var o = {};
if(changed) {
o[this.paramNames.name] = name;
o[this.paramNames.value] = this.encodeValue(value);
var found = false;
for (var ii=0; ii<this.queue.length-1; ii++) {
if (this.queue[ii].name == o.name) {
this.queue[ii] = o;
found = true;
}
}
if (!found) {
this.queue.push(o);
}
this.dirty = true;
}
return changed;
},

iwtrading
21 Feb 2008, 6:38 AM
Is this a possible solution?


queueChange:function(name, value) {
var changed = undefined === this.state[name] || this.state[name] !== value;
var o = {};
if(changed) {
o[this.paramNames.name] = name;
o[this.paramNames.value] = this.encodeValue(value);
var found = false;
for (var ii=0; ii<this.queue.length-1; ii++) {
if (this.queue[ii].name == o.name) {
this.queue[ii] = o;
found = true;
}
}
if (!found) {
this.queue.push(o);
}
this.dirty = true;
}
return changed;
},



Bugfix:


...
for (var ii=0; ii<this.queue.length; ii++) {
if (this.queue[ii][this.paramNames.name] == o[this.paramNames.name]) {
this.queue[ii] = o;
found = true;
}
}
...

jsakalos
21 Feb 2008, 8:05 AM
Yes, thank you for debugging. Grab the new code from the first post.

:) :) :)

iwtrading
21 Feb 2008, 9:59 AM
Another change, in onReadSuccess:

Bugfix

orig:


if(!(data instanceof Array) && true === this.logFailure) {
this.log(this.dataErrorText, data, response);
return;
}

patch:


if (data == null || !(data instanceof Array)) {
if (true === this.logFailure) {
this.log(this.dataErrorText, data, response);
}
return;
}

and in queueChange:

orig:


var changed = undefined === this.state[name] || this.state[name] !== value;

my version:


var changed = undefined === this.queue[name] || this.queue[name] !== value;


What do you think about?

jsakalos
21 Feb 2008, 10:34 AM
Another change, in onReadSuccess:

Bugfix

orig:


if(!(data instanceof Array) && true === this.logFailure) {
this.log(this.dataErrorText, data, response);
return;
}patch:


if (data == null || !(data instanceof Array)) {
if (true === this.logFailure) {
this.log(this.dataErrorText, data, response);
}
return;
}
The test you propose is redundant as if data === null (btw, always use three = when testing for null) data is not instance of array.


and in queueChange:

orig:


var changed = undefined === this.state[name] || this.state[name] !== value;my version:


var changed = undefined === this.queue[name] || this.queue[name] !== value;What do you think about?No, you have to test if state changed, not queue. Queue is very fragile thing, it's cleared always on successful server save. Purpose of this test is not to save state item that hasn't changed.

iwtrading
22 Feb 2008, 1:25 AM
I have some problems, sometimes I lose any values.

scenario:
[desktop example]
- drag and drop windows
- save to server with HttpProvider saveSubmit
- page refresh
- windows are in correct position
- I don't change anything
- save to server with HttpProvider saveSubmit
- page refresh
- error: I lose the windows position information

It perhaps misses the passage from state to queue.
The queue is empty and override the state value.
What do you think?

jsakalos
22 Feb 2008, 2:24 AM
Do you explicitly call submitState()? If yes you shouldn't.

I'm using the same code as is in the first post and I never lost any state of anything whatsoever. Doesn't it get lost serverside?

iwtrading
22 Feb 2008, 2:42 AM
Doesn't it get lost serverside?
yes, you are right.



Do you explicitly call submitState()? If yes you shouldn't.
Yes, I call submitState(), because I want to save the state only when I leave the application.
Are there possible problems doing in this way?

jsakalos
22 Feb 2008, 10:45 AM
Well, if you found the problem: "state lost serverside", and the above mentioned call does work, in that case I do not see any problem. Just set timeout high enough and call submitState on exit...

iwtrading
26 Feb 2008, 12:23 AM
Thank you Jozef,

I have another problem:
when the event 'onunload' fires I call this:
Ext.state.Manager.getProvider().submitState();

It's work fine, but with IExplorer I have this error (example):
The instruction at "0x0000000000000" referenced memory at "0x0000000000000". The memory could not be "read".

jsakalos
26 Feb 2008, 12:37 AM
Unfortunately, I have no idea. First, I do not use Micro$oft products and second, I've never used HttpProvider in this setup. BTW, onbeforeunload event is one of the most painful spots if you want a cross-browser application. I was replying to one thread recently where one user wanted to implement it and failed. You can try to search forums for onbeforeunload keyword.

jonx
12 Mar 2008, 2:38 AM
@jsakalos: this is exactly what I was looking for but unfortunately I don't have the required skills to use it. You are right when you say that it's nothing visual but this doesn't mean it's more easy for me to use ;) You affered to provide a demo if enought people would request it. I'm pretty tall. Do you think I can count as two people requesting for a demo :D
Even a simple sample would be nice. That way I could see where to refer to your code, where I shall call it to save state, to restore state. And if I may suggest a type of demo... hum, maybe you could take the portal sample and show how to save and restore the portlets columns and positions... H

jsakalos
12 Mar 2008, 3:26 AM
OK, I'll prepare a demo but be patient please as I'm pretty busy these day (months? ) and this is not very high in my priorities... ;)

Thank you for nice words. :)

jonx
12 Mar 2008, 4:06 AM
Those are the rules... I fully understand...
Thanks a lot in advance,
Ahoj !

dyndan
17 Mar 2008, 3:18 AM
Hi jsakalos,

thanks for your state provider, it saved me a lot of work. But before I could use it, I had to make a couple of changes cause your implementation is

not thread-safe.

Please see my commented changes in your code:



// vim: ts=4:sw=4:nu:fdc=2:nospell
/**
* Ext.ux.HttpProvider extension
*
* @author Ing. Jozef Sakalos
* @copyright (c) 2008, Ing. Jozef Sakalos
* @version $Id: Ext.ux.HttpProvider.js 734 2008-02-21 16:04:24Z jozo $
*
* @license Ext.ux.HttpProvider is licensed under the terms of
* the Open Source LGPL 3.0 license. Commercial use is permitted to the extent
* that the code/component(s) do NOT become part of another Open Source or Commercially
* licensed development library or toolkit without explicit permission.
*
* License details: http://www.gnu.org/licenses/lgpl.html
*/

/**
* @class Ext.ux.HttpProvider
* @extends Ext.state.Provider
* @constructor
* @param {Object} config Configuration object
*/
// {{{
Ext.ux.HttpProvider = function(config) {

this.addEvents('readsuccess', 'readfailure', 'savesuccess', 'savefailure');

// call parent
Ext.ux.HttpProvider.superclass.constructor.call(this);

Ext.apply(this, config, {
// defaults
delay:750 // buffer changes for 750 ms
,dirty:false
,started:false
,autoStart:true
,autoRead:true
,user:'user'
,id:1
,session:'session'
,logFailure:false
,logSuccess:false
//Here I use an object instead of an array. (Reason will be explained)

,queue:{}
,queueLength:0
//,queue:[]
,url:'.'
,readUrl:undefined
,saveUrl:undefined
,method:'post'
,saveBaseParams:{}
,readBaseParams:{}
,paramNames:{
id:'id'
,name:'name'
,value:'value'
,user:'user'
,session:'session'
,data:'data'
}
}); // eo apply

if(this.autoRead) {
this.readState();
}

this.dt = new Ext.util.DelayedTask(this.submitState, this);
if(this.autoStart) {
this.start();
}
}; // eo constructor
// }}}

Ext.extend(Ext.ux.HttpProvider, Ext.state.Provider, {

// localizable texts
saveSuccessText:'Save Success'
,saveFailureText:'Save Failure'
,readSuccessText:'Read Success'
,readFailureText:'Read Failure'
,dataErrorText:'Data Error'

// {{{
,initState:function(state) {
if(state instanceof Array) {
Ext.each(state, function(item) {
this.state[item.name] = this.decodeValue(item.value);
}, this);
}
else {
this.state = state ? state : {};
}
} // eo function initState
// }}}
// {{{
,set:function(name, value) {
if(!name) {
return;
}

this.queueChange(name, value);

} // eo function set
// }}}
// {{{
,start:function() {
this.dt.delay(this.delay);
this.started = true;
} // eo function start
// }}}
// {{{
,stop:function() {
this.dt.cancel();
this.started = false;
} // eo function stop
// }}}
// {{{
,queueChange:function(name, value) {
var changed = undefined === this.state[name] || this.state[name] !== value;
var o = {};
var i;
var found = false;
if(changed) {
o[this.paramNames.name] = name;
o[this.paramNames.value] = this.encodeValue(value);

//Using an object as queue simplifies things here:

if (!this.queue[name]) this.queueLength++;
this.queue[name]=o;
//for(i = 0; i < this.queue.length; i++) {
// if(this.queue[i].name === o.name) {
// this.queue[i] = o;
// found = true;
// }
//}
//if(false === found) {
// this.queue.push(o);
//}

this.dirty = true;
}
return changed;
} // eo function bufferChange
// }}}
// {{{
,submitState:function() {
if(!this.dirty) {
this.dt.delay(this.delay);
return;
}
this.dt.cancel();

//Copy queue (Reason will be explained)

var queueCopy={};
for (var p in this.queue) {
if (typeof this.queue[p]=="object" && this.queue[p]!=null) {
queueCopy[p]=this.queue[p];
};
};

var o = {
url:this.saveUrl || this.url
,method:this.method
,scope:this
,success:this.onSaveSuccess
,failure:this.onSaveFailure
//For later comparison purposes I pass a copy of this.queue here instead of a
//reference to the queue itself cause the queue possibly changes during the
//remote server call.

,queue:this.queueCopy
//,queue:this.queue
,params:{}
};

var params = Ext.apply({}, this.saveBaseParams);
params[this.paramNames.id] = this.id;
params[this.paramNames.user] = this.user;
params[this.paramNames.session] = this.session;
params[this.paramNames.data] = Ext.encode(this.queue);

Ext.apply(o.params, params);
Ext.Ajax.request(o);
} // eo function submitState
// }}}
// {{{
,clear:function(name) {
this.set(name, undefined);
} // eo function clear
// }}}
// {{{
,onSaveSuccess:function(response, options) {
if(this.started) {
this.start();
}
var o = {};
try {o = Ext.decode(response.responseText);}
catch(e) {
if(true === this.logFailure) {
this.log(this.saveFailureText, e, response);
}
return;
}
if(true !== o.success) {
if(true === this.logFailure) {
this.log(this.saveFailureText, o, response);
}
}
else {
//If you loop over options.queue without having passed a queue copy but a
//reference to the queue itself (options.queue==this.queue) you possibly loop
//over entries that were written during the time it took the server to answer the
//state submit.

//Ext.each(options.queue, function(item) {

//Looping over my copy of queue

for (var p in options.queue) {
var item=options.queue[p];

var name = item[this.paramNames.name];
var value = this.decodeValue(item[this.paramNames.value]);

//Here only these queue entries are deleted, which are found in the queue copy
//created by the time the call was sent to the server.
//Cause I use a object as queue identifing the corresponding entry is easy.

this.queue[name]=null;
this.queueLength--;

if(undefined === value || null === value) {
Ext.ux.HttpProvider.superclass.clear.call(this, name);
}
else {
// parent sets value and fires event
Ext.ux.HttpProvider.superclass.set.call(this, name, value);
}

};

//}, this);

//Throwing away the queue like this leads to all kind of strange state saving effects
//cause by the time the server returns a state saving cycle there can be a whole
//bunch of new entries in the queue which possibly were not sent to server yet.

//this.queue = [];
//this.dirty = false;

//Instead, only if the queueLength appears to be 0 I initialise the queue
//and remove the dirty flag:

if (this.queueLength==0) {
this.queue={};
this.dirty=false;
}


if(true === this.logSuccess) {
this.log(this.saveSuccessText, o, response);
}
this.fireEvent('savesuccess', this);
}
} // eo function onSaveSuccess
// }}}
// {{{
,onSaveFailure:function(response, options) {
if(true === this.logFailure) {
this.log(this.saveFailureText, response);
}
if(this.started) {
this.start();
}
this.fireEvent('savefailure', this);
} // eo function onSaveFailure
// }}}
// {{{
,onReadFailure:function(response, options) {
if(true === this.logFailure) {
this.log(this.readFailureText, response);
}
this.fireEvent('readfailure', this);

} // eo function onReadFailure
// }}}
// {{{
,onReadSuccess:function(response, options) {
var o = {}, data;
try {o = Ext.decode(response.responseText);}
catch(e) {
if(true === this.logFailure) {
this.log(this.readFailureText, e, response);
}
return;
}
if(true !== o.success) {
if(true === this.logFailure) {
this.log(this.readFailureText, o, response);
}
}
else {
try {data = Ext.decode(o[this.paramNames.data]);}
catch(ex) {
if(true === this.logFailure) {
this.log(this.dataErrorText, o, response);
}
return;
}
if(!(data instanceof Array) && true === this.logFailure) {
this.log(this.dataErrorText, data, response);
return;
}
Ext.each(data, function(item) {
this.state[item[this.paramNames.name]] = this.decodeValue(item[this.paramNames.value]);
}, this);

//Queue changed to object

this.queue={}:

//this.queue = [];

this.dirty = false;
if(true === this.logSuccess) {
this.log(this.readSuccessText, data, response);
}
this.fireEvent('readsuccess', this);
}
} // eo function onReadSuccess
// }}}
//

// {{{
,readState:function() {
var o = {
url:this.readUrl || this.url
,method:this.method
,scope:this
,success:this.onReadSuccess
,failure:this.onReadFailure
,params:{}
};

var params = Ext.apply({}, this.readBaseParams);
params[this.paramNames.id] = this.id;
params[this.paramNames.user] = this.user;
params[this.paramNames.session] = this.session;

Ext.apply(o.params, params);
Ext.Ajax.request(o);
} // eo function readState
// }}}
// {{{
,log:function() {
if(console) {
console.log.apply(console, arguments);
}
} // eo log
// }}}

}); // eo extend

// eof
dyndan

jsakalos
17 Mar 2008, 3:37 AM
Thanks for pointing out.

jsakalos
17 Mar 2008, 4:14 AM
I was looking a bit into it (not very high on my priority list) and your implementation changes the server api as you haven't done anything with submitState routine. Server expects that it will receive array of objects and after your changes it will receive nested objects keyed by name.

I'll take another look at it, as you're right that it is not thread-safe, to find out an implementation that would keep api.

dyndan
17 Mar 2008, 5:00 AM
You're right,

I forgot this part cause I use a different server implementation which requires that every state is postet as "param". The server then recognises the relevant arguments by naming convention.

I did not want to mix these changes in here.

To solve the problem and keep the server api, the following line of code



params[this.paramNames.data] = Ext.encode(this.queue);
has to be replaced as follows:




var a = [];
var aIdx=0;
for (var p in this.queue) a[aIdx++]= this.queue[p];
params[this.paramNames.data] = Ext.encode(a);
dyndan

jsakalos
17 Mar 2008, 10:49 AM
Hi dyndan,

would you please take a look at the code now? I found a spare hour so I fixed it to be thread-safe. The logic is that I'm optimistic in the first place so I reset dirty flag before submitting. In callback, if the flag is still reset, I just clear old queue (this.queue = [];) and I do a special processing (deleting unchanged entries) only if the queue dirtied meanwhile.

I guess this is of best performance as complex part of the code (finding an clearing unchanged entries) is executed only if needed. Also, server API stays same.

I haven't had an opportunity to thoroughly check the new logic so all your inputs are welcome.

Thanks.

jsakalos
24 Mar 2008, 4:58 PM
Sound interesting!!

Will be a demo in future?

Thanks in advance,

There is no demo dedicated to HttpProvider but http://rowactions.extjs.eu uses HttpProvider to keep state of window and grid.

jonx
1 Apr 2008, 8:43 AM
There is demo dedicated to HttpProvider but http://rowactions.extjs.eu uses HttpProvider to keep state of window and grid.
Hello jsakalos
thank you for providing a demo. I have still not finished adapting it to my code as I'm using asp.net and I'm trying to persist the state of the portal sample. I made some progresses but this still needs work ;)

Thanks again...

jack.slocum
1 Apr 2008, 7:22 PM
Hey guys, as mentioned in my previous post on this thread, you really should take a look at the hybrid provider in the examples/state folder of the distribution.

Saki, in response to your previous concern, only the state modification taking place is set in a cookie not the entire state (unlike CookieProvider). Because of that, there is no issue with the size of cookies getting too large.

I really would like to encourage you to take a look as it is definitely a preferred way to tackle this issue and will result in a much better user experience (reduced HTTP requests), no data loss (if they leave the page immediately their state is still safe, unlike HTTP which aborts on leave) and less/simplified code. I've used the approach in many applications with great success.

jonx
2 Apr 2008, 12:14 AM
Hello Jack,
I agree with you that in most cases the hybrid provider or even the cookie provider is a better choice (by the fact it's easier to setup and use) but just in my current project I can't use cookies at all. Meaning Saki's HttpProvider is the only way to go for me right now...

jsakalos
2 Apr 2008, 3:27 AM
Hi Jack,

I'll definitely take a look at it although it is not top priority for me right now. I've written this one before knowing about the Hybrid State provider and it works just fine for me and other. Anyway, there is always a space for improvement that I'll be only glad to fill...

Cheers,
Saki

stever
3 Apr 2008, 7:30 PM
Hi jsakalos,

Can you explain how how a state provider works? It seems to do nothing.

jsakalos
3 Apr 2008, 7:38 PM
Yeah, visually it does nothing but if you move a window new position is send to the server to be stored in a database. Next time you open the same window it opens in the last saved postition.

stever
3 Apr 2008, 7:59 PM
The manual talks about restoreState:

// in your initialization function
init : function(){
Ext.state.Manager.setProvider(new Ext.state.CookieProvider());
...
// supposed you have a "Ext.BorderLayout" href="output/Ext.BorderLayout.html">Ext.BorderLayout
var layout = new Ext.BorderLayout(...);
layout.restoreState();
// or a {Ext.BasicDialog}
var dialog = new Ext.BasicDialog(...);
dialog.restoreState();

But I can't find "restoreState" in the manual/API reference other than in that one spot.

At any rate, I think the example I'm working with is a bad one, which is why I don't see anything. I'm trying to get an accordian to remember which panel to show, but there is no event for setActiveItem. I think I need to have all the children save state instead.

jsakalos
4 Apr 2008, 3:28 AM
You could take a look at Ext.Component source code, this class deals with state most. You can also take a look at http://extjs.eu where I save collapsed state of panels. On simple function getState does it there.

jonx
4 Apr 2008, 5:22 AM
I also suggest you use firefox and the firebug module. that way you'll be able t see easily what is exchanged with the server... see the attachment...

jsakalos
4 Apr 2008, 6:07 AM
I think that this has already been solved in an another thread; correct me if I'm wrong...

Richie1985
11 Apr 2008, 5:55 AM
I want to use this script with a mysql backend. I become an error in the php saveState Function:

Invalid argument supplied for foreach()

because the data array is empty.

when i make a test output in the php file like this

echo json_decode($_POST["data"]);
i see nothing

with

echo $_POST["data"];

i see

[{\"name\":\"actiongrid\",\"value\":\"o:columns=a%3Ao%253Aid%253Ds%25253Acompany%255Ewidth%253Dn%25253A273%5Eo%253Aid%253Dn%25253A1%255Ewidth%253Dn%25253A146%5Eo%253Aid%253Dn%25253A2%255Ewidth%253Dn%25253A146%5Eo%253Aid%253Dn%25253A3%255Ewidth%253Dn%25253A67^sort=o%3Afield%3Ds%253Acompany%5Edirection%3Ds%253ADESC\"}]

can you help me?

jsakalos
11 Apr 2008, 6:23 AM
Check the source of state-sqlite.php at http://rowactions.extjs.eu.

Result of json_decode is array so you had better to use print_r instead of echo.

jsakalos
11 Apr 2008, 6:39 AM
Do you think it is a bug in HttpProvider? The above looks like PHP syntax error...

Richie1985
11 Apr 2008, 6:55 AM
sorry there was a error from me :(

i mean:

on first step of state.php i make to test this output:

echo $_POST["data"];

and it show me: [{\"name\":\"portal_south\",\"value\":\"o%3Acollapsed%3Db%253A1\"}]

and then i make this:


print_r($clientArgs);

and it show me:
stdClass Object
(
[id] => 1
[user] => 21117
[session] => session
[data] =>
)

why is the data field empty?

thanks

jsakalos
11 Apr 2008, 7:50 AM
No state has been sent (yet) from provider? Otherwise I have no idea... PHP configuration maybe, limiting POST size, or some another server-side issue.

Richie1985
11 Apr 2008, 8:16 AM
i found the problem, in the data string where "\"


$clientArgs->data = isset($_POST["data"]) ? json_decode(str_replace("\\", "", $_POST["data"])) : array(); solve the problem.

and now he saves states in my database.

but what i dont know, how can i save and restore with this script portal panels position etc.?

jsakalos
11 Apr 2008, 9:05 AM
That's another story... There was a thread about it - please search.

Richie1985
16 Apr 2008, 12:50 AM
mhhh, i found no cool example for this, your thread (http://extjs.com/forum/showthread.php?t=25042) i dont realy understand.

when i drag a portlet your script saves this in the database: "o%3Acollapsed%3Db%253A0%5Ehidden%3Db%253A0%5Ecolumn%3Ds%253Aext-comp-1006"

so the column is in the database, but when i reload the page he dont set the portlets to this column :(

have you a idea?

ps: echt schade das hier keiner deutsch spricht :(

jsakalos
16 Apr 2008, 2:57 AM
There is no Ext-built-in code for moving portlets to their saved positions. You need to write this code yourself.

Richie1985
16 Apr 2008, 4:33 AM
ohh this sound not good :(

have you a idea how i can do this?

jsakalos
16 Apr 2008, 1:11 PM
I've done it once but my code is too specific an posting it here would do no good. The best is to create portlet at their final destinations instead of moving them afterwards...

Richie1985
17 Apr 2008, 12:23 AM
okay i do this. then i have only 1 question, how can i put the value string (o%3Aposition%3Dn%253A0%5Ecollapsed%3Db%253A1%5Ehidden%3Db%253A0%5Ecolumn%3Ds%253Achart_2)

in a array? Ext.encode?? or what?

i thank you sooo much.

jsakalos
17 Apr 2008, 3:01 AM
You don't need to. This string is internal representation used by the provider. It decodes it for you and you can then use method: Ext.state.Manager.get('variableName').

NoahK17
17 Jun 2008, 7:07 AM
Saki: I haven't thoroughly looked through all your code yet, but have you provided and example of the mySql table back-end setup you use for your provider?

Cheers, and I apologize if you've already answered this question :)

jsakalos
18 Jun 2008, 3:05 AM
Here is the example and sqlite backend: http://rowactions.extjs.eu/ MySQL backend would be very similar if not same as sqlite's.

nebbian
14 Aug 2008, 1:19 AM
Hi Saki,

I ran into a problem where the cookies were getting too big for apache... did lots of searches, and eventually found my way to your site, yet again! It seems that every time I hit a problem you've solved it already :-?

I installed your HttpProvider extension, and it works perfectly!

Many thanks for all the hard work you've put in =D>

jsakalos
14 Aug 2008, 8:49 AM
You're welcome. I'm glad that you find my sites helpful. :)

DamianHartin
27 Aug 2008, 2:12 AM
Thanks for another great extension Saki.

Just wondering if anyone has implemented this in an ASP.Net environment. I don't really know any php though I can figure out most of what's here - I'm just getting stuck on some of the aspx setup??

e.g. I figure that I don't need to reference/call <?require("provider-sqlite.php")?> from my default.aspx page, I can just add logic to the code behind page to look up my database and return the state information... but how do I instantiate the 'Ext.state.Manager.setProvider' (as is done in 'provider-sqllite.php in the example) from the code behind page??

Any pointers would be greatly apreciated

Thanks

jsakalos
27 Aug 2008, 6:33 AM
Sorry, sbdy else must help you as I don't use M$ "products" except the hardware ones.

mepfuso
1 Sep 2008, 2:12 AM
Thanks for another great extension Saki.

Just wondering if anyone has implemented this in an ASP.Net environment. I don't really know any php though I can figure out most of what's here - I'm just getting stuck on some of the aspx setup??

e.g. I figure that I don't need to reference/call <?require("provider-sqlite.php")?> from my default.aspx page, I can just add logic to the code behind page to look up my database and return the state information... but how do I instantiate the 'Ext.state.Manager.setProvider' (as is done in 'provider-sqllite.php in the example) from the code behind page??

Any pointers would be greatly apreciated

Thanks


You must instantiate the StateManager somewhere in the javascript of your page, for example:




Ext.state.Manager.setProvider(new Ext.ux.HttpProvider({saveUrl: 'Handlers/ExtStateHandler.ashx'}));



This must be after Ext is loaded, but before you call Ext.onReady(...).

If you want to control this client script output from the server-side, you would use the ASP.NET Placeholder control or write your own control (for example an ExtScriptManager as I did) which spawns all necessary client script.

In order to save the state information to the server, I would then recommend to create a folder in your website called 'Handlers', as I did specify it in the setProvider statement above.

In this folders, you would do a 'Add new item' in your Visual Studio and choose 'Generic Handler'. You would name this handler 'ExtStateHandler.ashx' in this case.

The code would look something like this:




<%@ WebHandler Language="C#" Class="ExtStateHandler" %>

using System;
using System.Web;
using System.Web.SessionState;

using Jayrock.Json;
using Jayrock.Json.Conversion;

public class ExtStateHandler : IHttpHandler, IRequiresSessionState
{
public bool IsReusable
{
get { return false; } // return true only if your code in this Handler is thread safe! Otherwise return false!
}

public void ProcessRequest(HttpContext context)
{
string data = context.Request.Form["data"];

if (!String.IsNullOrEmpty(data))
{
JsonArray arr = JsonConvert.Import(data) as JsonArray;

if (arr != null && arr.Length >= 0)
{
foreach (JsonObject obj in arr)
{
if (obj != null)
{
string name = obj["name"] as string;

if (!String.IsNullOrEmpty(name))
{
object value = obj["value"];

// now you can save the state in your session, for example:

context.Session["ExtState_" + name] = value;

// and save all session keys which start with "ExtState_" later in your database,
// for example when the user logs out or unloads the web page where
// you would call another handler to do that,
// or update your database directly now.
}
}
}
}
}

context.Response.Write("{success: true}");
}
}



Get JayRock from http://jayrock.berlios.de/ if you don't have it already.

This handler requires more error handling and stuff, but I tried to keep things straight. You would normally derive it from a base class where you do some things you always need.

Now you have a way to save the Ext state. If you need help to restore/load the state on the client-side, let me know.

KJedi
10 Sep 2008, 8:28 AM
Here are some tips for implementing PHP-backend for this state provider:
1) Personally I needed encodeValue function, that produces the same result as Ext.state.Provider.encodeValue. Here is it:

function encodeValue($val)
{
if (is_integer($val))
{
$res = 'n:'.$val;
}
elseif (is_bool($val))
{
$res = 'b:'.($val ? 1 : 0);
}
elseif (is_array($val))
{
$flat = '';
for ($i = 0, $s = sizeof($val); $i < $s; $i++)
{
$flat .= $this->encodeValue($val[$i]);
if ($i != $s - 1) $flat .= '^';
}
$res = 'a:'.$flat;
}
elseif (is_object($val))
{
$flat = '';
foreach ($val as $k => $v)
{
$flat .= $k.'='.$this->encodeValue($v).'^';
}
$flat = substr($flat, 0, -1);
$res = 'o:'.$flat;
}
else
{
$res = 's:'.$val;
}
return JSEscape($res);
}

2) JSEscape is function, that behaves like JS built-in escape function:

function JSEscape($str)
{
$search = array('~','!','@','#','$','%','^','&','*','(',')','{','}','[',']','=',':','/',',',';','?','+','\'','"','\\');
$replace = array('%7E', '%21', '@', '%23', '%24', '%25', '%5E', '%26', '*', '%28', '%29', '%7B', '%7D', '%5B', '%5D', '%3D', '%3A', '/',
'%2C', '%3B', '%3F', '+', '%27', '%22', '%5C');
return str_replace($search, $replace, $str);
}

3) And the same JSUnescape:

function JSUnescape($str)
{
$search = array('%7E', '%21', '@', '%23', '%24', '%25', '%5E', '%26', '*', '%28', '%29', '%7B', '%7D', '%5B', '%5D', '%3D', '%3A', '/',
'%2C', '%3B', '%3F', '+', '%27', '%22', '%5C');
$replace = array('~','!','@','#','$','%','^','&','*','(',')','{','}','[',']','=',':','/',',',';','?','+','\'','"','\\');
return str_replace($search, $replace, $str);
}

4) Preparing PHP structure for json_encode:

$fields = $user->GetAllFields();
foreach ($fields as $field => $val)
{
if ($field == 'Password') continue;//don't export Password
$value = new stdClass();
$value->name = $field;
$value->value = $this->encodeValue($val);
$sessData[] = $value;
}
Then you just do json_encode($sessData)

5) When you take some actions on SaveState action, you may need to decode what is being saved (consider we have $st=$_POST['data'] somewhere):


$data = json_decode(stripslashes($st));
if (!is_array($data)) return true;
for ($i = 0, $s = sizeof($data); $i < $s; $i++)
{
if ($data[$i]->name == '...')
{
////////////////////////take needed actions here
}
}


Maybe it was posted somewhere, sorry if I missed that. Good luck!

jsakalos
10 Sep 2008, 10:55 AM
You can get my backend at http://rowactions.extjs.eu - just click on state-sqlite.php link.

DamianHartin
10 Sep 2008, 2:57 PM
Thanks for the great replies.

I've been away and havent had a chance to work further on this... but will do so tonight and let you all know how I go. (special thanks for the .Net help mepfuso)

Cheers,
Damian

timb
12 Sep 2008, 10:01 AM
First of all, I have to thank Saki for sharing this code! It was exactly what I was looking for.

I have this working with ASP.NET on the server. I've decided to use Json.NET (http://james.newtonking.com/pages/json-net.aspx) instead of Jayrock like mepfuso did (mepfuso, thanks for the sample! it's very similar using Json.NET). Everything was working fine, but then I decided to give the user the option of resetting the state. I've added a new header context menu that calls:

Ext.state.Manager.getProvider().clear('myName');
This didn't work so I walked through the code. I found that in the onSaveSuccess event the following is never true:

if (undefined === value || null === value) {
Ext.ux.HttpProvider.superclass.clear.call(this, name);
}
This is because value is equal to "undefined" with the quotes. So I tried calling the following instead of clear to see what happens:

Ext.state.Manager.getProvider().set('myName', null);
In this case, the value is equal to an empty object ({}), not null. I changed my code back to use clear and changed the HttpProvider to check for "undefined" === value. This worked, but the visual state of the grid was not reset. If I reload the screen, the grid is reset.

I have two questions:

Is there a problem with the source code when checking to see if the value is null, or is there a problem with the encoding on the server? Can someone with php try it out and see what happens?
Why isn't the visual state of the grid resetting? Is there something else that I need to do?


Thanks!
Tim

P.S. I decided to use this method rather than the hybrid approach due to the following assumption on the hybrid approach (please correct me if I'm wrong): It appears that the state is only saved on the server when the user loads the page again after changing the state. That means if the page is not loaded again, the state will not be saved. This is a problem if a) the cookies are cleared before loading the page again, or b) if the user changed the state on one computer and didn't reload the page and then viewed that page on a second computer.

jsakalos
12 Sep 2008, 11:28 AM
Re 1: I don't know what your server returns. Normally the server shouldn't return 'myName' at all, then undefined === value would evaluate to true.

Re 2: State is independent of the grid. You need to do both clear state and reset the grid.

Re PS: I've never tried hybrid provider - I cannot say anything qualified about that.

timb
12 Sep 2008, 1:09 PM
Thanks for the feedback.

Re 1: myName was just an example. Here's the value that is being returned from the server after using the clear method:
[{"name":"myName","value":"s%3Aundefined"}]

Once decoded, the value is equal to "undefined" (with quotes). Although I was able to resolve the logic by modifying the if statement to check for "undefined", I thought I'd share my solution just in case someone else was having the same problem. I also wanted to see if this was a problem with the Json.NET serializer on the server side (that's why I asked if anyone had the same problem using php).

Re 2: What do you mean when you say "reset" the grid? I have a grid panel and I tried using this.getView().refresh(true) on both the "savesuccess" and "statechange" events after clearing the state. I searched the documentation and I found the following example:
Ext.state.Manager.setProvider(new Ext.state.CookieProvider());
var win = new Window(...);
win.restoreState();

I searched the source for restoreState and the only place it found it was in the comment. I'm guessing this is "old" code that is no longer valid? I searched the forums too and didn't have much luck. Any advice would be helpful.

Thanks,
Tim

DamianHartin
12 Sep 2008, 9:37 PM
Finally got to implement your sample code mepfuso (thanks :)) and it's working perfectly storing data as a session variable (I still need to write it to the db but that's no prob)

I am however having trouble applying the state client side???? Could I ask how you (or timb) did it?

I am first off just trying to get Saki's theme combo to apply the state (per his code):



this.setValue(Ext.state.Manager.get(this.themeVar) || 'xtheme-slate.css');


but it wont work. If I try to alert() the value of Ext.state.Manager.get(this.themeVar) I get undefined. (a test aspx page that response.writes the value of the session variable 'ExtState_theme' shows the correct value though).

- I dont even know where to start with applying state to a grid -

Help Please :((

jsakalos
12 Sep 2008, 9:48 PM
@timb,

Re 1: Do not return myName at all and it should work. Encoded undefined value results in "undefined" (string).

Re 2: Let's imagine that you move a stateful window. The new position is saved by state manager. Now, you clear the state what means that you remove/delete the saved state. Of course, clearing the state does not move the window to the original position. That was what I was talking about: a) clear state and b) put the window/grid/component to its original state.

jsakalos
12 Sep 2008, 9:50 PM
@DamianHartin,

take a look at some of my examples or main page http://extjs.eu. There you can see working stateful ThemeCombo.

DamianHartin
13 Sep 2008, 6:50 PM
Thanks Saki,

I have been looking at rowactions.js (from your examples page) but I cant see where you apply the saved state settings?

Should I expect to see something where you set the columns for your store? or does the state get applied at a later point? I cant seem to see where it is applied?

Thanks,
Damian

jsakalos
14 Sep 2008, 12:04 AM
I was talking about ThemeCombo only value of which is restored in initComponent:



this.setValue(Ext.state.Manager.get(this.themeVar) || 'xtheme-default.css');

timb
17 Sep 2008, 11:02 AM
@saki,

Thanks for the info. My main problem was that I did not know how to reset the grid. I searched the documentation and didn't find much. I searched the forum and found the following from mystix: http://extjs.com/forum/showthread.php?p=194240#post194240. This post shows how to use the setConfig method of the ColumnModel. Just in case someone wants to do the same thing, here is the code I used. Please note that "this" refers to an extended GridPanel. The following is called while I'm initializing my GridPanel:


...
//Create a new HttpProvider
var stateProvider = new Ext.ux.HttpProvider({
saveUrl: 'myUrl.ashx',
autoRead: false,
session: 'mySessionName'
});

//We must add the listener after creating the new provider. Adding listeners in the initial
//config will not work.
stateProvider.on('statechange', function(stateProvider, name, value) {
//This code will be used to reset the state. There will be no value when the state is reset. If there is a value, exit the method.
if (value) {
return;
{

var cm = this.getColumnModel(); //get the current column model
var curColConfig = cm.config; //get the current column model config

var curSortInfo = this.getStore().sortInfo; //get the current sort info from the store
var defaultSortInfo = this.defaultSortInfo; //get the previously saved defaultSortInfo

Ext.apply(curColConfig, this.defaultColConfig); //overwrite the current column config with the previously saved defaultColConfig

cm.setConfig(curColConfig); //call setConfig to apply the changes and refresh the display

if (curSortInfo.field !== defaultSortInfo.field || curSortInfo.direction !== defaultSortInfo.direction) { //only apply the sort if it has changed
this.getStore().sort(defaultSortInfo.field, defaultSortInfo.direction); //apply the new sort using the previously saved defaultSortInfo
}
}, this);

...

var sortInfo = {field: 'Name', direction: 'ASC'}; //set up the initial/default sort
this.defaultSortInfo = Ext.ux.clone(sortInfo); //save the default sort for later using clone to ensure that the sort info isn't changed
var store = new Ext.data.Store({
sortInfo: sortInfo, //apply the sort to the store
....
)};

...

//set up the initial/default column config
var defaultColConfig = [
{ 'header': 'Name', 'dataIndex': 'Name', 'width': 100, 'hidden': false},
...
];

this.defaultColConfig = Ext.ux.clone(defaultColConfig); //save the default column config for later using clone to ensure that the sort info isn't changed

var columns = new Ext.grid.ColumnModel(defaultColConfig); //create the initial column model
...


I then set up a new menu item to reset the state and refresh the display. Here is the function I'm calling when the menu button is clicked:

resetState: function() {
var stateProvider = Ext.state.Manager.getProvider(); //get the state provider
if (stateProvider.get('myGridName')) { //we only want to clear the state if had previously been changed
stateProvider.clear('myGridName'); //clear the local and database state. If this is successful, the "statechange" event declared above will be called
}
}

I was hoping I could use the initialConfig.cm.config to get the initial column config, but this is always the same as the colModel.config (this seems like a bug, maybe because the initial column config was not cloned?).

As you can see, I am using Ext.ux.clone, which is also from saki (thanks again!). This is included with his http state provider (it can also be found here: http://extjs.com/forum/showthread.php?t=26644). If I didn't use this, then the defaultColConfig would always be equal to the current column config (the same as when using initialConfig, as mentioned above).

Hopefully this is the "correct" way to reset the grid when clearing the state. If anyone knows of a better way to do this, please let me know!

Thanks,
Tim

jsakalos
17 Sep 2008, 12:57 PM
I do not see anything better. If you want to reset something you need to have values to reset to. So you need to keep them somewhere.

I would do it the same way.

mepfuso
22 Sep 2008, 7:27 AM
Finally got to implement your sample code mepfuso (thanks :)) and it's working perfectly storing data as a session variable (I still need to write it to the db but that's no prob)

I am however having trouble applying the state client side???? Could I ask how you (or timb) did it?


In Ext and this HttpStateProvider, applying the state to the client is done with the provider's function initState(...), i.e. in hardcoded JavaScript, i.e. without any AJAX, i.e. before the page is displayed. You would load the state from your database and render it directly into your .aspx page client script.

The ASP.NET framework provides methods like RegisterClientScriptBlock(...). But unfortunately, these won't give you much control over where in the page the script is going to be rendered.

Thus, let's define a Literal control to work as a placeholder in your .aspx mark-up which should be somewhere between the script includes for the Ext library and where you call Ext.onReady(...):




<html>
<head>
<script src="myExt/adapter/ext/ext-base.js" type="text/javascript"></script>
<script src="myExt/ext-all.js" type="text/javascript"></script>
<script src="myExt/ux/state/HttpProvider.js" type="text/javascript"></script>
<asp:Literal ID="litExtInitScript" runat="server" Mode="PassThrough" />
[...]
</head>



In the code behind to your .aspx page, you would go something like:




protected void Page_Load(object sender, EventArgs e)
{
List<KeyValuePair<string, object>> myState = MyDataLayer.GetData("SELECT Key, Value FROM MyExtStateTable WHERE User = {currentUser}");

JsonArray arr = new JsonArray();

foreach (KeyValuePair<string, object> pair in myState)
{
JsonObject obj = new JsonObject();
obj["name"] = pair.Key;
obj["value"] = pair.Value;

arr.Add(obj);
}

this.litExtInitScript.Text = "<script type=\"text/javascript\">\r\n"
+ "//<![CDATA[\r\n"
+ "Ext.state.Manager.setProvider(new Ext.ux.HttpProvider({saveUrl: \"Handlers/ExtStateHandler.ashx\"}));\r\n"
+ "Ext.state.Manager.getProvider().initState(" + arr.ToString() + ");\r\n"
+ "//]]>\r\n"
+ "</script>";
}




This is untested, but I hope it comes across.

DamianHartin
23 Sep 2008, 4:07 AM
Thanks mepfuso - pure gold =D>

I had been working around that sort of logic but seemed to be making it harder for myself than I needed (as I can now see)

This truly is a great product - equaled only by the help of everyone here! so thanks again everyone

Cheers,
Damian

gfernandez
22 Oct 2008, 10:40 AM
hi
I do not understand what is the purpose of the readState() ajax function considering that the state is initialized with JS initialState().

thanks,
gfernandez

jsakalos
22 Oct 2008, 12:44 PM
Do you mean initState?

It's optional. You can either send initial state on the page load in an array that you send to initState (better and faster) or you can query the server on each state request. That is done by readState.

gfernandez
23 Oct 2008, 5:01 AM
Thanks Saki
Yes, sorry, I meant InitState
So, suppose I choose to the send de init state on the page load, should I initialize the provider with the config option autoRead:false?

jsakalos
23 Oct 2008, 5:02 PM
Yes. Here's PHP I've used in the case of http://cellactions.extjs.eu/


<?
// vim: ts=4:sw=4:nu:fdc=4

$user = isset($_COOKIE["user"]) ? $_COOKIE["user"] : (string) rand();
setcookie("user", $user, time() + 365 * 24 * 3600);

// get posted values
$clientArgs->id = isset($_POST["id"]) ? $_POST["id"] : 1;
$clientArgs->user = isset($_POST["user"]) ? $_POST["user"] : $user;
$clientArgs->session = isset($_POST["session"]) ? $_POST["session"] : "session";

// get variables and connection to sqlite
$stateFile = "state.sqlite";
$DSN="sqlite:" . realpath(".") . "/$stateFile";
$odb = new PDO("$DSN");

$sql =
"select name,value from state where "
."id={$clientArgs->id} and user='{$clientArgs->user}' and session='{$clientArgs->session}'"
;
$ostmt = $odb->query($sql);
$state = $ostmt->fetchAll(PDO::FETCH_OBJ);
$state = json_encode($state);

?>
<script type="text/javascript">
Ext.state.Manager.setProvider(new Ext.ux.HttpProvider({
url:'state-sqlite.php?#state'
,user:'<?=$clientArgs->user?>'
,session:'<?=$clientArgs->session?>'
,id:'<?=$clientArgs->id?>'
,readBaseParams:{cmd:'readState'}
,saveBaseParams:{cmd:'saveState'}
,autoRead:false
// ,logFailure:true
// ,logSuccess:true
}));
Ext.state.Manager.getProvider().initState(<?=$state;?>);
</script>
<?
// eof
?>

gfernandez
24 Oct 2008, 5:22 AM
Thanks saki
I tried it and work like a charm.
What I noticed is that the state changes are being propagated even with controls with "stateful=false" but seem to be a problem of the class Ext.Component. Have you ever seen this before?

jsakalos
24 Oct 2008, 3:21 PM
No, when I turned stateful off the components didn't save/restore it.

feiichi
3 Nov 2008, 8:09 AM
Hello,

thank you Jozef for this very useful extension. Maybe I am wrong but I found a few inaccuracies in onReadSuccess:

First, you try to double decode the response json. You decode the response.responseText and if everything's ok, you decode the "data" part of the response again, which fails:


try {data = Ext.decode(o[this.paramNames.data]);}
catch(ex) {
if(true === this.logFailure) {
this.log(this.dataErrorText, o, response);
}
return;
}

The "o" object already contains a decoded this.paramNames.data => "data" part.

I commented this try-catch sequence and replaced it with:


data = o[this.paramNames.data];

and it works fine.

Second, you never fire the "readfailure" event when something is wrong in the onReadSuccess. I see the method onReadFailure so perhaps it only needs some refactoring :).

Anyway, thanks again for this extension!

jsakalos
4 Nov 2008, 1:00 AM
1) I'm not quite sure if I get it fully. I use this provider in an application for 100+ clients connect to and I've never been indicated any error.
2) Take a look at http://rowactions.extjs.eu/source.php?file=js/Ext.ux.HttpProvider.js line 396

feiichi
5 Nov 2008, 12:09 AM
1) I'm not quite sure if I get it fully. I use this provider in an application for 100+ clients connect to and I've never been indicated any error.
2) Take a look at http://rowactions.extjs.eu/source.php?file=js/Ext.ux.HttpProvider.js line 396

Try this code:


<script type="text/javascript">
Ext.state.Manager.setProvider(new Ext.ux.HttpProvider({
url:'state-sqlite.php?#state'
,user:'189049645'
,session:'session'
,id:'1'
,readBaseParams:{cmd:'readState'}
,saveBaseParams:{cmd:'saveState'}
,autoRead:true
}));
Ext.state.Manager.getProvider().initState([]);
</script>

After initializing the state provider it auto-reads the state. My server response looks like this:

{"success":true,"data":[{"name":"group-field","value":"s%3Adue_date"}]}

The extension tries to read that automatically fetched response and throws an error "SyntaxError: missing ] after element list" on line 362:

try {data = Ext.decode(o[this.paramNames.data]);}

which is understandable since the o['data'] already contains decoded data.

jsakalos
5 Nov 2008, 2:09 AM
Yes, you're right. Anyway, I'd recommend against using autoRead as it just adds another server round trip. The recommended method is to load the full state on the first page load and call initState. That is the way I'm doing it and therefore that branch of code has never been executed in my case.

Thank you for reporting the bug.

feiichi
5 Nov 2008, 2:16 AM
Yes, you're right. Anyway, I'd recommend against using autoRead as it just adds another server round trip. The recommended method is to load the full state on the first page load and call initState. That is the way I'm doing it and therefore that branch of code has never been executed in my case.

Thank you for reporting the bug.

Thank you, I follow your recommendation, I was just testing how a state provider actually works :).

Cheers!

bwoody
12 Dec 2008, 9:53 AM
@jsakalos - thanks for sharing this control

I encountered a bug in the onSaveSuccess function on the line highlighted in red:



else {
var i, j, found;
for (i = 0; i < options.queue.length; i++) {
found = false;
for (j = 0; j < this.queue.length; j++) {
if (options.queue[i].name === this.queue[j].name) {
found = true;
break;
}
}
if (true === found && this.encodeValue(options.queue[i].value) === this.encodeValue(this.queue[j].value)) {
delete (this.queue[j]);
}
}
}When you delete an element of an array, the array length is not affected so you essentially end up with a 'hole' in the array. For example:


var myArray = ['hello', 'world'];
delete myArray[0];
myArray[0] <-- this is now undefined
The problem now occurs when we go for the next iteration through the for loops on this line:

if (options.queue[i].name === this.queue[j].name) <-- this.queue[j] is undefined

This is an easy fix:



delete (this.queue[j]); <-- Replace this line

this.queue.remove(this.queue[j]); <-- with this one

jsakalos
12 Dec 2008, 10:02 AM
Thank you very much for finding the bug and providing the patch.

:) :) :)

smudgeface
19 Dec 2008, 2:28 PM
Bug fix:




initState:function(state) {
if(state instanceof Array) {
Ext.each(state, function(item) {
this.state[item[this.paramNames.name]] = this.decodeValue(item[this.paramNames.value]);
}, this);
}
else {
this.state = state ? state : {};
}



Excellent work, by the way. I was about 25% of the way into my own implementation before I realized that buffering would be the best way to do this. At that point I decided to see if it had already been done and I was in luck. You saved me a days work!
THANKS

smudgeface
22 Dec 2008, 3:42 PM
I do have one question...why are you still using this.encodeValue? What does it do differently/better then just using Ext.encode/Ext.decode?

jsakalos
23 Dec 2008, 7:04 AM
The best answer you can get is to read the code of Provider::encodeValue and ext/util/JSON::encode. They are entirely different functions.

jsakalos
23 Jan 2009, 2:02 PM
Bug fix:




initState:function(state) {
if(state instanceof Array) {
Ext.each(state, function(item) {
this.state[item[this.paramNames.name]] = this.decodeValue(item[this.paramNames.value]);
}, this);
}
else {
this.state = state ? state : {};
}



Excellent work, by the way. I was about 25% of the way into my own implementation before I realized that buffering would be the best way to do this. At that point I decided to see if it had already been done and I was in luck. You saved me a days work!
THANKS
Thanks, otherwise it doesn't honor paramNames.value...

:) :) :)

smudgeface
26 Jan 2009, 3:53 PM
Saki,

Take a look in onSaveSuccess() and onSaveFailure(). Both contain the same block of code



if(this.started) {
this.start();
}


What this basically does is queue another request in 'this.delay' milliseconds. The problem here is that, if there is a communication error with the server, httpprovider enters an endless loop of retries. Maybe this is what you intended, but do you not think it makes more sense to only queue submitState when a queueChange occurs?

I tried this by commenting out this.start() in the if(this.started){} blocks and adding this.start() to the end of queueChange. Works as expected - failed submits do not retry until the user performs an action that requires another state save.

jsakalos
26 Jan 2009, 4:22 PM
Sounds good. The original idea was that you can programmatically stop the provider so the code at the end of queueChange should be:


if(this.started) {
this.start();
}


Also, then you could comment the start also in onSaveSuccess, right? Could you please test it taking in account these notes?

smudgeface
26 Jan 2009, 4:46 PM
Exactly! I commented out this.start() in both onSaveSuccess and onSaveFailure. Then, to make sure I tested fully, I caused both types of failure events: communication failure by renaming the file pointed to in URL (causing onSaveFailure), and application failure by returning o.success = false.

In both cases, httpProvider attempted to reach the server only once but retried on the next state change. I also noticed that my web app was more responsive as there was less AJAX activity.

here is the updated code (comments and headers removed ;) )


,queueChange:function(name, value) {
var changed = undefined === this.state[name] || this.state[name] !== value;
var o = {};
var i;
var found = false;
if(changed) {
o[this.paramNames.name] = name;
o[this.paramNames.value] = this.encodeValue(value);
for(i = 0; i < this.queue.length; i++) {
if(this.queue[i].name === o.name) {
this.queue[i] = o;
found = true;
}
}
if(false === found) {
this.queue.push(o);
}
this.dirty = true;
}
if(this.started) {
this.start();
}
return changed;
}

,onSaveSuccess:function(response, options) {
var o = {};
try {o = Ext.decode(response.responseText);}
catch(e) {
if(true === this.logFailure) {
this.log(this.saveFailureText, e, response);
}
this.dirty = true;
return;
}
if(true !== o.success) {
if(true === this.logFailure) {
this.log(this.saveFailureText, o, response);
}
this.dirty = true;
}
else {
Ext.each(options.queue, function(item) {
var name = item[this.paramNames.name];
var value = this.decodeValue(item[this.paramNames.value]);

if(undefined === value || null === value) {
Ext.ux.HttpProvider.superclass.clear.call(this, name);
}
else {
// parent sets value and fires event
Ext.ux.HttpProvider.superclass.set.call(this, name, value);
}
}, this);
if(false === this.dirty) {
this.queue = [];
}
else {
var i, j, found;
for(i = 0; i < options.queue.length; i++) {
found = false;
for(j = 0; j < this.queue.length; j++) {
if(options.queue[i].name === this.queue[j].name) {
found = true;
break;
}
}
if(true === found && this.encodeValue(options.queue[i].value) === this.encodeValue(this.queue[j].value)) {
this.queue.remove(this.queue[j]);
}
}
}
if(true === this.logSuccess) {
this.log(this.saveSuccessText, o, response);
}
this.fireEvent('savesuccess', this);
}
}

,onSaveFailure:function(response, options) {
if(true === this.logFailure) {
this.log(this.saveFailureText, response);
}
this.dirty = true;
this.fireEvent('savefailure', this);
}

jsakalos
26 Jan 2009, 5:50 PM
Thanks, I'll let my users to test the new version for a couple of days and if nothing happens I'll update the code in the first post.

smudgeface
27 Jan 2009, 9:15 AM
Just one thing, I noticed that I left some modifications that are only relevant to my code. I have edited my previous post to correct the errors. Please make sure that you use the new corrected code in case you missed it.

jsakalos
27 Jan 2009, 9:26 AM
Anway, I haven't copied&pasted your code, I never do that. I have written patches manually. Thanks for caring about that.... :)

calavera
31 Jan 2009, 9:37 PM
Hello. Nice work. I am trying to grab the state from the database like this:


$sql =
"select name,value from state where "
."id={$clientArgs->id} and user='{$clientArgs->user}' and session='{$clientArgs->session}'"
;

$ostmt = $odb->query($sql);
$state = $ostmt->fetchAll(PDO::FETCH_OBJ);
$state = json_encode($state);

And then :
Ext.state.Manager.getProvider().initState(<?=$state;?>);

The problem is that $state is empty. Where could be the problem ? If I copy the query and run it in my sqlite manager, it would bring up the data correctly. And if I paste the data directly into the initState line, the columns are updated correctly...So the problem is about getting the data from the database...What am I doing wrong ??

Thanks

jsakalos
1 Feb 2009, 6:03 AM
It's very difficult, if not impossible, to say anything about server side. I would make a PHP test page with echo xxx at various points if the code to see what's PHP doing.

calavera
1 Feb 2009, 3:40 PM
Ok, I have it figured out. I had a wrong path to the .sqlite file. It's working great now!

I wonder if I can add an on.load kind of function so that I can see a loading message when I change the width of a column for example. What do you think ?

jsakalos
1 Feb 2009, 6:21 PM
While saving state? I wouldn't do it, it would only distract users - they wouldn't know why something is saving if they "didn't click a save button".

calavera
1 Feb 2009, 6:27 PM
I understand, but my users know. And I wouldn't want them to resize a second column before the first one has finished the query. Can you help me to achieve this ?

Thank you for your time.

jsakalos
1 Feb 2009, 6:53 PM
HttpProvider is buffering what means that is doesn't immediately send state. There is delay (interval) that defaults to 750 ms so if you resize a column it is quite likely that you can resize also second one within that time frame.

If you still insist on "a save indicator" then you're on your own - I have no time for that functionality and I don't think it is a good idea.

calavera
2 Feb 2009, 12:06 AM
I understand. Thank you anyway, it's a great extension!

calavera
2 Feb 2009, 8:21 PM
Can I please bother you with another question ? Do you have any idea why it worked perfect on my localhost but once moved to online webserver it keeps throwing this PHP error ?


Warning: Invalid argument supplied for foreach() in /home/*my path*/provider-sqlite.php on line 83 ?
BTW: My provider-sqlite.php is the same as you state-sqlite.php from rowactions plugin. I just got the names mixed up

Where line 83 is from saveState:
foreach($clientArgs->data as $row) {
I have permissions to the db file and folder. I've runned some tests and $_POST["data"] is not empty at the begining of the file. $clientArgs->data returns an empty array though...

Any ideas ? Thank you

smudgeface
3 Feb 2009, 12:20 AM
your best bet here is to install firebug and then install the firephp add on. All you have to do then is "include_once" the firephp file and you can send variables back to your firebug console by calling fb($someVar);
This allows for very convenient variable inspection at run time and will allow you to really see what is going on. I suspect that there is a PHP configuration difference between your localhost and your hosted server. Are they running the same PHP version?

make a file on localhost and on your webserver called getInfo.php, and set its contents to
<?php
phpinfo();
?>

then go to http://some path/getInfo.php to get all the current php config on both systems.

jsakalos
3 Feb 2009, 1:04 AM
I also think that it is PHP config difference. Check php.ini.

calavera
3 Feb 2009, 8:36 AM
Ok, I got it working on my webserver as well. The problem was, as you too were saying in my PHP configurations. My webserver was causing a NULL json_decode because of the magic_quotes setting in PHP.ini. So I just needed to make a stripslashes() over the data. I have made a function to handle different PHP configuration:


function my_json_decode($data) {
if (get_magic_quotes_gpc()) {
$data = stripslashes($data);
}
return json_decode($data);
}

I hope this one help others who encounter the same problem. Thank you for your help.

jsakalos
3 Feb 2009, 11:15 AM
Yes, that is solution if you cannot turn magic quotes off. I have my own servers so I simply change php.ini.

Anyway, congrats that you've done it !!! :)

calavera
3 Feb 2009, 3:18 PM
Yup, shared server over here :( .

thijsvanmenen
9 Feb 2009, 12:02 AM
Currently I use Ext.state.SessionProvider (from the Ext examples) but I want to switch to this implementation.

But is there a way to combine a CookieProvider with this one? I only know the logged in user from the cookie.

So I want to store the logged in user in the cookie, and the rest of the state in the database.

TIA.

jsakalos
9 Feb 2009, 12:05 AM
Not really. There can be only one provider set in state manager. You need to access cookies some another way.

BTW, why do you want to switch from SessionProvider?

thijsvanmenen
9 Feb 2009, 12:08 AM
BTW, why do you want to switch from SessionProvider?

Because I am running into problems with cookie length.

smudgeface
9 Feb 2009, 8:38 AM
why not just manually set a cookie

jsakalos
9 Feb 2009, 8:39 AM
Yes, that is what I've meant.

cebola
24 Feb 2009, 6:03 AM
@DamianHartin,

take a look at some of my examples or main page http://extjs.eu. There you can see working stateful ThemeCombo.

This example just save state.
But not restore state from server.
How to do this?

If i Use the Ext.state.CookieProvider in my app, the grid render with the last colum possition.
Its possible using Ext.ux.HttpProvider ???

jsakalos
24 Feb 2009, 6:56 AM
No, CookieProvider is different provider. This page uses HttpProvider; just study how it is done there: http://cellactions.extjs.eu/

cebola
24 Feb 2009, 12:30 PM
No, CookieProvider is different provider. This page uses HttpProvider; just study how it is done there: http://cellactions.extjs.eu/

Tank you Saki.
But.. I create other class extend from CookieProvider with base in httpProvider



Ext.ux.HttpDBProvider = function(config) {
Ext.ux.HttpDBProvider.superclass.constructor.call(this,config);
this.path = "/";
this.expires = new Date(new Date().getTime()+(1000*60*60*24*7)); //7 days
this.domain = null;
this.secure = false
this.state={};
Ext.apply(this, config);
}

Ext.extend(Ext.ux.HttpDBProvider, Ext.state.CookieProvider, {
setCookie : function(name, value){
var o = {
url:this.saveUrl || this.url
,method:this.method
,scope:this
,params:{}
};
var params = Ext.apply({}, this.saveBaseParams);
params[this.paramNames.id] = this.id;
params[this.paramNames.session] = this.session;
params[this.paramNames.user] = this.user;
params[this.paramNames.name] = name;
params[this.paramNames.data] = this.encodeValue(value);
Ext.apply(o.params, params);
Ext.Ajax.request(o);
},
readCookies : function(){
var cookies = {};
var o = {url:this.readUrl||this.url,method:this.method ,scope:this,params:{}
,success:function(response, options){
var data = eval(response.responseText.split("\n")[0]);
Ext.each(data, function(item) {
var tmp;
try {
tmp=this.decodeValue(item[this.paramNames.value]);
} catch(e){ dump(e);realAlert(e);return }
this.state[item[this.paramNames.name]] = tmp;
}, this);
}
};
var params = Ext.apply({}, this.readBaseParams);
params[this.paramNames.id] = this.id;
params[this.paramNames.session] = this.session;
params[this.paramNames.user] = this.user;
Ext.apply(o.params, params);
Ext.Ajax.request(o);
return
},
clearCookie : function(name){
var o = {url:this.saveUrl||this.url,method:this.method,scope:this,params:{}};
var params = Ext.apply({}, this.cleanBaseParams);
params[this.paramNames.id] = this.id;
params[this.paramNames.session] = this.session;
params[this.paramNames.user] = this.user;
params[this.paramNames.name] = name;
Ext.apply(o.params, params);
Ext.Ajax.request(o);
}
});




this.cp=new Ext.ux.HttpDBProvider({id:"STATEPROVIDER",paramNames:{id:'CODIGO',name:'nome',value:'valor',user:'usuario',session:'SESSAO',data:'dados'},session:"",method:'POST',cleanBaseParams:{cmd:'cleanState'},readBaseParams:{cmd:'readState'},saveBaseParams:{cmd:'saveState'},readUrl:"",saveUrl:"",user:99,url:'jsonsql.php'});
Ext.state.Manager.setProvider(this.cp);


on ready page I create the state object with readCookies;


this.cp.readCookies();


It's solve my problems.

jsakalos
24 Feb 2009, 12:43 PM
Sorry, in that case you are on your own.

cebola
25 Feb 2009, 4:23 AM
ok Saki.. No problems..
only for shared....

KH9l3b
26 Feb 2009, 4:45 AM
Hi, I didnt understand what to do to restore elements state :(
I wrote something like that:


Ext.state.Manager.setProvider(new Ext.ux.HttpProvider({
url:'state.htm',
user:'user',
session:'session',
id:'id',
readBaseParams:{cmd:'readState'},
saveBaseParams:{cmd:'saveState'},
autoRead:true,
logFailure:true,
logSuccess:true
}));
Ext.onReady(function(){
var w = new Ext.Window({height:100, width:200, stateful:true, stateId:"testWindow1"});
var window = new Ext.Window({height:100, width:100, stateful:true, stateId:"testWindow2"});
window.show();
w.show();
});

I wrote methods for saving states to DB and getting them back.
Then I moved and changed the size of those windows and so the changes in my DB (so saving is working...)
Then I refreshed the page and with "autoRead:true" I received the response

{"success":true,"data":[{"accountId":1,"name":"testWindow1","value":"o%3Awidth%3Dn%253A200%5Eheight%3Dn

%253A115%5Ex%3Dn%253A229%5Ey%3Dn%253A121"},{"accountId":1,"name":"testWindow2","value":"o%3Awidth%3Dn

%253A254%5Eheight%3Dn%253A241%5Ex%3Dn%253A590%5Ey%3Dn%253A224"}]}

So, I think, the response is also correct...
But windows didn't take the correct shape and position! :(
May be I missed something?

jsakalos
26 Feb 2009, 9:12 AM
The question is if the state is back from the server when you create windows. The most preferred method is to load the initial state at the first page load (autoRead:false). See how is it done here: http://cellactions.extjs.eu

cyfl
2 Mar 2009, 7:23 AM
Hello !

I have a question : How to save other information about windows like Maximize, Minimize, show, hide, ...
?

Thanks !

jsakalos
2 Mar 2009, 7:26 AM
You have to provide your own getState and/or applyState methods.

cyfl
2 Mar 2009, 7:35 AM
You have to provide your own getState and/or applyState methods.

how I can do that ?
:D

jsakalos
2 Mar 2009, 8:36 AM
Take a look at http://examples.extjs.eu, read the docs, tutorials, forum posts. There is a lot about state saving/restoring there. Anything I'd write here has already been written.

jurban
5 Mar 2009, 9:38 AM
For what some reason I simply can't get this provider to work.
When I used the cookieprovider the grid saved and restored its state just fine:

Ext.onReady(function()
{
var gStateProvider = new Ext.state.CookieProvider();
Ext.state.Manager.setProvider(gStateProvider);
...
I then changed this code to:

Ext.onReady(function()
{
var gStateProvider = new Ext.ux.HttpProvider({
readUrl:'/myapp/extUtilityJson?action=readAllocGridState',
saveUrl:'/myapp/extUtilityJson?action=saveAllocGridState',
user:gUserId // Global variable set by the .jsp
});
Ext.state.Manager.setProvider(gStateProvider);
...
My servlet is returning the following:

{"success":true,"data":[{"name":"AllocGrid","value":"o%3Acolumns%3Da%253Ao%25253Aid%25253Ds%2525253Anumberer%25255Ewidth%25253Dn%2525253A23%255Eo%25253Aid%25253Ds%2525253Aalloc_sts%25255Ewidth%25253Dn%2525253A75%255Eo%25253Aid%25253Ds%2525253Aalloc_qty%25255Ewidth%25253Dn%2525253A80%255Eo%25253Aid%25253Ds%2525253Afirm%25255Ewidth%25253Dn%2525253A50%255Eo%25253Aid%25253Ds%2525253Af%25255Ewidth%25253Dn%2525253A30%255Eo%25253Aid%25253Ds%2525253Aoff%25255Ewidth%25253Dn%2525253A50%255Eo%25253Aid%25253Ds%2525253Aacct%25255Ewidth%25253Dn%2525253A80%255Eo%25253Aid%25253Ds%2525253Aunbal%25255Ewidth%25253Dn%2525253A65%25255Ehidden%25253Db%2525253A1%255Eo%25253Aid%25253Ds%2525253Aref_text%25255Ewidth%25253Dn%2525253A100%25255Ehidden%25253Db%2525253A1%255Eo%25253Aid%25253Ds%2525253Ao_c%25255Ewidth%25253Dn%2525253A40%25255Ehidden%25253Db%2525253A1%255Eo%25253Aid%25253Ds%2525253Apriority%25255Ewidth%25253Dn%2525253A80%25255Ehidden%25253Db%2525253A1%255Eo%25253Aid%25253Ds%2525253Aowner_ref%25255Ewidth%25253Dn%2525253A100%25255Ehidden%25253Db%2525253A1%255Eo%25253Aid%25253Ds%2525253Aclearing_id_col%25255Ewidth%25253Dn%2525253A1000%25255Ehidden%25253Db%2525253A1"}]}
I get no errors but unlike when I use the CookieProvider my grid does not get restored to the state it was in when the user last viewed the page. What am I doing wrong?

jsakalos
5 Mar 2009, 9:45 AM
Is the state available at the time when grid needs it?

It's already been discussed, briefly, init the provider on initial page load.

jurban
5 Mar 2009, 10:40 AM
Is the state available at the time when grid needs it?

It's already been discussed, briefly, init the provider on initial page load.
Ok, that fixed it. I thought you couldn't set the provider until the onReady was called. My bad.

But now I have a new problem. Once I make any change to the grid that triggers a state change save instead of the state change being sent to the server once the state change command is being sent to the server over and over until I leave the page. Its like the save state change is caught in an infinite loop. Do I need to set some switch or something when I instantiate the provider?

calavera
5 Mar 2009, 11:01 AM
Ok, that fixed it. I thought you couldn't set the provider until the onReady was called. My bad.

But now I have a new problem. Once I make any change to the grid that triggers a state change save instead of the state change being sent to the server once the state change command is being sent to the server over and over until I leave the page. Its like the save state change is caught in an infinite loop. Do I need to set some switch or something when I instantiate the provider?

Check the response(s) of the request(s). They might throw an sqlite error regarding database config.

jsakalos
5 Mar 2009, 11:34 AM
Yes, if failure returns from the server the provider tries again.

jurban
5 Mar 2009, 11:37 AM
Check the response(s) of the request(s). They might throw an sqlite error regarding database config.
Thanks, that was it.

jurban
5 Mar 2009, 12:46 PM
Ok, I have this working and it real cool, maybe too cool.

Above my grid I have 3 sections which are optional to the user. I have these set up as xtype:'fieldset' and collapsible:true (you can see where this is going, right?). Having seen how cool the it is when the grid saves and restores it state my boss now wants the collapse/expand state of the fieldsets to be saved and restored. So I configured the fieldsets with stateId and stateful values. However the fieldsets did not save the their collapse/epand states. What do I need to do to enable the saveing/restoring of the fieldset state?

jsakalos
5 Mar 2009, 1:03 PM
Take a look at: http://examples.extjs.eu/?ex=accstate

jurban
5 Mar 2009, 1:31 PM
Take a look at: http://examples.extjs.eu/?ex=accstate
Ok, so I configured my fieldsets as follows:

stateId: 'FieldSet1',
stateful:true,
stateEvents: ["collapse", "expand"],
getState:function() {
return {collapsed:this.collapsed};
},

This did invoke the state save but it didn't send up the correct stateId. Even though I did not change the gird the state save sent the server the stateId of the grid but with the value of the fieldset. As a result the grid state is trashed and he fieldset state is simply not saved.

jsakalos
5 Mar 2009, 1:33 PM
The code looks good. The problem must be elsewhere. Non-unique stateIds? Or something else.

jurban
5 Mar 2009, 2:33 PM
The code looks good. The problem must be elsewhere. Non-unique stateIds? Or something else.
Strange, I restarted the app server and the problem went away. Something must have gotten cached. Anyhow it is working just fine. Thanks!

VATigers
31 Mar 2009, 11:24 AM
I have a similar requirement of storing the state on to a database. This threads looks like a possible soultion.

There are many patches applied to the code from page 1 of this thread to the last page.
I was wondering as to what is the fully functional code inclusive of the patches.

Is it possible to have the fully functional code in a file or post ?
It would be really helpful for me and for many others.

jsakalos
31 Mar 2009, 11:50 AM
I've updated the code in the first post to the latest version. See also http://extjs.eu/docs/?class=Ext.ux.state.HttpProvider

VATigers
31 Mar 2009, 11:54 AM
Awesome dude!!
You are so helpful as always=D>

Thanks a lot.

micah.d.lamb
2 Apr 2009, 9:38 AM
Would there be an easy way to modify this to just send the state updates on page unload? I don't see the point of sending updates before leaving the page? Otherwise this is working perfect for me. thanks.

jsakalos
2 Apr 2009, 9:46 AM
Unload event doesn't work cross-browser.

smudgeface
2 Apr 2009, 9:48 AM
The other problem with doing that is you could have a nearly limitless list of changes that have occurred since the session began. And the entire session state save hinges on a single, bulky update. An incremental approach is much more reliable and makes more sense in this case.

micah.d.lamb
2 Apr 2009, 12:40 PM
Sounds reasonable enough. In either case this method of saving state is gonna be a lot more efficient than sending several kilobytes of worthless cookies on each request. There is no way to prevent the browser from sending cookies right?

jsakalos
2 Apr 2009, 12:58 PM
AFAIK, there is none, except delete cookies. But that is not what we want. Further, http server have limit on cookies size - I've already run into this issue.

rtikku
4 Apr 2009, 12:39 AM
Hi Saki,

Thanks for this really useful extention.

I have implemented it and am able to save the state. For restoring the state, I understand from your post #6 that I need to call initState to restore the state when the page is initially loaded. If that is how we restore the state, what is the significance of readUrl config option.

I looked at your cellActions example, but could not find any refrence to HttpProvider, other than the HttpProvider.js being included in the page.

Thx.

Rakesh

jsakalos
4 Apr 2009, 2:17 AM
Well, it is there, it works, but it is not generally used. The original idea was make it symmetrical; if it has save it should have also read.

feiichi
13 Apr 2009, 3:10 AM
Hello jsakalos,

Thanks for the extension!

I would suggest to add also "readMethod" and "saveMethod" attributes. For example on the Rails RESTful platform, you'd want to use GET for reading and POST/PUT for saving in the same URL.

jsakalos
13 Apr 2009, 3:53 AM
GET size is limited so if you'd save/retrieve state via GET you could easily run into troubles.

feiichi
13 Apr 2009, 9:27 AM
Well, the params size is limited with GET, but I don't see a problem with that as long as you use it just for reading/getting the states :).

Nevertheless I added this little tweak myself so now your extension nicely cooperates with the rails scheme.

Cheers!

Bodom78
14 Apr 2009, 2:44 AM
Just wanted to say a quick thanks Saki. It took me a full day of reading, modifying and re-reading but I was finally able to successfully incorporate this into one of my application.

Keep up the great work :)

fangzhouxing
14 Apr 2009, 4:26 AM
I study this extension for some time, for performance, I turned off all state in my project, and I try to use this extension to restore the state functionality.

My question: In a OneApplicationOnePage app, when the page loaded, the user haved not log in, how to read the state info by user?

jsakalos
14 Apr 2009, 7:48 PM
I personally use browser login dialog together with Apache digest authentication so when my page loads first time the user is already logged in.

For another setups (Ext login dialogs) you need at least two pages: login page and main page and you only load the main page when user successfully logs in.

VATigers
15 Apr 2009, 6:59 PM
First of all let me thank 'Saki' for this wonderful state provider.
Thanks to Mepfuso and timb for providing directions to use this state provider with asp.net.
It works for me.

I have a question. Instead of sending the state every 750 ms, I want to save the state when the user navigates from the page.
Is this possible ?

Apologize if this is already answered.

jsakalos
16 Apr 2009, 6:24 AM
Unfortunately, there is no reliable cross-browser way to do it. There is onbeforeunload event but it works (somehow) only in Firefox.

chrizmaster
18 Apr 2009, 1:58 AM
hey dude,

i am pretty new to that kind of stuff. i am now need a way, to store the stae of my subapplication (i used the webdesktop and with subapplications i mean the windows). means the size, the position, the state of the components inside the window and so on.

that seems what your extension is used to be. but... is there anywhere a documentation to read more about that? how should the server side be designed (database) what extactly do i have to do in my client components.. stuff like that.

hope not to annoy you to much ;)
chriz

fangzhouxing
18 Apr 2009, 6:16 AM
I created the server side database table to store data send by HttpProvider like this:


CREATE TABLE "t_MyState" (
"id" integer NOT NULL PRIMARY KEY,
"user_id" integer NOT NULL,
"name" varchar(100) NOT NULL,
"value" varchar(255) NOT NULL
)


and this is the Django(Python) code to store the data of state:


@require_login
def save_my_state(request):
if 'data' in request.POST:
states = demjson.decode(request.POST['data'])
for state in states:
name = state['name']
value = state['value']
user_id = request.user.id
MyState.objects.filter(user_id=user_id,name=name).delete()
MyState.objects.create(user_id=user_id,name=name,value=value)

return ajax_ok()

jsakalos
18 Apr 2009, 3:13 PM
@chrizmaster, this site contains all information including the server side backend: http://cellactions.extjs.eu/

chrizmaster
20 Apr 2009, 12:24 AM
Hi,

some questions.

you init the provider with that:



Ext.state.Manager.getProvider().initState([{"name":"cawin","value":"o%3Awidth%3Dn%253A783%5Eheight%3Dn%253A443%5Ex%3Dn%253A779%5Ey%3Dn%253A175"},{"name":"theme","value":"s%3Axtheme-default.css"}]);


I expected that I have to set an option to my window something like useStateProvider:'Ext.ux.HTTP..." but I don't. so how does the window know, that it has to call the your provider if the window is resized or moved?

And if I want to restore the state of more than one window, do i have to call the init function for every window?

regards
christian

jsakalos
20 Apr 2009, 9:03 AM
The above line should contain full saved state for all objects so it can be executed only once at the initial page load.

Components that have non-automatic ids or that have stateIds are stateful by default so the Ext state infrastructure will call all necessary methods to keep state.

Take a look at state examples at http://examples.extjs.eu on how to make some components stateful.

VATigers
22 Apr 2009, 10:20 AM
I found that the page makes a http request to save the state immediately after the page is rendered. This sends null state to the server. Am I the only one who is facing the issue ?
Also is there a way to save the state on particular event. Not every 750 ms. The application, I am working on is very rigid on Network latency and we canot afford making a server request every 750 ms for all users.Any help on this would be really appreciated.

calavera
22 Apr 2009, 10:57 AM
I found that the page makes a http request to save the state immediately after the page is rendered. This sends null state to the server. Am I the only one who is facing the issue ?
Also is there a way to save the state on particular event. Not every 750 ms. The application, I am working on is very rigid on Network latency and we canot afford making a server request every 750 ms for all users.Any help on this would be really appreciated.
Hello VATigers!
1. Mine doesn't make any http requests after the page is rendered.

2. Correct me if I'm wrong, but the request is fired only after a change had been made (reordering results, filtering results, resizing columns, moving columns etc.). And it's only one request per change.

I hope I got this right.

VATigers
22 Apr 2009, 11:35 AM
The code makes a request to the server to read the state.
The autoRead if set to true calls the function 'ReadState' which makes a request to the server to read the state.
You ar right, a request is made only when the state changes. But we do not want to make a server request to save state every time the state changes. But instead save the entire page state manually say when the user navigates away from the page or something similar.

jsakalos
22 Apr 2009, 11:46 AM
1. Use technique from http://cellactions.extjs.eu
2. what about increasing time - should be configurable.

VATigers
22 Apr 2009, 12:21 PM
Thanks Saki,
saki, is your solution to my first problem or the second ?

I am using EXTJs alongwith ASP.NET. I made the 'autoRead' to be false. And am passing the saved state along with the page. This helps in preventing a request to the server to read the state.
I am not able to figure out as to how I would collect the state and send it to the sever manually.

jsakalos
22 Apr 2009, 12:34 PM
1 to 1, 2 to 2 ;)

So 1 is solved as you write - it is the solution I've had in mind.

2. you could try autoStart:false and then to call sumbitState() manually

KH9l3b
24 Apr 2009, 2:07 AM
I have some bug with this provider. The Ext.Window always saves it's state even if it set
stateful: false :( How can I fix it?

chrizmaster
24 Apr 2009, 2:16 AM
show the code...

KH9l3b
24 Apr 2009, 2:57 AM
Ext.state.Manager.setProvider(new Ext.ux.HttpProvider({
url:'state.htm',
user:'user',
session:'session',
id:'id',
readBaseParams:{cmd:'readState'},
saveBaseParams:{cmd:'saveState'},
autoRead:false,
logFailure:true,
logSuccess:true
}));
Ext.state.Manager.getProvider().initState(${model.state});


var window = new Ext.Window({
stateful: false,
id:'WINDOW-INCOMING-ALL',
title:"Documents",
resizable:false,
layout:'border',
items:[panel, {region:'west', items:[documentsGrid, options], split:true, frame: true, width: 465}],
height: 485,
width: 820,
buttons:[viewHistory, deleteButton, makeArchive, markAsRead, send, save, edit]
});

When I move the window, the ajax request is send to server with such info:

cmd:saveState
data:[{"name":"WINDOW-INCOMING-ALL","value":"o%3Awidth%3Dn%253A820%5Eheight%3Dn%253A487%5Ex%3Dn%253A224%5Ey
%3Dn%253A101"}]

jsakalos
24 Apr 2009, 10:21 AM
I have some bug with this provider. The Ext.Window always saves it's state even if it set
stateful: false :( How can I fix it?
Provider has nothing to do with this. State manager and its underlying provider just sit there and wait for a request to save state. Double check if the window is really stateful:false at runtime. Maybe you've discovered an Ext bug... ;)

smudgeface
24 Apr 2009, 10:44 AM
You could just set stateEvents to []. This would serve the same effect as there would be no events in which the component would activate its saveState method.

VATigers
28 Apr 2009, 10:33 AM
Hi Saki,

Coming back to my post #176, I could get the provider to submit state manually. =P~

I was wondering as to how would I submit the state when the user navigates away from the page. Does EXTJs provide some events to catch this ?

One option which comes to mind is to save the state when the document unloads.
something like this



<script type="text/javascript">
var hp = new Ext.ux.state.HttpProvider({
saveUrl: '../StateHandler.ashx'

});
Ext.state.Manager.setProvider(hp);
Ext.state.Manager.getProvider().initState([{
"name": "pnl3",
"value": "o%3Acollapsed%3Db%253A1"
}]);
Ext.onReady(function()
{
Ext.EventManager.on(document, 'unload',
function() {
hp.submitState();
});
});
</script>

jsakalos
28 Apr 2009, 11:19 AM
This question has already been asked (and answered) several times. Unfortunately, the answer is that there is no reliable cross-browser way. If you use Firefox only you could utilize onbeforeunload event.

VATigers
28 Apr 2009, 12:17 PM
hmmm...
Sorry........I did not realize that the cross-browser answer was actually the answer to my question.

Well..our users will only be using IE .

jsakalos
28 Apr 2009, 2:41 PM
Maybe IE8 has some unload event, I don't know, I'm on Linux. However, even if yes, can you guarantee that all users will use IE8?

You know, I develop a commercial application, non-public, and I write in "Minimum System Requirements": Firefox 3.0 or later.

Maybe you can do the same for IE8.

KH9l3b
28 Apr 2009, 11:50 PM
I checked... I added something like alert(window.stateful) and it said "false", but still when I move the window I see the Ajax requests for it's state saving :(

jsakalos
29 Apr 2009, 12:37 AM
Anyway, the provider doesn't save state itself; something must call it to save.

VATigers
29 Apr 2009, 9:56 AM
yes, Ours is an internal application. So the organization does not allow any other browsers except IE. So I can be sure on that.

Will the approach in Post #182 work ? It does work but is it recommended?



Ext.EventManager.on(document, 'unload', function() { hp.submitState(); });

jsakalos
29 Apr 2009, 10:03 AM
Everything that works is recommended... ;)

VATigers
29 Apr 2009, 10:55 AM
\:D/

Well, your HttpProvider serves my purpose. Thanks a lot. It was much appreciated in our team !
Also thanks to mepfuso for the ASP.Net code.

mystix
29 Apr 2009, 4:43 PM
yes, Ours is an internal application. So the organization does not allow any other browsers except IE. So I can be sure on that.

Will the approach in Post #182 work ? It does work but is it recommended?



Ext.EventManager.on(document, 'unload', function() { hp.submitState(); });



better:


Ext.EventManager.on(document, 'unload', hp.submitState);

hp.submitState is a function.
there's no need to add an additional anonymous function wrapper.

VATigers
30 Apr 2009, 5:36 AM
Makes sense mystix. Thanks for pointing it out.

calavera
17 May 2009, 8:58 PM
Can somebody please tell me how can I "custom read" the filters that were applied in a manner in which I can send through $_POST, $_GET or anything the values to a PHP file for generating a HTML table with the filters applied on the PHP query ?

I need to format the query based on the filters. It's for custom printing.

Thank you.

jsakalos
17 May 2009, 11:01 PM
How does it relate to HttpProvider?

calavera
18 May 2009, 6:36 AM
Oups, you're right! Wrong forum. Sorry :">

chrizmaster
19 May 2009, 5:57 AM
I don't get it....:-/

At the moment, I can save the states in my database. The state is saved everytime I change a component. But if I restart my webapp, the state is not restored.

I called this:



Ext.state.Manager.getProvider().initState([{"name":"fileexplorer","values":"o%3Awidth%3Dn%253A840%5Eheight%3Dn%253A580%5Ex%3Dn%253A109%5Ey%3Dn%253A193"},{"name":"tree-panel","values":"o%3Awidth%3Dn%253A117"}]);


after I set the Provider (which is before the components are build.)

what is wrong here?

For example if I move the window to another location and I expect to see this window at this location on restart, but it's still in the "original location".

This also happens if I try to move the window of the rowactions example of saki.

Christian

chrizmaster
19 May 2009, 6:28 AM
sorry, my fault, was a mistake on my site;)

chrizmaster
19 May 2009, 1:52 PM
another question:

my components now saves and restore the states.

I am using an extended version of the web desktop. Now my problem is, that if I close a window and recall it by doubleclicking on a web-desktop icon, the window restores the state it had when i called
Ext.state.Manager.getProvider().initState

and not with the "new state" (e.g. a new position or so)
is there something I have to do in the show listener of the window??

Thanks
Chriz

jsakalos
19 May 2009, 2:07 PM
Nothing extra should be necessary. Source says:


Ext.extend(Ext.Window.DD, Ext.dd.DD, {
moveOnly:true,
headerOffsets:[100, 25],
startDrag : function(){
var w = this.win;
this.proxy = w.ghost();
if(w.constrain !== false){
var so = w.el.shadowOffset;
this.constrainTo(w.container, {right: so, left: so, bottom: so});
}else if(w.constrainHeader !== false){
var s = this.proxy.getSize();
this.constrainTo(w.container, {right: -(s.width-this.headerOffsets[0]), bottom: -(s.height-this.headerOffsets[1])});
}
},
b4Drag : Ext.emptyFn,

onDrag : function(e){
this.alignElWithMouse(this.proxy, e.getPageX(), e.getPageY());
},

endDrag : function(e){
this.win.unghost();
this.win.saveState();
}
});

chrizmaster
19 May 2009, 11:08 PM
could it be a problem, that I create the window new, everytime I open it?

jsakalos
20 May 2009, 12:16 AM
With different id or stateId? If yes, that is the problem. Try to give the window same stateId for each instance.

chrisjcha
28 May 2009, 6:26 AM
Hello,

I'm having some trouble with the stateEvents of my grid panels not being picked up. When I call the following it works great and writes to the server.
Ext.state.Manager.getProvider().set(...)But when I resize/move a column, the set() function is not being called and so nothing gets queued..


Ext.state.Manager.setProvider(new Ext.ux.HttpProvider({
url:'<my cookies url>'
,user:'999'
,readBaseParams:{cmd:'readState'}
,saveBaseParams:{cmd:'saveState'}
,autoRead:false
}));
Ext.state.Manager.getProvider().initState(<my data>);In the grid I have:


stateful: true,
stateEvents: ['columnmove', 'columnresize']When I was using the CookieProvider these events were being picked up.
I have initialized HttpProvider before Ext.onReady. Please help.

Thanks for providing this!

jsakalos
28 May 2009, 10:41 AM
You need to give the grid id or stateId. Also mind that HttpProvider sends requests once per 750ms if a state changes. I do not see any reason why same grid would save state with cookie provider and not with HttpProvider. Any provider is only lowest level that does nothing by itself just provides a layer between state manager and actual state storage.

chrisjcha
29 May 2009, 5:38 AM
I did some more digging and found that HttpProvider did work with some grids and not others. The problem was that my project also uses Ext.grid.SmartCheckBoxSelectionModel.js, which does this:


Ext.state.Manager.setProvider(new Ext.state.CookieProvider());It was in those grids that it wasn't working. Once I commented it out, it was fine.

dbassett74
11 Jun 2009, 3:23 PM
I initiated another post on this before I found this one. My question is, can this be utilized to store such things as the state of a window as modified by the end user? For example, in FireBug, lets say you modified the width and height of a ExtJs window. I then want to click a button and have it use this code to send back all the properties of the window so that I can store somewhere on the server side. Is this what this code is used for?

calavera
11 Jun 2009, 5:50 PM
I initiated another post on this before I found this one. My question is, can this be utilized to store such things as the state of a window as modified by the end user? For example, in FireBug, lets say you modified the width and height of a ExtJs window. I then want to click a button and have it use this code to send back all the properties of the window so that I can store somewhere on the server side. Is this what this code is used for?
Saki answered this before. Take a look at his cellactions example: http://cellactions.extjs.eu/

jsakalos
12 Jun 2009, 12:33 AM
I initiated another post on this before I found this one. My question is, can this be utilized to store such things as the state of a window as modified by the end user? For example, in FireBug, lets say you modified the width and height of a ExtJs window. I then want to click a button and have it use this code to send back all the properties of the window so that I can store somewhere on the server side. Is this what this code is used for?
Normally, state is saved w/o user intervention periodically.

dbassett74
12 Jun 2009, 5:51 AM
That's the difference here. I need to be able to control the saving the state myself. I want to provide the end user a Save button. So probably I'm using this a little different, but do you think I'll be able to implement this and figure it out?

jsakalos
12 Jun 2009, 5:54 AM
Study and understand the code - I think it shouldn't be that difficult. If lucky, it can be done with config only (e.g. do not start the provider) and by manually calling submitState.

dbassett74
12 Jun 2009, 6:08 AM
So I shouldn't need to use the provider at all? Basically, I'm building sort of an end user designer that will allow them to graphically move things around and resize and change their z-order, etc. I then need to save this to a database. So before I even proceeded with this, I need to make sure there is someway to save these properties as set in the browser by the end user. So is this essentially "config" stuff? I haven't played around too much yet with ExtJs directly, only through the Coolite library, but I want to start working directly with ExtJs as it provide much more flexibility and doesn't tie you into M$. Is there any good examples on working with "config", to both set the initial config and save the config? Thanks.

jsakalos
12 Jun 2009, 7:06 AM
From your description, this provider is not the route to take. Just save your values in the database.

dbassett74
12 Jun 2009, 8:39 AM
Okay, but that is what I don't know how to do. Let's say I have an ExtJs window displayed and the user changes the width and height of it. They then click on a button to send ALL the window configuration information back to the server via AJAX. I don't know how to do this. Is there some easy method to grab a hold of all configuration information for a given object and send it in JSON over AJAX to a given URL for further processing?

jsakalos
12 Jun 2009, 12:34 PM
You must decide what is "configuration information" so you will need to collect all data falling into this category and send it to server. Ext makes no assumptions on this.

As to using Ajax to send something to server, consult the docs and/or examples; there is plenty of them.

jgilles
16 Jun 2009, 5:09 AM
Hi,

Thank you for this component, very useful.

Just on issue : if the same parameter is submitted twice, with a new value and then with the previous value, the correct value is not sent.

ex :



Ext.state.Manager.set('foo', true); // wait 750ms... => ok.

Ext.state.Manager.set('foo', false); // wait... => ok

Ext.state.Manager.set('foo', true); // don't wait, and immediatly :
Ext.state.Manager.set('foo', false); // => the value 'true' is sent. Bad !
Actually the bug is in queueChange :

var changed = undefined === this.state[name] || this.state[name] !== value;If name is already in the queue, the correct value to check is the item in the queue, not the value in the state.



,queueChange:function(name, value) {
var prev_value = this.state[name];
for(i = 0; i < this.queue.length; i++) {
if(this.queue[i].name === name) {
prev_value = this.decodeValue(this.queue[i].value);
}
}
var changed = undefined === prev_value || prev_value !== value;

An optimization could be to remove the corresponding item in the queue if the value remains the same in order to avoid some unnecessary ajax requests, but it's probably too much work for a small gain.

jsakalos
16 Jun 2009, 8:32 AM
Yes, you're right. Implemented the change and updated the code in the first post.

Thank you.

calavera
16 Jun 2009, 9:52 AM
Yes, you're right. Implemented the change and updated the code in the first post.

Thank you.

I'm using an old code you posted here and I just updated with the latest from your first post and I get all kind of errors like: "Ext.ux.HttpProvider is not a constructor". I think it's based on the old code. How can I safely update my code to the latest ? What's changed ? Here's my old code:


// vim: ts=4:sw=4:nu:fdc=2:nospell
/**
* Ext.ux.HttpProvider extension
*
* @author Ing. Jozef Sakalos
* @copyright (c) 2008, Ing. Jozef Sakalos
* @version $Id: Ext.ux.HttpProvider.js 308 2008-08-12 21:51:12Z jozo $
*
* @license Ext.ux.HttpProvider is licensed under the terms of
* the Open Source LGPL 3.0 license. Commercial use is permitted to the extent
* that the code/component(s) do NOT become part of another Open Source or Commercially
* licensed development library or toolkit without explicit permission.
*
* License details: http://www.gnu.org/licenses/lgpl.html
*/

/*global Ext, console */

// {{{
// Define clone function if it is not already defined
if('function' !== Ext.type(Ext.ux.clone)) {
Ext.ux.clone = function(o) {
if('object' !== typeof o) {
return o;
}
var c = 'function' === typeof o.pop ? [] : {};
var p, v;
for(p in o) {
if(o.hasOwnProperty(p)) {
v = o[p];
if('object' === typeof v) {
c[p] = Ext.ux.clone(v);
}
else {
c[p] = v;
}
}
}
return c;
};
} // eo clone
// }}}

/**
* @class Ext.ux.HttpProvider
* @extends Ext.state.Provider
* @constructor
* @param {Object} config Configuration object
*/
// {{{
Ext.ux.HttpProvider = function(config) {

this.addEvents(
/**
* @event readsuccess
* Fires after state has been successfully received from server and restored
* @param {HttpProvider} this
*/
'readsuccess'
/**
* @event readfailure
* Fires in the case of an error when attempting to read state from server
* @param {HttpProvider} this
*/
,'readfailure'
/**
* @event savesuccess
* Fires after the state has been successfully saved to server
* @param {HttpProvider} this
*/
,'savesuccess'
/**
* @event savefailure
* Fires in the case of an error when attempting to save state to the server
* @param {HttpProvider} this
*/
,'savefailure'
);

// call parent
Ext.ux.HttpProvider.superclass.constructor.call(this);

Ext.apply(this, config, {
// defaults
delay:750 // buffer changes for 750 ms
,dirty:false
,started:false
,autoStart:true
,autoRead:true
,user:'user'
,id:1
,account:'account'
,logFailure:false
,logSuccess:false
,queue:[]
,url:'.'
,readUrl:undefined
,saveUrl:undefined
,method:'post'
,saveBaseParams:{}
,readBaseParams:{}
,paramNames:{
id:'id'
,name:'name'
,value:'value'
,user:'user'
,account:'account'
,data:'data'
}
}); // eo apply

if(this.autoRead) {
this.readState();
}

this.dt = new Ext.util.DelayedTask(this.submitState, this);
if(this.autoStart) {
this.start();
}
}; // eo constructor
// }}}

Ext.extend(Ext.ux.HttpProvider, Ext.state.Provider, {

// localizable texts
saveSuccessText:'Save Success'
,saveFailureText:'Save Failure'
,readSuccessText:'Read Success'
,readFailureText:'Read Failure'
,dataErrorText:'Data Error'

// {{{
/**
* Initializes state from the passed state object or array.
* This method can be called early during page load having the state Array/Object
* retrieved from database by server.
* @param {Array/Object} state State to initialize state manager with
*/
,initState:function(state) {
if(state instanceof Array) {
Ext.each(state, function(item) {
this.state[item.name] = this.decodeValue(item.value);
}, this);
}
else {
this.state = state ? state : {};
}
} // eo function initState
// }}}
// {{{
/**
* Sets the passed state variable name to the passed value and queues the change
* @param {String} name Name of the state variable
* @param {Mixed} value Value of the state variable
*/
,set:function(name, value) {
if(!name) {
return;
}

this.queueChange(name, value);

} // eo function set
// }}}
// {{{
/**
* Starts submitting state changes to server
*/
,start:function() {
this.dt.delay(this.delay);
this.started = true;
} // eo function start
// }}}
// {{{
/**
* Stops submitting state changes
*/
,stop:function() {
this.dt.cancel();
this.started = false;
} // eo function stop
// }}}
// {{{
/**
* private, queues the state change if state has changed
*/
,queueChange:function(name, value) {
var changed = undefined === this.state[name] || this.state[name] !== value;
var o = {};
var i;
var found = false;
if(changed) {
o[this.paramNames.name] = name;
o[this.paramNames.value] = this.encodeValue(value);
for(i = 0; i < this.queue.length; i++) {
if(this.queue[i].name === o.name) {
this.queue[i] = o;
found = true;
}
}
if(false === found) {
this.queue.push(o);
}
this.dirty = true;
}
return changed;
} // eo function bufferChange
// }}}
// {{{
/**
* private, submits state to server by asynchronous Ajax request
*/
,submitState:function() {
if(!this.dirty) {
this.dt.delay(this.delay);
return;
}
this.dt.cancel();

var o = {
url:this.saveUrl || this.url
,method:this.method
,scope:this
,success:this.onSaveSuccess
,failure:this.onSaveFailure
,queue:Ext.ux.clone(this.queue)
,params:{}
};

var params = Ext.apply({}, this.saveBaseParams);
params[this.paramNames.id] = this.id;
params[this.paramNames.user] = this.user;
params[this.paramNames.account] = this.account;
params[this.paramNames.data] = Ext.encode(o.queue);

Ext.apply(o.params, params);

// be optimistic
this.dirty = false;

Ext.Ajax.request(o);
} // eo function submitState
// }}}
// {{{
/**
* Clears the state variable
* @param {String} name Name of the variable to clear
*/
,clear:function(name) {
this.set(name, undefined);
} // eo function clear
// }}}
// {{{
/**
* private, save success callback
*/
,onSaveSuccess:function(response, options) {
if(this.started) {
this.start();
}
var o = {};
try {o = Ext.decode(response.responseText);}
catch(e) {
if(true === this.logFailure) {
this.log(this.saveFailureText, e, response);
}
this.dirty = true;
return;
}
if(true !== o.success) {
if(true === this.logFailure) {
this.log(this.saveFailureText, o, response);
}
this.dirty = true;
}
else {
Ext.each(options.queue, function(item) {
if(!item) {
return;
}
var name = item[this.paramNames.name];
var value = this.decodeValue(item[this.paramNames.value]);

if(undefined === value || null === value) {
Ext.ux.HttpProvider.superclass.clear.call(this, name);
}
else {
// parent sets value and fires event
Ext.ux.HttpProvider.superclass.set.call(this, name, value);
}
}, this);
if(false === this.dirty) {
this.queue = [];
}
else {
var i, j, found;
for(i = 0; i < options.queue.length; i++) {
found = false;
for(j = 0; j < this.queue.length; j++) {
if(options.queue[i].name === this.queue[j].name) {
found = true;
break;
}
}
if(true === found && this.encodeValue(options.queue[i].value) === this.encodeValue(this.queue[j].value)) {
delete(this.queue[j]);
}
}
}
if(true === this.logSuccess) {
this.log(this.saveSuccessText, o, response);
}
this.fireEvent('savesuccess', this);
}
} // eo function onSaveSuccess
// }}}
// {{{
/**
* private, save failure callback
*/
,onSaveFailure:function(response, options) {
if(true === this.logFailure) {
this.log(this.saveFailureText, response);
}
if(this.started) {
this.start();
}
this.dirty = true;
this.fireEvent('savefailure', this);
} // eo function onSaveFailure
// }}}
// {{{
/**
* private, read state callback
*/
,onReadFailure:function(response, options) {
if(true === this.logFailure) {
this.log(this.readFailureText, response);
}
this.fireEvent('readfailure', this);

} // eo function onReadFailure
// }}}
// {{{
/**
* private, read success callback
*/
,onReadSuccess:function(response, options) {
var o = {}, data;
try {o = Ext.decode(response.responseText);}
catch(e) {
if(true === this.logFailure) {
this.log(this.readFailureText, e, response);
}
return;
}
if(true !== o.success) {
if(true === this.logFailure) {
this.log(this.readFailureText, o, response);
}
}
else {
try {data = Ext.decode(o[this.paramNames.data]);}
catch(ex) {
if(true === this.logFailure) {
this.log(this.dataErrorText, o, response);
}
return;
}
if(!(data instanceof Array) && true === this.logFailure) {
this.log(this.dataErrorText, data, response);
return;
}
Ext.each(data, function(item) {
this.state[item[this.paramNames.name]] = this.decodeValue(item[this.paramNames.value]);
}, this);
this.queue = [];
this.dirty = false;
if(true === this.logSuccess) {
this.log(this.readSuccessText, data, response);
}
this.fireEvent('readsuccess', this);
}
} // eo function onReadSuccess
// }}}
// {{{
/**
* Reads saved state from server by sending asynchronous Ajax request and processing the response
*/
,readState:function() {
var o = {
url:this.readUrl || this.url
,method:this.method
,scope:this
,success:this.onReadSuccess
,failure:this.onReadFailure
,params:{}
};

var params = Ext.apply({}, this.readBaseParams);
params[this.paramNames.id] = this.id;
params[this.paramNames.user] = this.user;
params[this.paramNames.account] = this.account;

Ext.apply(o.params, params);
Ext.Ajax.request(o);
} // eo function readState
// }}}
// {{{
/**
* private, logs errors or successes
*/
,log:function() {
if(console) {
console.log.apply(console, arguments);
}
} // eo log
// }}}

}); // eo extend

// eof


Thanks.

jsakalos
16 Jun 2009, 1:45 PM
Namespace changed. Now it is Ext.ux.state.HttpProvider. Sorry for inconvenience but I've put all my extensions to namespaces à la Ext.

calavera
16 Jun 2009, 2:54 PM
I see. I've modified my script but now I get "this.queue[i] is undefined" at the line "if(this.queue[i].name === name) {" when I refresh the page, after some grid config is sent.

Any idea ? Thanks.

jsakalos
16 Jun 2009, 11:50 PM
Sorry, my bad. Take the code from the first post now.

calavera
17 Jun 2009, 12:00 AM
Sorry, my bad. Take the code from the first post now.
It works great. Thank you man!

calavera
17 Jun 2009, 4:13 AM
I've got a question: Is it possible to have an "autoSave: true" option ? I want to get the grid config from database at the click of one button but if I access for the first time the grid or if I never changed its config, it has no database entry and I need to grab its default config. So I would like to make a database entry with the default config after the grid has been loaded.

Can this be made ? Thank you and sorry for so many questions. :)

jsakalos
17 Jun 2009, 11:34 AM
You could manually trigger some state event first time, however, if you set a config variable that is later saved in state and if no state is available then that variable is used. For example, you set width:600 and height:400 on a stateful window at config time. The window will be 600x400 first time. When user resizes it to, let's say, 700x500 the window will be 700x500 next time, ignoring config values 600x400.

calavera
17 Jun 2009, 12:40 PM
You could manually trigger some state event first time, however, if you set a config variable that is later saved in state and if no state is available then that variable is used. For example, you set width:600 and height:400 on a stateful window at config time. The window will be 600x400 first time. When user resizes it to, let's say, 700x500 the window will be 700x500 next time, ignoring config values 600x400.
I could do a check to see if the ID of the grid is stored in database. If it is, do nothing at the grid display.. if it's not, I would like to grab the current grid values, the default ones, and store them into database. I need current column names and width + the filters applyed to the grid. To be even more specific: I want to make a call to a PHP file with the grid ID. The PHP file should search the grid ID in my database, grab the filter data and column names + width and then to generate a PDF file. I want to make one single PHP file that dinamicaly creates the PDF file for every grid. That's why I need the grid ID and then the values stored in database.

Any ideas on how could I make a default database config entry for every grid at the moment that its accessed ?

Thanks.

jsakalos
17 Jun 2009, 1:03 PM
Who will set defaults? User? Or you (the developer)?

calavera
17 Jun 2009, 2:22 PM
I will set the initial config of the grids. But through column model and sortinfo, minimal config. I would like to make a check at the database file for the grid_id and the user's ID. It that can't be found I would like to grab the current default grid config and add it to the database. This way, at the first user interaction with the specified grid, he would be able to immediately click on the PDF generation button and I would be able to find some config for the respective grid and build my PDF with the column names and width. You think that's possible ?

Thank you.

smudgeface
18 Jun 2009, 4:58 PM
Saki,

I noticed that on line 223 you changed Ext.ux.clone(this.queue) to Ext.ux.util.clone(this.queue) but you do not include clone at the top like you used to. Is this intentional?

jsakalos
18 Jun 2009, 5:11 PM
Yes, I've moved clone to Ext.ux.util (http://extjs.eu/docs/?class=Ext.ux.util).

calavera
19 Jun 2009, 6:54 PM
Can anyone please tell me how can I decode state values like this ?


o%3Acolumns%3Da%253Ao%25253Aid%25253Ds%2525253Afirst_column%25255Ewidth%25253Dn%

I need to grab the values and reuse them somewhere... I need to pass them to an array or somthing... Any idea on how to make this more easy to read ?

Thanks.

jsakalos
20 Jun 2009, 12:33 AM
The provider has decodeValue/encodeValue methods for that.

calavera
21 Jun 2009, 7:37 AM
I want to take the decoded values and run another MySQL query based on the filter data extracted from there. I see that the type of the filter (string, date, numeric, list) it's not saved into sqlite database. Can I save the type filter value too ?

Thanks.

jsakalos
21 Jun 2009, 10:13 AM
I'm not sure that I understand the question. Can you rephrase it please?

BTW, Ext state infrastructure is good to save/restore state. I wouldn't use it for anything else.

polydyne
22 Jun 2009, 9:08 AM
I want to use HttpProvider to save to state of my grid to db, After reading a few times, I am still not able to implement this code. May be I am going nuts here, but some one has to put some step by step instruction from start to finish.. I am sorry I guess I need to be spoon fed :(

jsakalos
22 Jun 2009, 11:53 AM
Are you able to save state of the grid with CookieProvider?

polydyne
22 Jun 2009, 12:57 PM
Are you able to save state of the grid with CookieProvider?

No, I havent tried with Cookie yet. Got started directly with HttpProvider.

Thanks.

jsakalos
22 Jun 2009, 2:13 PM
I'd recommend to try CookieProvider first to eliminate possible problems not related to HttpProvider. It's quite complex to implement HttpProvider as you need also running server-side.

ext_fan
14 Aug 2009, 8:12 PM
Hi - Thanks Saki for the g8 extension.

Does anyone know the step by step guide for implementing this extension using jsp or server side java.

jsakalos
14 Aug 2009, 11:08 PM
Once your java backend can save and read database, your're almost done. You just need to create a database table to keep the state and then mimic the HttpProvider related logic from http://cellactions.extjs.eu/

ext_fan
15 Aug 2009, 9:59 PM
Hi Saki - Thanks for replying.

I'm planning to post the step by step implementation guide to all java users in this thread.

Trying to implement your plugin, just wanted to know what does "data" or "value" parameter holds.

In the savestate method you save the name and value from data object. Just curious what is the sample "data" and how does the "name" "value" look like or what it contains.

jsakalos
15 Aug 2009, 11:31 PM
name is stateId or id of the component and value is encoded state. Example:


name = stat-admin-mod-win
value = o%3Awidth%3Dn%253A931%5Eheight%3Dn%253A604%...

bogguard
31 Aug 2009, 6:26 AM
Hi,

I'm also in need of a database save of the state and so i implemented your http-provider.js in extjs 3.0.

Saving the state into my database works suprisingly well, although the retrieving of the state goes less great.
When resizing a column the values are posted to the database, when reloading the page the values are retrieved from the database but my grid isn't rendered properly. It's like it doesn't look to the values and just creates the default gridview.

Does the grid needs any extra function to render the grid with the data from the httpProvider?

Also I notice that any component (even if it isn't defined as stateful:true is giving a post to the php page when resizing it.

Kind regards,
Bogguard

jsakalos
31 Aug 2009, 11:59 AM
1. Has it worked with Ext 2.x? If it hasn't or if you haven't tried then the main question is: Is the state data available at the time you render the components? BTW, I've tested HttpProvider with Ext 3.0 and it just works, no problems.

2. Components are stateful by default, however, for state saving/restoring they must have non-ext-generated ids or stateIds. You can turn statefulness off by explicitly setting stateful:false, or by not giving components your ids or stateIds.

bogguard
1 Sep 2009, 12:59 AM
Hi Saki,

I investigated further and checked for any difference with your sample.
Then I noticed you are using Ext.state.Manager.getProvider().initState instead of using the autoread:true.

I started debugging and came to the conclusion that the ajax request happens before the grid rendering, but the returned result and filled up state occurs probaby after the grid rendering request. So that's why it didn't render with the correct state.

Implementing the initstate and setting my autoread to false fixed the problem.

Thanks for your help!

btw: i included a small zip as attachment for people trying to make it work in a very simple sample. Just include your ext to it and you can play with it. Just some hardcoded strings in the php you can replace by your http post requests if you change anything. Firebug is a must.

medley
24 Sep 2009, 6:52 AM
Hello,

I added this code to define the provider :



Ext.state.Manager.setProvider(new Ext.ux.state.HttpProvider({
url:'torisManagementAction.do?action='+ TOP.struts.action.saveState
,user:TOP.userLogged
,session:'session'
,id:TOP.userIdLogged
,readBaseParams:{cmd:'readState'}
,saveBaseParams:{cmd:'saveState'}
,autoSave:true
}));
Ext.state.Manager.getProvider().initState([{"grid.receivedAM":"testgrid","value":"o:columns=a%3Ao%253Aid%253Ds%25253AtitleDoc%255Ewidth%253Dn%25253A25%255Ehidden%253Db%25253A1%5Eo%253Aid%253Ds%25253Arapporteurs%255Ewidth%253Dn%25253A0%255Ehidden%253Db%25253A1%5Eo%253Aid%253Ds%25253AdocumentType%255Ewidth%253Dn%25253A0%255Ehidden%253Db%25253A1%5Eo%253Aid%253Ds%25253AdocCommittee%255Ewidth%253Dn%25253A0%255Ehidden%253Db%25253A1%5Eo%253Aid%253Ds%25253Aannotation_doc%255Ewidth%253Dn%25253A0%255Ehidden%253Db%25253A1%5Eo%253Aid%253Ds%25253ApeNumberFormatted%255Ewidth%253Dn%25253A0%255Ehidden%253Db%25253A1%5Eo%253Aid%253Ds%25253AvotingDate%255Ewidth%253Dn%25253A0%255Ehidden%253Db%25253A1%5Eo%253Aid%253Ds%25253Aresponsible_doc%255Ewidth%253Dn%25253A0%255Ehidden%253Db%25253A1%5Eo%253Aid%253Ds%25253AdocId%255Ewidth%253Dn%25253A183%255Ehidden%253Db%25253A1%5Eo%253Aid%253Ds%25253AamdNbr%255Ewidth%253Dn%25253A99%5Eo%253Aid%253Ds%25253ApeNumberFormattedAM%255Ewidth%253Dn%25253A171%5Eo%253Aid%253Ds%25253Alanguage%255Ewidth%253Dn%25253A169%5Eo%253Aid%253Ds%25253AamdType%255Ewidth%253Dn%25253A50%255Ehidden%253Db%25253A1%5Eo%253Aid%253Ds%25253AamdSubtype%255Ewidth%253Dn%25253A152%5Eo%253Aid%253Ds%25253AprocedureNumber%255Ewidth%253Dn%25253A297%5Eo%253Aid%253Ds%25253AtitleAm%255Ewidth%253Dn%25253A195%5Eo%253Aid%253Ds%25253Asignataires%255Ewidth%253Dn%25253A83%5Eo%253Aid%253Ds%25253Aresponsible_am%255Ewidth%253Dn%25253A0%255Ehidden%253Db%25253A1%5Eo%253Aid%253Ds%25253Aversion%255Ewidth%253Dn%25253A170%5Eo%253Aid%253Ds%25253AtablingDate%255Ewidth%253Dn%25253A233%5Eo%253Aid%253Dn%25253A20%255Ewidth%253Dn%25253A46^sort=o%3Afield%3Ds%253AprocedureNumber%5Edirection%3Ds%253AASC"}]);



I used a grid whose stateId is grid.receivedAM.

When I change the size of a column, nothing happens. I don't see any request to the server.

Thanks for help
Medley

jsakalos
24 Sep 2009, 5:05 PM
Does your grid have a non-ext-generated id? Or stateId?

medley
24 Sep 2009, 10:22 PM
Yes I defined my own id and stateId

Medley

jsakalos
25 Sep 2009, 12:10 AM
Then it needs a bit of deeper analysis - this problem has not been reported by anyone else. Check if the grid is stateful, if it calls saveState on column resize and/or step into the code to see what's happening.

charleshimmer
14 Oct 2009, 9:56 AM
Great plugin Saki. Was just wanted I needed because I have users who were running into the cookie size limit issues.

But I've ran into a problem. I'm able to save the state data in my db just fine, but when I go to restore the state, or apply the state of a grid, I get the following error.



ext-all-debug.js:61618 TypeError: Result of expression 's' [undefined] is not an object.


Line 61618 of ext-all-debug.js is the line in red. It looks like it is crashing trying to get the column id.


applyState: function (state) {
var cm = this.colModel,
cs = state.columns;
if (cs) {
for (var i = 0, len = cs.length; i < len; i++) {
var s = cs[i];
var c = cm.getColumnById(s.id);
if (c) {
c.hidden = s.hidden;
c.width = s.width;
var oldIndex = cm.getIndexById(s.id);
if (oldIndex != i) {
cm.moveColumn(oldIndex, i);
}
}
}
}
if (state.sort && this.store) {
this.store[this.store.remoteSort ? 'setDefaultSort' : 'sort'](state.sort.field, state.sort.direction);
}
var o = Ext.apply({},
state);
delete o.columns;
delete o.sort;
Ext.grid.GridPanel.superclass.applyState.call(this, o);
},


Here is how I'm using the plugin with the state data hard coded in there for testing purposes. If I apply just a panel or something simple, it works fine, but if I apply the grid with the id 'unidentified' it throws the error. Any ideas?



Ext.state.Manager.setProvider(new Ext.ux.state.HttpProvider({
saveUrl: baseUrl + App.settings.apiUrl + 'caller/save_state/',
autoRead: false
}));
Ext.state.Manager.getProvider().initState([{
"name": "unidentified",
"value": "o:columns=a%3Ao%253Aid%253Ds%25253Anumberer%255Ewidth%253Dn%25253A40%5Eo%253Aid%253Ds%25253Aname%255Ewidth%253Dn%25253A233%5Eo%253Aid%253Dn%25253A2%255Ewidth%253Dn%25253A100%255Ehidden%253Db%25253A1%5Eo%253Aid%253Dn%25253A3%255Ewidth%253Dn%25253A110%255Ehidden%253Db%25253A1%5Eo%253Aid%253Dn%25253A4%255Ewidth%253Dn%25253A110%255Ehidden%253Db%25253A1%5Eo%253Aid%253Dn%25253A5%255Ewidth%253Dn%25253A180%255Ehidden%253Db%25253A1%5Eo%253Aid%253Dn%25253A6%255Ewidth%253Dn%25253A100%255Ehidden%253Db%25253A1%5Eo%253Aid%253Dn%25253A7%255Ewidth%253Dn%25253A100%255Ehidden%253Db%25253A1%5Eo%253Aid%253Dn%25253A8%255Ewidth%253Dn%25253A100%255Ehidden%253Db%25253A1%5Eo%253Aid%253Dn%25253A9%255Ewidth%253Dn%25253A100%255Ehidden%253Db%25253A1%5Eo%253Aid%253Dn%25253A10%255Ewidth%253Dn%25253A100%255Ehidden%253Db%25253A1%5Eo%253Aid%253Dn%25253A11%255Ewidth%253Dn%25253A100%255Ehidden%253Db%25253A1%5Eo%253Aid%253Dn%25253A12%255Ewidth%253Dn%25253A100%5Eo%253Aid%253Dn%25253A13%255Ewidth%253Dn%25253A100%255Ehidden%253Db%25253A1%5Eo%253Aid%253Dn%25253A14%255Ewidth%253Dn%25253A100%255Ehidden%253Db%25253A1%5Eo%253Aid%253Dn%25253A15%255Ewidth%253Dn%25253A100%5Eo%253Aid%253Ds%25253Alast_result%255Ewidth%253Dn%25253A150%5Eo%253Aid%253Dn%25253A17%255Ewidth%253Dn%25253A130%5Eo%253Aid%253Dn%25253A18%255Ewidth%253Dn%25253A110%5Eo%253Aid%253Dn%25253A19%255Ewidth%253Dn%25253A150%255Ehidden%253Db%25253A1%5Eo%253Aid%253Dn%25253A20%255Ewidth%253Dn%25253A100%255Ehidden%253Db%25253A1%5Eo%253Aid%253Dn%25253A21%255Ewidth%253Dn%25253A100%5Eo%253Aid%253Dn%25253A22%255Ewidth%253Dn%25253A70%5Eo%253Aid%253Dn%25253A23%255Ewidth%253Dn%25253A100%5Eo%253Aid%253Dn%25253A24%255Ewidth%253Dn%25253A100%255Ehidden%253Db%25253A1^sort=o%3Afield%3Ds%253Afirst_name%5Edirection%3Ds%253AASC"
}]);

charleshimmer
14 Oct 2009, 9:57 AM
p.s. I'm using ExtJS 3.0.2 and I checked the json in jslint and it checked out.

jsakalos
14 Oct 2009, 11:02 AM
Is the grid special in some aspect? 'Cause I use the provider for grids (column widths) w/o any problem. Have you defined stateEvents? Can it be a colliding id or stateId?

charleshimmer
14 Oct 2009, 11:11 AM
special aspect? don't think so, it's just in a tab panel. It does have lots of columns, but that shouldn't matter.

The grid states restores fine when using the cookieProvider, but then you run into size limit.

It's a unique id, I'll tried several ids just in case it wasn't. Why would it be looking for s.id? You don't have to give each column an id right?