PDA

View Full Version : Ext.Direct based State Provider



jsakalos
18 Sep 2009, 7:45 AM
Hi all.

Based on my HttpProvider (http://www.extjs.com/forum/showthread.php?t=24970), I've just finished DirectProvider. The logic is same but this one uses Ext.Direct to interact with server.



// vim: ts=4:sw=4:nu:fdc=2:nospell
/*global Ext, console */
/**
* @class Ext.ux.state.DirectProvider
* @extends Ext.state.Provider
*
* Buffering state provider that sends and receives state information to/from server
* using Ext.Direct infrastructure
*
* @author Ing. Jozef Sakáloš
* @copyright (c) 2008-2009, Ing. Jozef Sakáloš
* @version 2.0
* @date 18. September 2009
* @revision $Id: Ext.ux.state.DirectProvider.js 23 2009-09-18 15:41:12Z jozo $
* @depends Ext.ux.util
*
* @license Ext.ux.state.DirectProvider 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
*
* @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 DirectProvider
* @constructor
* @param {Object} config Configuration object
*/
// {{{
Ext.ux.state.DirectProvider = function(config) {

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

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

Ext.apply(this, config, {
// defaults
delay:750 // buffer changes for 750 ms
,dirty:false
,started:false
,autoStart:true
,autoRead:true
,readFn:Ext.emptyFn
,saveFn:Ext.emptyFn
,user:'user'
,id:1
,session:'session'
,logFailure:false
,logSuccess:false
,queue:[]
,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.saveState, this);
if(this.autoStart) {
this.start();
}
}; // eo constructor
// }}}

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

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

// {{{
/**
* Clears the state variable
* @param {String} name Name of the variable to clear
*/
,clear:function(name) {
this.set(name, undefined);
} // eo function clear
// }}}
// {{{
/**
* 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
// }}}
// {{{
/**
* private, logs errors or successes
*/
,log:function() {
if(console) {
console.log.apply(console, arguments);
}
} // eo log
// }}}
// {{{
/**
* 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
// }}}
// {{{
/**
* Ext direct call callback
* @private
*/
,readCallback:function(response, e) {
// handle success
if(true === e.status) {
if(!Ext.isArray(response) && true === this.logFailure) {
this.log(this.dataErrorText, response, e);
return;
}
Ext.each(response, 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, response, e);
}
this.fireEvent('readsuccess', this);
}
// handle failure
else {
if(true === this.logFailure) {
this.log(this.readFailureText, response, e);
}
this.fireEvent('readfailure', this);
}
} // eo function readCallback
// }}}
// {{{
/**
* Reads saved state from server by sending asynchronous Ajax request and processing the response
*/
,readState:function() {
var o = {
paramNames:this.paramNames
}
o[this.paramNames.id] = this.id;
o[this.paramNames.user] = this.user;
o[this.paramNames.session] = this.session;

this.readFn(o, this.readCallback, this);
} // eo function readState
// }}}
// {{{
/**
* Direct call callback function
* @private
*/
,saveCallback:function(response, e) {
// handle success
if(true === e.status) {

// get queue clone that has been sent to server as data
var queue = e.getTransaction().args[0][this.paramNames.data];

// iterate through the sent queue and set local state
Ext.each(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.DirectProvider.superclass.clear.call(this, name);
}
else {
// parent sets value and fires event
Ext.ux.state.DirectProvider.superclass.set.call(this, name, value);
}
}, this);

// nothing to do if we've not got dirty during the server roundtrip
if(false === this.dirty) {
this.queue = [];
}
else {
var i, j, found;
for(i = 0; i < queue.length; i++) {
found = false;
for(j = 0; j < this.queue.length; j++) {
if(queue[i].name === this.queue[j].name) {
found = true;
break;
}
}
// remove equal items, i.e. items already saved
if(true === found && this.encodeValue(queue[i].value) === this.encodeValue(this.queue[j].value)) {
this.queue.remove(this.queue[j]);
}
}
}
if(true === this.logSuccess) {
this.log(this.saveSuccessText, response, e);
}
this.fireEvent('savesuccess', this);
}

// handle failure
else {
if(true === this.logFailure) {
this.log(this.saveFailureText, response, e);
}
this.dirty = true;
this.fireEvent('savefailure', this);
}
} // eo function saveCallback
// }}}
// {{{
/**
* private, submits state to server by asynchronous Ajax request
*/
,saveState:function() {
if(!this.dirty) {
this.dt.delay(this.delay);
return;
}
this.dt.cancel();

var queueClone = Ext.ux.util.clone(this.queue);
var o = {
paramNames:this.paramNames
}
o[this.paramNames.id] = this.id;
o[this.paramNames.user] = this.user;
o[this.paramNames.session] = this.session;
o[this.paramNames.data] = queueClone;

// be optimistic
this.dirty = false;

// call direct function
this.saveFn(o, this.saveCallback, this);

} // eo function saveState
// }}}
// {{{
/**
* 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
// }}}

}); // eo extend

// eof

Dumbledore
22 Oct 2009, 4:41 AM
can you provide a simple direct php class to handle this?

Bye, Dumbledore

jsakalos
22 Oct 2009, 3:46 PM
The server side I use is proprietary so I cannot publish the code. However, it shouldn't be that difficult to code it as you only need two functions: saveState and readState (or your own names). If you will deliver the full state on page load and initialize the provider with that state (autoRead:false and initState(....) at the beginning of the page) then you only need saveState remotable method. This is what is sent to server:


{"action":"State","method":"saveState","data":[{"paramNames":{"id":"id","name":"name","value":"value","user":"user","session":"session","data":"data"},"id":"1","user":"jozo@default","session":"Perseus","data":[{"name":"testwidget","value":"o%3Awidth%3Dn%253A200%5Eheight%3Dn%253A150%5Ex%3Dn%253A395%5Ey%3Dn%253A171"}]}],"type":"rpc","tid":2}

Dumbledore
23 Oct 2009, 5:12 AM
ok, i figure out how to to that. Two questions:

1. Why you don´t implement a simple get function to get only one state?
2. Why do my listener not work?


// State-Handler
Ext.state.Manager.setProvider(new Ext.ux.state.DirectProvider({
user : 'user',
readFn : Ext.app.State.read,
saveFn : Ext.app.State.save,
autoRead: false,
listeners : {
readsuccess : function(r){
console.log("readsuccess");
console.log(r);
},
readfailure : function(r){
console.log("readfailure");
console.log(r);
}
}
}));

Dumbledore
23 Oct 2009, 6:58 AM
ok, listener don´t work. But this works fine:


// State-Handler
var stateProvider = new Ext.ux.state.DirectProvider({
user : 'user',
session : this.title,
readFn : Ext.app.State.read,
saveFn : Ext.app.State.save,
autoRead: false
})
stateProvider.on('readsuccess', function(prov){
console.log(prov.state);
})

jsakalos
25 Oct 2009, 12:19 AM
You have also options: logSuccess:true, logFailure:true.

Dumbledore
26 Oct 2009, 12:47 AM
i need again your help...

i have a singlepage application. All is rendered at startup and then the login appears. After login i make a ...stateProvider.readState();

But how do i set at this time all states to all (always rendered) components?

[update]

when i switched back to Cookie-Provider all runs fine... Whats wrong?

When i run follwing:

Ext.state.Manager.getProvider().initState([{"name":"app_rightnavigation","value":"o%3Acollapsed%3Db%253A0"}]);

My Panel with the stateId "app_rightnavigation" must expanded? Also at runtime?

Bye, Dumbledore

jsakalos
26 Oct 2009, 12:29 PM
Generally, the state provider must be initialized at the beginning of the application as state is read and applied when classes are instantiated. Cookie provider has saved state automatically available as it is stored by client. For HttpProvider you need to deliver this state from the server - ideally at the initial page load.

See http://cellactions.extjs.eu/ for example on how it can be done. The content of the page is not interesting - mind only state save/restore parts. Take a look also at the page source to see the app flow.

Dumbledore
28 Oct 2009, 11:48 PM
now it worked for me, thanks for your help.

It will be nice if there were a destroy state (destroyFn) handler in future versions. In this case it will be easier to remove no needed entries.

Bye...

jsakalos
29 Oct 2009, 10:38 AM
You can call clear method to remove an individual state. There is no clearAll method currently.

Dumbledore
15 Feb 2010, 9:50 PM
Hi,

is have a small problem with that state handling of this extension.

If you make following at the firebug console:



Ext.state.Manager.getProvider().set('theme', 'xtheme-black');
Ext.state.Manager.getProvider().clear('theme');
Ext.state.Manager.getProvider().get('theme', 'xtheme-blue');


The last call should the default value of the state, but it´s return 'undefined'. But why?


Bye, Dumbledore

Dumbledore
16 Feb 2010, 12:41 AM
me again...

there is a piece of code inside the component like this:



,saveCallback:function(response, e) {
// handle success
if(true === e.status) {

// get queue clone that has been sent to server as data
var queue = e.getTransaction().args[0][this.paramNames.data];

// iterate through the sent queue and set local state
Ext.each(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.DirectProvider.superclass.clear.call(this, name);
}
else {
// parent sets value and fires event
Ext.ux.state.DirectProvider.superclass.set.call(this, name, value);
}
}, this);


This runs on failure, because decodeValue returns a string. When i changed this to



if('undefined' === value || null === value) {


all runs fine. Is it a bug?

jsakalos
16 Feb 2010, 4:53 AM
Hi,

is have a small problem with that state handling of this extension.

If you make following at the firebug console:



Ext.state.Manager.getProvider().set('theme', 'xtheme-black');
Ext.state.Manager.getProvider().clear('theme');
Ext.state.Manager.getProvider().get('theme', 'xtheme-blue');
The last call should the default value of the state, but it´s return 'undefined'. But why?


Bye, Dumbledore

Hmmm, clear is clear. I'd say that it could be expected behavior. Another providers behave different way?

jsakalos
16 Feb 2010, 4:55 AM
'undefined' is string, undefined is special type undefined.

Dumbledore
16 Feb 2010, 10:58 AM
Hi,

i try many things but this code inside the Provider is never used, and so the state delete of the provider superclass never called:


if(undefined === value || null === value) {
Ext.ux.state.DirectProvider.superclass.clear.call(this, name);
}


when i changed this to:


if('undefined' === value || null === value)

all runs fine. In my opinion its a bug. Can you test it?

SMMJ_Dev
5 Mar 2010, 6:44 AM
Hello Everyone, I am a new poster. I had a question about this DirectProvider. First of all, I made some slight modifications to it so that it would work with DirectJNgine since I am working on the Java side. I have included my Ext.ux.state.DirectProvider changes. Basically, the Ext.Direct function doesn't accept the 3rd parameter 'this' which I assume keeps the scope for the callback function. Since I don't have the scope, I have to make a call to get the provider and I replace the 'this' with the variable I created that get's the provider.The changes can be seen in the following functions: saveState, saveCallback, readState, and readCallback


Ext.ns('Ext.ux.state');
Ext.ux.state.DirectProvider=function(config){
this.addEvents(
'readsuccess'
,'readfailure'
,'savesuccess'
,'savefailure'
);
Ext.ux.state.DirectProvider.superclass.constructor.call(this);
Ext.apply(this, config,{
delay:750,//buffer changes for 750 ms
dirty:false,
started:false,
autoStart:true,
autoRead:true,
readFn:Ext.emptyFn,
saveFn:Ext.emptyFn,
user:'user',
id:1,
session:'session',
logFailure:false,
logSuccess:false,
queue:[],
paramNames:{
id:'id',
name:'name',
value:'value',
user:'user',
session:'session',
data:'data'
}
});
if(this.autoRead){this.readState();}
this.dt=new Ext.util.DelayedTask(this.saveState,this);
if(this.autoStart){this.start();}
};
Ext.extend(Ext.ux.state.DirectProvider,Ext.state.Provider,{
saveSuccessText:'Save Success',
saveFailureText:'Save Failure',
readSuccessText:'Read Success',
readFailureText:'Read Failure',
dataErrorText:'Data Error',
clear:function(name){this.set(name,undefined);},
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:{};}
},
log:function(){
if(console){console.log.apply(console,arguments);}
},
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;
},
readCallback:function(response,e){
var provider=Ext.state.Manager.getProvider();
// handle success
if(true===e.status){
if(!Ext.isArray(response)&&true===provider.logFailure){
provider.log(provider.dataErrorText,response,e);
return;
}
Ext.each(response,function(item){
provider.state[item[provider.paramNames.name]]=provider.decodeValue(item[provider.paramNames.value]);
},provider);
provider.queue=[];
provider.dirty=false;
if(true===provider.logSuccess){provider.log(provider.readSuccessText,response,e);}
provider.fireEvent('readsuccess',provider);
}
// handle failure
else{
if(true===provider.logFailure){provider.log(provider.readFailureText,response,e);}
provider.fireEvent('readfailure',provider);
}
},
/**
* Reads saved state from server by sending asynchronous Ajax request and processing the response
*/
readState:function(){
var o={paramNames:this.paramNames}
o[this.paramNames.id]=this.id;
o[this.paramNames.user]=this.user;
o[this.paramNames.session]=this.session;
this.readFn(o,this.readCallback);
},
/**
* Direct call callback function
* @private
*/
saveCallback:function(result,e){
var provider=Ext.state.Manager.getProvider();
// handle success
if(true===e.status){
// get queue clone that has been sent to server as data
var queue=e.getTransaction().args[0][provider.paramNames.data];
// iterate through the sent queue and set local state
Ext.each(queue,function(item){
if(!item){return;}
var name=item[provider.paramNames.name];
var value=provider.decodeValue(item[provider.paramNames.value]);
if(undefined===value||null===value){Ext.ux.state.DirectProvider.superclass.clear.call(provider, name);}
else{Ext.ux.state.DirectProvider.superclass.set.call(provider,name,value);}
},provider);
// nothing to do if we've not got dirty during the server roundtrip
if(false===provider.dirty){provider.queue=[];}
else{
var i,j,found;
for(i=0;i<queue.length;i++){
found=false;
for(j=0;j<provider.queue.length;j++){
if(queue[i].name===provider.queue[j].name){found=true;break;}
}
// remove equal items, i.e. items already saved
if(true===found&&provider.encodeValue(queue[i].value)===provider.encodeValue(this.queue[j].value)){provider.queue.remove(provider.queue[j]);}
}
}
if(true===provider.logSuccess){provider.log(provider.saveSuccessText,response,e);}
provider.fireEvent('savesuccess',provider);
}
// handle failure
else{
if(true===provider.logFailure){provider.log(provider.saveFailureText,response,e);}
provider.dirty=true;
provider.fireEvent('savefailure',provider);
}
},
/**
* private, submits state to server by asynchronous Ajax request
*/
saveState:function(){
if(!this.dirty){
this.dt.delay(this.delay);
return;
}
this.dt.cancel();
var queueClone=Ext.ux.util.clone(this.queue);
var o={paramNames:this.paramNames}
o[this.paramNames.id]=this.id;
o[this.paramNames.user]=this.user;
o[this.paramNames.session]=this.session;
o[this.paramNames.data]=queueClone;
// be optimistic
this.dirty=false;
// call direct function
this.saveFn(o,this.saveCallback);
},
/**
* 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);
},
/**
* Starts submitting state changes to server
*/
start:function(){
this.dt.delay(this.delay);
this.started=true;
},
/**
* Stops submitting state changes
*/
stop:function(){
this.dt.cancel();
this.started=false;
}
});


// State-Handler
Ext.state.Manager.setProvider(new Ext.ux.state.DirectProvider({
user : 'user',
readFn : Ext.app.State.read,
saveFn : Ext.app.State.save,
autoRead: false
}));
OK, so anyway, I've been doing a lot of reading in the forums. I have gotten the saving working, and when I make a call to Ext.state.Manager.getProvider().initState([...]); and manually put in an initial state, everything works fine. My problem is how do I get the readFn of the provider to read my settings from the database? I came across this information on your forums: http://www.extjs.com/forum/showthread.php?t=61482. They are creating a dynamic JavaScript page to fill in the initState information. Ok, I can do this and this will work.

So then my new question becomes what is the purpose of the readFn of the Direct Provider? Is the readFn suppose to do this for me? Or do I just create a dynamic JavaScript page like the user in the above forum link did?

jsakalos
6 Mar 2010, 12:56 PM
If autoRead is false readFn is not used.

I have no clue what is the nature of "slight modifications to it so that it would work with DirectJNgine".

I don't think that to change a code and then post it is a good idea without clearly saying that you're forking and that you are going to maintain the fork.

Much better is to create an extension, to report a bug, to request a feature, whatever applies.

SMMJ_Dev
8 Mar 2010, 5:34 AM
"If autoRead is false readFn is not used."
Ok, that makes since. Well, I am not clear on the process of using the readFn with autoRead set to true. I do have this working with a dynamic javascript page though, and it works really well! Thank you for your help!


"I have no clue what is the nature of "slight modifications to it so that it would work with DirectJNgine".

I don't think that to change a code and then post it is a good idea without clearly saying that you're forking and that you are going to maintain the fork.

Much better is to create an extension, to report a bug, to request a feature, whatever applies. "

There was a scope issue on the DirectJNgine side for the callback function that's all. It's all basically the same exact code with a modification of how to make the readFn/saveFn calls and how to handle the callbacks for the modifications.

I am definitely not wanting to maintain the code. I do not have time to mess with that. I just wanted to implement Ext.Direct state saving and reading with Java. I just so happen to be using the DirectJNgine because it's easy to implement and maintain. I figured if other people were having the same problems I was having to implement this that they could look at what I did, possibly to help them out a little, idk.

If anyone else has problems, I highly recommend taking some Ext training classes! :D

jsakalos
8 Mar 2010, 11:40 AM
The main problem of forking, when it is not clearly defined, is that if people download your code from the above and then ask me, as the author, about a problem, I cannot say if the problems my original code or your modification.

The fast, maybe a bit dirty, solution could be if you posted your changes as: "change line xxx to yyyy", etc. Then people would download the original code, applied your modifications and in the case of problem it could be clearly distinguished.

SMMJ_Dev
8 Mar 2010, 12:17 PM
Would this work?

http://www.djengineer.com/Differences.htm

On the left is your version. On the right are my modifications. The differences are highlighted, and the files are side by side.

jsakalos
8 Mar 2010, 12:27 PM
I don't know how other users can use diffs - we'll see reactions. What is definitely missing is that you do not alter the file header so after applying the diff the file will still read: Author: Saki, Revision: xxx so user do not know that the file has been modified.

The resulting file has to clearly state that it is your modification, contact to you, nature of modification, etc. Try to understand me: I can maintain, support, help on extensions I have written and I know what they contain.

It may sound strange but by posting an extension I take the responsibility of helping users with its applications, of fixing bugs, of implementing new features, generally, of maintaining it. I want the same from you. If you post an improved/modified version, take the responsibility for your changes.

SMMJ_Dev
8 Mar 2010, 1:00 PM
I am very unfamiliar with this process and don't really know anything about what I'm suppose to do here. I made an update that shows that I was the author of the modification and where the origin source came from. I left your copyright on there. I am unfamiliar with copyrights really, so I guess I keep that on there for you since I don't have any copyright stuff myself.

Is there anything I am doing wrong? I hope I do not violate any of your copyright information. If I have, let me know and I can just remove my modifications.
http://www.djengineer.com/Differences.htm

jsakalos
8 Mar 2010, 1:15 PM
Nothing wrong. Sorry if I made you to feel so. The patch is now OK, it says thay you've done those modifications and if users will need some help they will know whom to address. That is all there is to it.

Thank you for keeping up, I believe your modifications help someone.

I appreciate and admire everybody who share their work and knowledge so I'm glad that we've made it up to this point.

:) :) :)

SMMJ_Dev
8 Mar 2010, 1:22 PM
Oh ok, cool! I will be working with ExtJS for awhile. Thanks for all of your help sir!

Sesshomurai
28 May 2010, 11:51 AM
Hello Everyone, I am a new poster. I had a question about this DirectProvider. First of all, I made some slight modifications to it so that it would work with DirectJNgine since I am working on the Java side. I have included my Ext.ux.state.DirectProvider changes. Basically, the Ext.Direct function doesn't accept the 3rd parameter 'this' which I assume keeps the scope for the callback function. Since I don't have the scope, I have to make a call to get the provider and I replace the 'this' with the variable I created that get's the provider.The changes can be seen in the following functions: saveState, saveCallback, readState, and readCallback


Ext.ns('Ext.ux.state');
Ext.ux.state.DirectProvider=function(config){
this.addEvents(
'readsuccess'
,'readfailure'
,'savesuccess'
,'savefailure'
);
Ext.ux.state.DirectProvider.superclass.constructor.call(this);
Ext.apply(this, config,{
delay:750,//buffer changes for 750 ms
dirty:false,
started:false,
autoStart:true,
autoRead:true,
readFn:Ext.emptyFn,
saveFn:Ext.emptyFn,
user:'user',
id:1,
session:'session',
logFailure:false,
logSuccess:false,
queue:[],
paramNames:{
id:'id',
name:'name',
value:'value',
user:'user',
session:'session',
data:'data'
}
});
if(this.autoRead){this.readState();}
this.dt=new Ext.util.DelayedTask(this.saveState,this);
if(this.autoStart){this.start();}
};
Ext.extend(Ext.ux.state.DirectProvider,Ext.state.Provider,{
saveSuccessText:'Save Success',
saveFailureText:'Save Failure',
readSuccessText:'Read Success',
readFailureText:'Read Failure',
dataErrorText:'Data Error',
clear:function(name){this.set(name,undefined);},
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:{};}
},
log:function(){
if(console){console.log.apply(console,arguments);}
},
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;
},
readCallback:function(response,e){
var provider=Ext.state.Manager.getProvider();
// handle success
if(true===e.status){
if(!Ext.isArray(response)&&true===provider.logFailure){
provider.log(provider.dataErrorText,response,e);
return;
}
Ext.each(response,function(item){
provider.state[item[provider.paramNames.name]]=provider.decodeValue(item[provider.paramNames.value]);
},provider);
provider.queue=[];
provider.dirty=false;
if(true===provider.logSuccess){provider.log(provider.readSuccessText,response,e);}
provider.fireEvent('readsuccess',provider);
}
// handle failure
else{
if(true===provider.logFailure){provider.log(provider.readFailureText,response,e);}
provider.fireEvent('readfailure',provider);
}
},
/**
* Reads saved state from server by sending asynchronous Ajax request and processing the response
*/
readState:function(){
var o={paramNames:this.paramNames}
o[this.paramNames.id]=this.id;
o[this.paramNames.user]=this.user;
o[this.paramNames.session]=this.session;
this.readFn(o,this.readCallback);
},
/**
* Direct call callback function
* @private
*/
saveCallback:function(result,e){
var provider=Ext.state.Manager.getProvider();
// handle success
if(true===e.status){
// get queue clone that has been sent to server as data
var queue=e.getTransaction().args[0][provider.paramNames.data];
// iterate through the sent queue and set local state
Ext.each(queue,function(item){
if(!item){return;}
var name=item[provider.paramNames.name];
var value=provider.decodeValue(item[provider.paramNames.value]);
if(undefined===value||null===value){Ext.ux.state.DirectProvider.superclass.clear.call(provider, name);}
else{Ext.ux.state.DirectProvider.superclass.set.call(provider,name,value);}
},provider);
// nothing to do if we've not got dirty during the server roundtrip
if(false===provider.dirty){provider.queue=[];}
else{
var i,j,found;
for(i=0;i<queue.length;i++){
found=false;
for(j=0;j<provider.queue.length;j++){
if(queue[i].name===provider.queue[j].name){found=true;break;}
}
// remove equal items, i.e. items already saved
if(true===found&&provider.encodeValue(queue[i].value)===provider.encodeValue(this.queue[j].value)){provider.queue.remove(provider.queue[j]);}
}
}
if(true===provider.logSuccess){provider.log(provider.saveSuccessText,response,e);}
provider.fireEvent('savesuccess',provider);
}
// handle failure
else{
if(true===provider.logFailure){provider.log(provider.saveFailureText,response,e);}
provider.dirty=true;
provider.fireEvent('savefailure',provider);
}
},
/**
* private, submits state to server by asynchronous Ajax request
*/
saveState:function(){
if(!this.dirty){
this.dt.delay(this.delay);
return;
}
this.dt.cancel();
var queueClone=Ext.ux.util.clone(this.queue);
var o={paramNames:this.paramNames}
o[this.paramNames.id]=this.id;
o[this.paramNames.user]=this.user;
o[this.paramNames.session]=this.session;
o[this.paramNames.data]=queueClone;
// be optimistic
this.dirty=false;
// call direct function
this.saveFn(o,this.saveCallback);
},
/**
* 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);
},
/**
* Starts submitting state changes to server
*/
start:function(){
this.dt.delay(this.delay);
this.started=true;
},
/**
* Stops submitting state changes
*/
stop:function(){
this.dt.cancel();
this.started=false;
}
});


// State-Handler
Ext.state.Manager.setProvider(new Ext.ux.state.DirectProvider({
user : 'user',
readFn : Ext.app.State.read,
saveFn : Ext.app.State.save,
autoRead: false
}));
OK, so anyway, I've been doing a lot of reading in the forums. I have gotten the saving working, and when I make a call to Ext.state.Manager.getProvider().initState([...]); and manually put in an initial state, everything works fine. My problem is how do I get the readFn of the provider to read my settings from the database? I came across this information on your forums: http://www.extjs.com/forum/showthread.php?t=61482. They are creating a dynamic JavaScript page to fill in the initState information. Ok, I can do this and this will work.

So then my new question becomes what is the purpose of the readFn of the Direct Provider? Is the readFn suppose to do this for me? Or do I just create a dynamic JavaScript page like the user in the above forum link did?

Thanks for this! I was just trying to add this extension using my DirectJNgine provider. Also, thank you for moving the ,'s where they should be. ;)

I will try mine and see what results I get.

chrizmaster
31 May 2010, 5:30 AM
Dude,
as you mentioned, you also work with DirectJNgine. Could you provide your save function (or part of it) here? Im struggling with the parameter which I should set to my savefn.
As I looked at firebug, I saw that saki calls my savefn ath the server with a big bunch of parameters. (one big object with much stuff in it) As I tried to set a save method at the server like this:



public void saveStat(HashMap map)...

I got some exceptions so Im unsure which is the right parameter-type...
Chriz

chrizmaster
31 May 2010, 5:56 AM
Hi,
you've a bug in your code. The variable "response" is not available it you set logSuccess and logFailure to true...





Ext.ns('Ext.ux.state');
Ext.ux.state.DirectProvider=function(config){
this.addEvents(
'readsuccess'
,'readfailure'
,'savesuccess'
,'savefailure'
);
Ext.ux.state.DirectProvider.superclass.constructor.call(this);
Ext.apply(this, config,{
delay:750,//buffer changes for 750 ms
dirty:false,
started:false,
autoStart:true,
autoRead:true,
readFn:Ext.emptyFn,
saveFn:Ext.emptyFn,
user:'user',
id:1,
session:'session',
logFailure:false,
logSuccess:false,
queue:[],
paramNames:{
id:'id',
name:'name',
value:'value',
user:'user',
session:'session',
data:'data'
}
});
if(this.autoRead){this.readState();}
this.dt=new Ext.util.DelayedTask(this.saveState,this);
if(this.autoStart){this.start();}
};
Ext.extend(Ext.ux.state.DirectProvider,Ext.state.Provider,{
saveSuccessText:'Save Success',
saveFailureText:'Save Failure',
readSuccessText:'Read Success',
readFailureText:'Read Failure',
dataErrorText:'Data Error',
clear:function(name){this.set(name,undefined);},
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:{};}
},
log:function(){
if(console){console.log.apply(console,arguments);}
},
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;
},
readCallback:function(response,e){
var provider=Ext.state.Manager.getProvider();
// handle success
if(true===e.status){
if(!Ext.isArray(response)&&true===provider.logFailure){
provider.log(provider.dataErrorText,response,e);
return;
}
Ext.each(response,function(item){
provider.state[item[provider.paramNames.name]]=provider.decodeValue(item[provider.paramNames.value]);
},provider);
provider.queue=[];
provider.dirty=false;
if(true===provider.logSuccess){provider.log(provider.readSuccessText,response,e);}
provider.fireEvent('readsuccess',provider);
}
// handle failure
else{
if(true===provider.logFailure){provider.log(provider.readFailureText,response,e);}
provider.fireEvent('readfailure',provider);
}
},
/**
* Reads saved state from server by sending asynchronous Ajax request and processing the response
*/
readState:function(){
var o={paramNames:this.paramNames}
o[this.paramNames.id]=this.id;
o[this.paramNames.user]=this.user;
o[this.paramNames.session]=this.session;
this.readFn(o,this.readCallback);
},
/**
* Direct call callback function
* @private
*/
saveCallback:function(result,e){
var provider=Ext.state.Manager.getProvider();
// handle success
if(true===e.status){
// get queue clone that has been sent to server as data
var queue=e.getTransaction().args[0][provider.paramNames.data];
// iterate through the sent queue and set local state
Ext.each(queue,function(item){
if(!item){return;}
var name=item[provider.paramNames.name];
var value=provider.decodeValue(item[provider.paramNames.value]);
if(undefined===value||null===value){Ext.ux.state.DirectProvider.superclass.clear.call(provider, name);}
else{Ext.ux.state.DirectProvider.superclass.set.call(provider,name,value);}
},provider);
// nothing to do if we've not got dirty during the server roundtrip
if(false===provider.dirty){provider.queue=[];}
else{
var i,j,found;
for(i=0;i<queue.length;i++){
found=false;
for(j=0;j<provider.queue.length;j++){
if(queue[i].name===provider.queue[j].name){found=true;break;}
}
// remove equal items, i.e. items already saved
if(true===found&&provider.encodeValue(queue[i].value)===provider.encodeValue(this.queue[j].value)){provider.queue.remove(provider.queue[j]);}
}
}
if(true===provider.logSuccess){provider.log(provider.saveSuccessText,response,e);}
provider.fireEvent('savesuccess',provider);
}
// handle failure
else{
if(true===provider.logFailure){provider.log(provider.saveFailureText,response,e);}
provider.dirty=true;
provider.fireEvent('savefailure',provider);
}
},
/**
* private, submits state to server by asynchronous Ajax request
*/
saveState:function(){
if(!this.dirty){
this.dt.delay(this.delay);
return;
}
this.dt.cancel();
var queueClone=Ext.ux.util.clone(this.queue);
var o={paramNames:this.paramNames}
o[this.paramNames.id]=this.id;
o[this.paramNames.user]=this.user;
o[this.paramNames.session]=this.session;
o[this.paramNames.data]=queueClone;
// be optimistic
this.dirty=false;
// call direct function
this.saveFn(o,this.saveCallback);
},
/**
* 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);
},
/**
* Starts submitting state changes to server
*/
start:function(){
this.dt.delay(this.delay);
this.started=true;
},
/**
* Stops submitting state changes
*/
stop:function(){
this.dt.cancel();
this.started=false;
}
});


// State-Handler
Ext.state.Manager.setProvider(new Ext.ux.state.DirectProvider({
user : 'user',
readFn : Ext.app.State.read,
saveFn : Ext.app.State.save,
autoRead: false
}));

SMMJ_Dev
31 May 2010, 5:07 PM
Lol, yeah it's not actually my code. I just did a little bit of rewriting to existing code to get the server calls working. I've never looked at the logFailure or logSuccess stuff or the response. I've actually just did a little bit of changes in order to get anything close to working for me. It was just a test case so that I knew I could do the ExtDirect State Provider with java. In a few months I will probably be going more in depth into the code and I can make more updates if needed during the process.

Anyways, here's my example that I did below. Our company will eventually use an Oracle database to save all the settings in, but for my test example I'm using database objects (db40):



package com.CompanyName.direct;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Iterator;
import com.db4o.Db4oEmbedded;
import com.db4o.ObjectContainer;
import com.db4o.ObjectSet;
import com.db4o.query.Query;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.softwarementors.extjs.djn.config.annotations.DirectAction;
import com.softwarementors.extjs.djn.config.annotations.DirectMethod;
@DirectAction(action="StateProvider")
public class StateProvider{
public class StateObject{
String user="",session="";
private String name="",value="";
StateObject(){}
StateObject(String u,String s,String n,String v){
super();
this.user=u;
this.session=s;
this.name=n;
this.value=v;
}
}
@DirectMethod
public String read(String user){
String ret="{\"success\":false}";
ArrayList<StateObject> r=readState(user,null);
if(r!=null){
Gson gson=new GsonBuilder().create();
String json="{\"success\":true,\"data\":"+gson.toJson(r)+"}";
ret=json;
}
return ret;
}
@DirectMethod
public String save(JsonArray Data) throws UnsupportedEncodingException{
String sRet="";
String user=((JsonObject)Data.get(0)).get("user").toString().replace("\"","");
String session=((JsonObject)Data.get(0)).get("session").toString().replace("\"","");
JsonObject data=(JsonObject)((JsonArray)((JsonObject)Data.get(0)).get("data")).get(0);
String name=data.get("name").toString();
String value=data.get("value").toString();
StateObject so=new StateObject(user,session,name,value);
if(saveState(so)){sRet="{\"success\":true}";}
else{sRet="{\"success\":false}";}
return sRet;
}
@DirectMethod
public String viewStates(StateObject so){
ArrayList<StateObject> al=readState(so);
Gson gson=new GsonBuilder().setPrettyPrinting().create();
String json=gson.toJson(al);
return json;
}
@DirectMethod
public boolean deleteDBFile(String user){
String filename=user+".db4o";
File f=new File(filename);
if(!f.exists()){throw new IllegalArgumentException("Delete: no such file or directory: " + filename);}
if(!f.canWrite()){throw new IllegalArgumentException("Delete: write protected: " + filename);}
boolean success=f.delete();
return success;
}
private boolean saveState(StateObject so){
String filename=so.user+".db4o";
boolean ret=false;
ObjectContainer db=Db4oEmbedded.openFile(filename);
try{
Query query=db.query();
query.constrain(StateObject.class);
query.descend("name").constrain(so.name);
ObjectSet<StateObject> result=query.execute();
Iterator<StateObject> i=result.iterator();
StateObject found=null;
while(i.hasNext()){found=(StateObject)i.next();}
if(found!=null){db.delete(found);}
db.store(so);
ret=true;
}
catch(Exception e){ret=false;System.err.println(e.getMessage());}
finally{db.close();}
return ret;
}
private ArrayList<StateObject> readState(String user,String name){
String filename=user+".db4o";
ObjectContainer db=Db4oEmbedded.openFile(filename);
ArrayList<StateObject> al=new ArrayList<StateObject>();
try{
Query query=db.query();
query.constrain(StateObject.class);
query.descend("user").constrain(user);
if(name!=null){
query.descend("name").constrain(name);
}
ObjectSet<StateObject> result=query.execute();
Iterator<StateObject> i=result.iterator();
while(i.hasNext()){
al.add((StateObject)i.next());
}
}
finally{db.close();}
return al;
}
private ArrayList<StateObject> readState(StateObject so){
String filename=so.user+".db4o";
ObjectContainer db=Db4oEmbedded.openFile(filename);
ArrayList<StateObject> al=new ArrayList<StateObject>();
try{
Query query=db.query();
query.constrain(StateObject.class);
query.descend("user").constrain(so.user);
if(so.name!=null){
query.descend("name").constrain(so.name);
}
ObjectSet<StateObject> result=query.execute();
Iterator<StateObject> i=result.iterator();
while(i.hasNext()){
al.add((StateObject)i.next());
}
}
finally{db.close();}
return al;
}
}
DirectProviderDemo.html


<html>
<head>
<link rel="stylesheet" type="text/css" href="../ExtJS/resources/css/ext-all.css">
<script type="text/javascript" src="../ExtJS/adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="../ExtJS/ext-all.js"></script>
<script type="text/javascript" src="../djn/djn-remote-call-support.js"></script>
<script type="text/javascript" src="../ejn/ejn-assert.js"></script>
<script type="text/javascript" src="util.js"></script>
<script type="text/javascript" src="DirectProvider.js"></script>
<script type="text/javascript" src="Api.js"></script>
<script type="text/javascript" src="InitializeStates.jsp"></script>
<script type="text/javascript" src="stateprovider.js"></script>
</head>
<body></body>
</html>
InitializeStates.jsp


<%@page import="com.db4o.Db4oEmbedded,com.db4o.ObjectContainer,com.db4o.ObjectSet,com.db4o.query.Query"%>
<%@page import="java.util.ArrayList,java.util.Iterator,com.smmj.direct.StateProvider.StateObject"%>
<%@page import="com.google.gson.Gson,com.google.gson.GsonBuilder"%>
<%!
String user="",uSession="",js="";
%><%
user=request.getRemoteUser();
if(user==null){user="myuser";}
uSession=session.getId();
js=createJSInitState(user,uSession).toString();
%>
<%!
public StringBuffer createJSInitState(String user,String sId){
String InitialState=getInitialState(user);
StringBuffer javascript=new StringBuffer();
javascript.append("Ext.app.REMOTING_API.enableBuffer=0;");
javascript.append("var remotingProvider=Ext.Direct.addProvider(Ext.app.REMOTING_API);");
javascript.append("Ext.state.Manager.setProvider(new Ext.ux.state.DirectProvider({");
javascript.append(" readFn:StateProvider.read,");
javascript.append(" saveFn:StateProvider.save,");
javascript.append(" user:'"+user+"',");
javascript.append(" session:'"+sId+"',");
javascript.append(" autoRead:false,");
javascript.append(" autoSave:false,");
javascript.append("}));");
javascript.append("Ext.state.Manager.getProvider().initState(");
javascript.append(InitialState);
javascript.append(");");
return javascript;
}
public String getInitialState(String user){
String filename=user+".db4o";
ObjectContainer db=Db4oEmbedded.openFile(filename);
ArrayList<StateObject> al=new ArrayList<StateObject>();
try{
Query query=db.query();
query.constrain(StateObject.class);
query.descend("user").constrain(user);
ObjectSet<StateObject> result=query.execute();
Iterator<StateObject> i=result.iterator();
while(i.hasNext()){al.add((StateObject)i.next());}
}
finally{db.close();}
Gson gson=new GsonBuilder().setPrettyPrinting().create();
String json=gson.toJson(al).replace("\\\"","").replace("\n","");
return json;
}
%><%out.println(js);%>


stateprovider.js


Ext.BLANK_IMAGE_URL='/INNS_Direct/ExtJS/resources/images/default/s.gif';
Ext.onReady(function(){
this.win=new Ext.Window({
stateEvents:["bodyresize","enable","disable"],
getState:function(){
return {"width":this.getWidth(),"height":this.getHeight(),"disabled":this.disabled,"x":this.x,"y":this.y,"component":"window"};
},
layout:'fit',
title:'test',
width:440,
height:410,
autoScroll:true,
constrain:true,
stateful:true,
id:'window',
stateId:'window'
});
this.win.show();
});

SMMJ_Dev
31 May 2010, 5:18 PM
I haven't done much with the DirectJNgine and the state provider. This has been a shelved item for me right now since priorities at work have shifted. I hope my save function helps you at least though.

chrizmaster
31 May 2010, 11:13 PM
UHM!
I tried all plain Java-Types as param-types that I knew and that would make sense, but I didn't try JsonArray from the gson Package. Thanks for this!
What is currently not clear to me is why saki is sending an array to the server if something is saved. Didn't see any needs for that, cause could it really happen, that 2 components wants to save the status the same time and if yes, whats the problem with simply sending 2 requests?
Chriz

SMMJ_Dev
1 Jun 2010, 5:29 AM
Yeah it took me a little while until I figured it out.

Sesshomurai
1 Jun 2010, 5:48 PM
UHM!
I tried all plain Java-Types as param-types that I knew and that would make sense, but I didn't try JsonArray from the gson Package. Thanks for this!
What is currently not clear to me is why saki is sending an array to the server if something is saved. Didn't see any needs for that, cause could it really happen, that 2 components wants to save the status the same time and if yes, whats the problem with simply sending 2 requests?
Chriz

I think the way it works is the stateprovider attempts to save or load the state in 1 call, so that it becomes more or less atomic in nature. If you had 30 components trying to save state at the same time, it would be a waste. And then there's a chance for some to fail and some not, which could leave your app a mess. This is just my take on it all though.

jsakalos
2 Jun 2010, 2:02 AM
Just to explain how it works: This is buffering state provider what means that if any number of components tries to save state at the "same" time their requests are accepted and buffered (cached) locally. Then every predefined period (750ms by default) the state provider checks if its buffer (queue) is dirty and sends the save request to the server if yes.

Any problem with this logic?

SMMJ_Dev
3 Jun 2010, 9:25 PM
I haven't come across any problems. If I do though, I'll be sure to post any updates. After your explanation I just now understand what chrizmaster was asking. Yes, makes total since. I remember coming across the buffering. I just thought it was for if you are constantly resizing a window, for example, that it would basically wait until you have stopped resizing or for it to save it's state. Reducing the number of requests this way. I didn't realize it would batch multiple states from different components in a queue that made a request w/in a predefined period. sweet.