PDA

View Full Version : UpdateManager.prototype.update()



Animal
25 Oct 2006, 2:13 AM
Currently, when updateManager.prototype.update() executes, the callback function, and the onUpdate method are fired as soon as the renderer.render() function returns.

The DOM element in question may not have been updated by then.

I've changed the processSuccess function to defer the callback and onUpdate firing into a callback function which is passed to renderer.render().

The renderer passes this function on to UpdateManager.prototype.update() which I've changed to perform postprocessing on the updated element.

Usually, this just consists of calling the callback, but may process scripts if the flag is set.

The postprocessing waits until a special, additional element with a known ID is present in the element.

I think this works well, and makes the event and callback sequence more logical. Often, you do want to wait for the document to be complete before, say activating a TabPanelItem. I think it makes a good enhancement to the library - Jack, do you want to add it?



YAHOO.ext.UpdateManager.prototype.processSuccess = function(response){
this.transaction = null;

// The callback function, and onUpdate are invoked by the renderer.
this.renderer.render(this.el, response, this, function()
{
this.onUpdate.fireDirect(this.el, response);
if(typeof response.argument.callback == 'function'){
response.argument.callback(this.el, true);
}
}.createDelegate(this));
if(response.argument.form && response.argument.reset){
try{ // put in try/catch since some older FF releases had problems with this
response.argument.form.reset();
}catch(e){}
}
};

YAHOO.ext.UpdateManager.BasicRenderer.prototype.render = function(el, response, updateManager, callback){
// The callback function is called by Element.update() after the DOM has been updated
el.update(response.responseText, updateManager.loadScripts, callback);
};

YAHOO.ext.Element.prototype.update = function(html, loadScripts, callback)
{
var dom = this.dom;

// Id of additional element added at the end of the innerHTML so that we
// can wait for its presence
var waitForId = YAHOO.util.Dom.generateId();
var totalWait = 0;

var postProcessor = function(){
// If we've been waiting less than half a second, and renderer has not caught up, wait again
if ((totalWait < 500) && (dom.lastChild.id !== waitForId))
{
totalWait += 10;
setTimeout(postProcessor, 10);
return;
}

if (loadScripts)
{
var s = dom.getElementsByTagName("script");
var docHead = document.getElementsByTagName("head")[0];

// For browsers which discard scripts when inserting innerHTML, extract the scripts using a RegExp
if(s.length == 0){
var re = /(?:<script.*(?:src=[\"\'](.*)[\"\']).*>.*<\/script>)|(?:<script.*>([\S\s]*?)<\/script>)/ig; // assumes HTML well formed and then loop through it.
var match;
while(match = re.exec(html)){
var s0 = document.createElement("script");
if (match[1])
s0.src = match[1];
else if (match[2])
if (YAHOO.util.Dom.isSafari)
s0.innerHTML = match[2];
else
s0.text = match[2];
else
continue;
docHead.appendChild(s0);
}
}else {
for(var i = 0; i < s.length; i++){
var s0 = document.createElement("script");
s0.type = s[i].type;
if (s[i].text) {
if (YAHOO.util.Dom.isSafari)
s0.innerHTML = s[i].text;
else
s0.text = s[i].text;
} else {
s0.src = s[i].src;
}
docHead.appendChild(s0);
}
}
}

// Callback
if(typeof callback == 'function'){
callback(this.dom, true);
}
delete postProcessor;
};

dom.innerHTML = html + "<span style=\"display:none\" id=\"" + waitForId + "\"></span>";

// Post process - this waits for the renderer to render element 'waitForId'
postProcessor();
};

jack.slocum
25 Oct 2006, 7:11 AM
I think this type of logic is better suited for a different renderer with it's own set of events. Implementing it in the default mechanism seems like overkill for most updates.

Something else that might help with this is in the latest UpdateManager code I have updated it to use Observable. You could use a delayedListener for a similar result:

mgr.delayedListener(obj.myCallback, obj, true, 50); <-- delay 50 milliseconds

This isn't as fail-safe as waiting for a particular element, but in every instance I have used it it seems to work flawlessly.

As for my first suggestion, something like this would work:


Aspicio.UpdateRender = function(){
this.events = {
'update': new YAHOO.util.CustomEvent('update')
};
};

YAHOO.extendX(Aspicio.UpdateRender, YAHOO.ext.util.Observable, {
render: function .... <-- here you add your delay functionality and then fire your update event
});

Animal
1 Nov 2006, 1:15 AM
Can we revisit this issue?

I've just added some processing which really needs to execute after the updated Element has been fully rendered. It's setting up some complex stuff (A Toolbar with buttons that process stuff found in the Element) based on what is there.

It works fine with my overidden functions which I've been keeping in yui-ext-ext.js.

I really think there needs to be the capability to execute on completion of rendering. I think onUpdate should fire after rendering is complete.

Or perhaps there should be an onRender event, and update() only needs to apply the extra wait for that extra, added element if onUpdate has subscribers...

With more complex Ajax updates being applied, I can see the need to postprocess the updated element knowing for sure that it contains everythnig it is supposed to contain.

jack.slocum
1 Nov 2006, 1:59 AM
The standard way to do this is to add a callback in your code that is loaded. So say you load foo.js with an UpdateManager call, in foo.js add a call when it's done to your callback function. Would this work in your instance?

Animal
1 Nov 2006, 2:26 AM
Yes, it would execute at the right time.

But it depends which set of javascript code has the knowledge about what is going on overall. This is a problem I've come across a lot with Ajaxy stuff. Scripts running in different contexts not "knowing" about each other.

The loaded HTML element is usually a subsidiary item. It's the loading code which should know what it's loaded, and how to integrate that into the overall scheme of what's on the page.

In this case, I have two TabPanelItems in a tabset. The right one contains a grid. The left one can load some JSP - the source of which is dictated by the grid's HQL query.

I have solved it with my extensions, so I'm not having a problem - I just think it might come up for others in future so it's worth bearing this thread in mind in case it does.

jack.slocum
1 Nov 2006, 2:44 AM
What exactly is the problem? Are you waiting for a specific element or waiting for a component to initialize or .... ?

Animal
1 Nov 2006, 4:04 AM
I'm building a Toolbar depending on what's in the loaded element.

The loaded page doesn't know that it's having a toolbar made out of it.

It's a bit up in the air now because we haven't finalized this two-tabbed list design.

Currently, we have an entity maintenance JSP in the first tab, and a yu-ext Grid of an HQL query of those entities in the second. Double-clicking a row, loads the entity JSP for that instance (The entity uuid is element 0 in the data array in the DataModel, but "skipped" by the query methods that the Grid calls) into the first tab, and activates that tab. So it pulls in "/Country.jsp?id=4"

Then a Toolbar is created containing next, prev, save, delete etc. It creates the "save"/"delete" buttons by creating a closure which does a YAHOO.ext.Connect.setForm/asyncRequest on the first form element in the loaded code.

But this might change. I don't think it should be tied to a Grid of entities, and dbl-click loading the entity's maintenance page. It could be a Grid containing the results of any valid HQL query, and double-click should... erm.. do something with that row of the query - load something relevant into the first tab.