PDA

View Full Version : Scope and event handler hell



cwells
26 May 2007, 3:39 PM
I've enabled drag and drop on a Tree that post to a server-side function and then either allows the node to move or cancels it based on the server's response. The dragging and posting all work fine. The problem I have is getting the server's response back to the function responsible for canceling the drag (handleMoveNode needs to return false to cancel).



// move node.id to newParent.id
function handleMoveNode ( tree, node, oldParent, newParent, index ) {
var success = false;
var c = new Ext.data.Connection ( ).request ( {
url: '/account/moveaccount',
method: 'POST',
scope: this,
params: {
id: node.id,
parent: newParent.id
},
callback: function ( options, bSuccess, response ) {
try { var o = Ext.decode ( response.responseText ); }
catch ( e ) { return; }
if ( 'object' != typeof o ) { return; }

// this is where I need to return the value
// "up" a level to handleMoveNode's scope
// (and this doesn't work)
this.success = o.success;
}
} );

return success; // fails: always false
}

var tree = new Ext.tree.TreePanel (
'tree-div', {
animate: true,
loader: new Tree.TreeLoader ( { dataUrl: '/account/subaccounts' } ),
enableDD: true,
containerScroll: true
} );
tree.on ( 'beforemove', handleMoveNode, handleMoveNode, true );


Clearly I've got scoping issues, but I'm not sure how to resolve them.

Thanks for any help.

Konstantin
26 May 2007, 4:22 PM
According to the docs for Ext.data.Connection, the request() is asynchronous and returns immediately. Thus, handleMoveNode() will return value of 'success' way before the request is made. Making the call synchronously would make the UI feel sluggish at best. I would create some sort of undo() function, which would be called if the request fails.

Also, as jsakalos mentions below, the "this.success = o.success;" line is problematic, as "this" will refer to the outer space (the scope in which the handler was defined). Simply saying "success = o.success;" would suffice, I think, as the function you declare as callback, should capture the definition of "var success", and will thus have access to it.

jsakalos
26 May 2007, 4:25 PM
I've enabled drag and drop on a Tree that post to a server-side function and then either allows the node to move or cancels it based on the server's response. The dragging and posting all work fine. The problem I have is getting the server's response back to the function responsible for canceling the drag (handleMoveNode needs to return false to cancel).



// move node.id to newParent.id
function handleMoveNode ( tree, node, oldParent, newParent, index ) {
var success = false;
var c = new Ext.data.Connection ( ).request ( {
url: '/account/moveaccount',
method: 'POST',
scope: this,
params: {
id: node.id,
parent: newParent.id
},
callback: function ( options, bSuccess, response ) {
try { var o = Ext.decode ( response.responseText ); }
catch ( e ) { return; }
if ( 'object' != typeof o ) { return; }

// this is where I need to return the value
// "up" a level to handleMoveNode's scope
// (and this doesn't work)
this.success = o.success;
}
} );

return success; // fails: always false
}

var tree = new Ext.tree.TreePanel (
'tree-div', {
animate: true,
loader: new Tree.TreeLoader ( { dataUrl: '/account/subaccounts' } ),
enableDD: true,
containerScroll: true
} );
tree.on ( 'beforemove', handleMoveNode, handleMoveNode, true );
Clearly I've got scoping issues, but I'm not sure how to resolve them.

Thanks for any help.

Delete that bold red this and it should work. But, even if it does it's not all. You should clarify concept of scope and "this" variable for future.

There is one sticky thread up in the Help forum that might help.

Hi,

PS: Please change the icon of the thread as bulb usually means something resolved or general useful help information. Question mark would be fine.

efege
26 May 2007, 4:30 PM
tree.on ( 'beforemove', handleMoveNode, handleMoveNode, true );


It seems there's an error on the line above: the 3rd argument should be an object (the scope, i.e. the 'this' context for the handler function), but you are repeating the previous argument, i.e. a function.

jsakalos
26 May 2007, 4:53 PM
I've got somehow interested in the fourth "true" argument of the "on" call, I stepped into the ext-all-debug and it seems to me that setting it to true has no effect. The fourth argument is expected go be an object with options like "delay", "buffer", "single", etc.

Also I've tested to put the function itself as the third argument and FB hasn't given any error only the event handler runs in the scope of itself. Function is object too in javascript so putting it into third argument doesn't cause immediate errors.

When the event handler runs in the scope of itself it may but doesn't have to produce errors as it can be fully valid. It's unusual solution but might work.

Hope this helps.

Hi

cwells
26 May 2007, 6:33 PM
@Konstantin:
I think you've found the real core of my problem: my "scoping" concerns were masking the deeper issue of the procedure being async. Probably what I'll try next is having the callback utilize the oldParent argument to move the node back to where it was when an error occurs.

@jsaklos:
I know functions are first-class objects in JavaScript, so I assumed they would work as the third argument (and firebug didn't complain, seeming to confirm this belief). As far as the fourth "true" argument, I'd thought I'd seen that in the examples (but hadn't looked up what it meant :P).

Anyway, thanks everyone for the speedy help. I'd been pulling my remaining hair out for a bit over this.

cwells
26 May 2007, 9:16 PM
In case anyone is interested, here's the working code:



function handleMoveNode ( tree, node, oldParent, newParent, index ) {
var nextSibling = node.nextSibling;

// prevent other events while processing this one
Ext.util.Observable.capture ( tree, function ( ) { return false; } );

new Ext.data.Connection ( ).request ( {
url: '/account/moveaccount',
method: 'POST',
params: {
id: node.id,
parent: newParent.id
},
callback: function ( options, bSuccess, response ) {
try { var o = Ext.decode ( response.responseText ); }
catch ( e ) { return; }
if ( 'object' != typeof o ) { return; }
if ( !o.success ) {
// move the node back
if ( !nextSibling ) {
oldParent.appendChild ( node );
} else {
oldParent.insertBefore ( node, nextSibling );
}
}
// restore normal event handling
Ext.util.Observable.releaseCapture ( tree );
}
} );
}

var tree = new Ext.tree.TreePanel (
'tree-div', {
animate: true,
loader: new Ext.tree.TreeLoader ( { dataUrl: '/account/subaccounts' } ),
enableDD: true,
containerScroll: true
} );
tree.on ( 'beforemove', handleMoveNode );


One thing I found somewhat curious is that I wasn't able to use



tree.capture ( function ( ) { return false; } );


I thought that I should be able to do this since Tree is a subclass of Observable. Not a big deal but seems rather incongruous.

cwells
26 May 2007, 9:21 PM
Oops. Works when all is well, but there's a danger event handling won't be restored if an exception is thrown. This should be better:



// move node.id to newParent.id
function handleMoveNode ( tree, node, oldParent, newParent, index ) {
var nextSibling = node.nextSibling;

new Ext.data.Connection ( ).request ( {
url: '/account/moveaccount',
method: 'POST',
params: {
id: node.id,
parent: newParent.id
},
callback: function ( options, bSuccess, response ) {
try { var o = Ext.decode ( response.responseText ); }
catch ( e ) { return; }
if ( 'object' != typeof o ) { return; }
if ( !o.success ) {
// move the node back
Ext.util.Observable.capture ( tree, function ( ) { return false; } );
if ( !nextSibling ) {
oldParent.appendChild ( node );
} else {
oldParent.insertBefore ( node, nextSibling );
}
Ext.util.Observable.releaseCapture ( tree );
}
}
} );
}