Archive for 'AJAX'
One of my annoyances with Ext is sometimes some of the widgets only half do what you want and only half support features you’d expect.
Take the TreeLoader, responsible for loading nodes into a Tree. It works well most of the time and I don’t usually need to complain about it, until today. I needed to increase the time-out for the request only the TreeLoader exposes just two configuration options for the actual request, a requestMethod so you can switch between GET and POST and the actual url. Reading the documentation and looking through Firebug there seemed no way of doing what I wanted so I had to delve into the actual source code.
It turns out yet again I need to override a core function of the component, requestData and alter the AJAX request myself. Rather than do a complete override which would affect ALL TreeLoader’s I instead extended it, the full code can be seen below:
DCStorm.IQ.Profiles.TreeLoader = Ext.extend(Ext.tree.TreeLoader,
{
requestData : function(node, callback, scope){
if(this.fireEvent("beforeload", this, node, callback) !== false){
if(this.directFn){
var args = this.getParams(node);
args.push(this.processDirectResponse.createDelegate(this, [{callback: callback, node: node, scope: scope}], true));
this.directFn.apply(window, args);
}else{
this.transId = Ext.Ajax.request({
method:this.requestMethod,
url: this.dataUrl || this.url,
timeout: this.requestTimeout,
success: this.handleResponse,
failure: this.handleFailure,
scope: this,
argument: {callback: callback, node: node, scope: scope},
params: this.getParams(node)
});
}
}else{
// if the load is cancelled, make sure we notify
// the node that we are done
this.runCallback(callback, scope || node, []);
}
}
});
The only part I actually modified was the AJAX request, I simply added a timeout configuration option and had it pull the requestTimeout value, as can be seen on line 14. So in my new TreeLoader I can do this (line 5):
loader: new DCStorm.IQ.Profiles.TreeLoader({
preloadChildren: true,
clearOnLoad: true,
requestMethod: "POST",
requestTimeout: (60 * 2) * 1000,
dataUrl: "profiles/backend/profiles.ashx?action=columnstree",
baseParams: {
//action: "columnstree",
view: this.viewName,
level: this.profile.profile.level,
agency: ((this.profile.profile.level == 1) ? this.profile.profile.agency : 0),
site: ((this.profile.profile.level == 2) ? this.profile.profile.site : 0),
sitegroup: ((this.profile.profile.level == 1) ? this.profile.profile.sitegroup : 0),
type: "groupings",
filter: "",
selected: ""
}
}),
And that’s pretty much it.
One thing we’ve often found useful but underused is response caching, especially in the Web 2.0 world. There are a variety of options available and here’s a quick example of how to use the built in caching in ASP.NET.
For our example we’re going to cache for no more than 10 minutes, you can alter this as suits you. We’re also caching on an absolute time-scale but ASP.NET lets you also set a sliding time-scale.
public void ProcessRequest(HttpContext context)
{
// Set Content Type
context.Response.ContentType = "application/json";
// Set key
string key = "mydata";
// Check cache for our hash
object cache = context.Cache.Get(key);
if (cache != null) {
// Write out cached item
context.Response.Write("/* From Cache */\r\n");
context.Response.Write(cache.ToString());
context.Response.Flush();
// Return
return;
}
// Get JSON from database or wherever
string json = "{test:'Hello'}";
context.Response.Write(json);
context.Response.Flush();
// Add response to cache
context.Cache.Insert(
key,
json,
null,
DateTime.UtcNow.AddMinutes(10),
System.Web.Caching.Cache.NoSlidingExpiration
);
}
What happens is on the request we first check the cache for key. If we find that the cached item exists we retrieve this from the cache and dump that into the response.
If the item doesn’t exist however we generate the item content, be it a database call or whatever. Here this is mostly some kind of JSON so once we have the content we’d normally return we again dump that into the response but this time we also add it to the cache with the same key.
Caching is great for reducing load on databases and processing time, especially with commonly returned data such as dropdown lists who’s data rarely changes.
For further reading and a more fuller introduction to ASP.NET caching click here.
So today I toyed around with the idea of loading JavaScript files on-demand rather than all at once on page load. The idea is it will not slow down page loading and also you could make it conditional so you only load JavaScript if it’s needed, say if a user makes a certain choice.
My initial effort was:
function loadScript(url)
{
var el = document.createElement("script");
el.type = "text/javascript";
el.src = url;
document.getElementsByTagName("head")[0].appendChild(el);
}
Fairly simple, create a new <script> tag, set up its attributes then append it to the <head> tag. However when I came to run it with the following code it didn’t want to play ball:
<html>
<head>
<title>Test</title>
</head>
<body>
<script type="text/javacript" src="scriptloader.js"></script>
<script type="text/javascript">
alert("Start");
loadScript("otherscript.js");
// Call something in other script
DoOther();
alert("Stop");
</script>
</body>
</html>
I’m not sure why this didn’t work but I suspect it’s because the JavaScript file isn’t actually completely loaded by the time I call DoOther() which means DoOther() doesn’t exist yet. Thankfully trawling around online I found somebody else had solved this issue by using a callback and waiting for the script to become ready.
His code is:
/**
* function loadScript
* Copyright (C) 2006-2007 Dao Gottwald
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Contact information:
* Dao Gottwald <dao@design-noir.de>
*
* @version 1.6
* @url http://design-noir.de/webdev/JS/loadScript/
*/
function loadScript(url, callback) {
var f = arguments.callee;
if (!("queue" in f))
f.queue = {};
var queue = f.queue;
if (url in queue) { // script is already in the document
if (callback) {
if (queue[url]) // still loading
queue[url].push(callback);
else // loaded
callback();
}
return;
}
queue[url] = callback ? [callback] : [];
var script = document.createElement("script");
script.type = "text/javascript";
script.onload = script.onreadystatechange = function() {
if (script.readyState && script.readyState != "loaded" && script.readyState != "complete")
return;
script.onreadystatechange = script.onload = null;
while (queue[url].length)
queue[url].shift()();
queue[url] = null;
};
script.src = url;
document.getElementsByTagName("head")[0].appendChild(script);
}
As you can see this is way more extensive and complete than my paltry effort. Plugging this into my little example we have:
<html>
<head>
<title>Test</title>
</head>
<body>
<script type="text/javacript" src="scriptloader.js"></script>
<script type="text/javascript">
alert("Start");
loadScript("otherscript.js",function() {
// Call something in other script
DoOther();
});
alert("Stop");
</script>
</body>
</html>
Of course, it will get to alert(”Stop”) long before it calls DoOther() due to the asynchronous nature of the call but it will be able to call DoOther() now.
With Ext if you do not specify the HTTP request type then it will attempt to make this decision for you, the Ext documentation says that if the request has no parameters it will use GET otherwise it will use POST. When you’re starting out this is fine but later on you will soon find you want to change this, especially in relation to optimization, POST is a more expensive version than GET and GET is cachable.
You can easily adapt a standard Ext.Ajax request by specifying the request method like this:
Ext.Ajax.request({
method: "GET",
url: "http://www.example.com/",
...
});
Simple. The above code is nice for plain AJAX requests however for Ext.data.Store requests things are a little trickier, for that you need to specify a new proxy in the config for the store to use:
var store: new Ext.data.Store({
autoLoad: false,
proxy: new Ext.data.HttpProxy({
method: "GET",
url: "http://www.example.com/timezones.php"
}),
reader: new Ext.data.JsonReader(
{root: "list", totalProperty: "count"},
[{name: "timezone_id", type: "int"},{name: "timezone_name", type: "string"}]
)
});
Wouldn’t the store request using GET anyhow you ask? Yes it would. However, here comes the crunch, what if you added params? This is especially so when using for example the paging toolbar which passes it’s information as such.
Hopefully this will help you in your quest to write better web applications.
Note: There is a slight caveat to using GET over POST and that is because GET makes all the params part of the URL so anything that restricts URL length will not like obscenely long lists of params. In the naming and shaming game I single IE out here.