Ajax and Reflection
I am developing an ajax-based application and wondering, what role reflection plays or might play here?
Probably most importantly I am asking myself, if it would be a good approach to
handle all ajax responses through a single handler,
reflect or interpret the data or error
delegate further processing (e.g. where to inject the html) based upon the analysis.
Is this a budding procedure? What pros and cons come to mind?
Additional clearification
My current implementation, which I am not happy with, looks like this.
Register eventhandlers for user action, which lead to ajax requests.
For each request:
Determine which container is the target for the new content
Validate the ajax response
Pass the result to the appropiate rendering function if everything is as expected
Here is an example
function setGamedayScoringChangeHandlers() {
$("#community").delegate("div.community div.nav", "click", function() {
var orderId = $(this).html();
var communityId = $(this).closest('.communityView ').dashId();
requestGamedayScoringByOrderId(communityId, orderId);
});
}
function requestGamedayScoringByOrderId(communityId, orderId) {
var $targetContainer = $('#community-' + communityId + '-gameday');
$.ajax({
url: '?api=league&func=getGamedayScoringByCommunityIdAndOrderId',
data: {
communityId : communityId,
orderId : orderId
},
success: function(result) {
// custom indicator, that sth. didn't work as supposed
if (result.success === false) {
// a php error couldn't be handled as expected
if (result.error === 'phpRuntimeError') {
// ..
}
// ..
}
else {
renderGamedayScoring(result, $targetContainer);
}
}
});
}
Question
How can this and especially the redundant error checking be simplified? Could Reflection, in a sense of: "Is the response valid? And what does the error message say or data look like?" be a reasonable structure do deal with this? Additionally: Is the "coupling" of the actual ajax request and determing the $targetContainer a "normal" procedure?
Many thanks,
Robson
Yes I think register ajax handler trought one pipe is a good way, because it is more easy to control, you will have less redundant code and less boarding effects. If I look at your code comments it seems the response is not as you expect. I use to do like this for controling a group of ajax request talking with server script. I build one request object like :
// myscript.js
var rqPHP = {
url:'php/dispatcher.php', type:'POST', dataType:'json',
success:function(json, status, jXHR){
//console.log('rqPHP.succes : ', json);
if(!json) return console.warn('[rqPHP.success] json is null');
if(!json.cmd) return console.warn('[rqPHP.success] json.cmd is null');
if(!json.res) return console.warn('[rqPHP.success] json.res is null');
if(json.err && json.err.length){ console.warn('[rqPHP.success errors cmd:'+json.cmd+'] '+json.err);}
// so if no errors, dispatch actions based on original command asked
switch(json.cmd){
case 'loadfile' :
// do whatever with response
break;
case 'savefile' :
// do whatever with response
break;
}
},
error:function(jXHR, status, err){
console.warn('[rqPHP.error] ', status,',',err,',',jXHR.responseText);
}
};
then when use this object trought all my group of different actions and I precise wich action and arguments I pass. I use to ask for a json data so I am able to receive an easy parsing response, so I am able to return the original command asked, and some details on errors that may occured for example, and when I need to fire the request :
// myscript.js
rqPHP.data = {'cmd':'loadfile', 'filename':'file.dat', 'arg2':'other argument'};
$.ajax(rqPHP);
Then an example of one server script that will respond :
// dispatcher.php
$pv = $_POST;
$res = '';
$err = array();
// you check the command asked for :
switch(strtolower($pv['cmd'])){
case 'savefile' :
// do whatever
break;
case 'loadfile' :
// do whatever
if(any error){
$err[] = $loadError;// push error with whatever details you'll retrieve in javascript
}else{
$res = ',"res":"'.$dataLoaded.'"';// format json response so you'll check the var exist
}
break;
}
$jsonRes = '{"cmd":"'.$pv['cmd'].'"'.$res.',"err":"'.implode('|', $err).'"}';// json result
print $jsonRes;
They may be some errors, it is just for the principe, I hope that will help, just some last advices :
you should better use the requestObject.data to pass any arguments instead of setting the url like you did, this is much more easy because jQuery does the properly encoding work
you may use POST so the url stay clean, post vars are 'hidden'
in your case, because you may want to centralize server actions with ONE server script, you should use 'json' as dataType because it is much easier to retrieve details from the response, such errors. You have to distinct the ajax error that is trigger when the url doesn't exist, or access denied, well when the server replies it just can't respond to this request, and distinct the properly response of your server script, I mean the script responds well but it may occur an command error, for example for a 'loadfile' command, the argument fileUrl may be wrong or unreadable, so the action is done but the response will be not valid for you...
If you plan to fire many loads for differents parts (I mean you may don't wait response for an ajax before loading a new one), it should be better to set main success and errors functions for keeping centralization and then build one new request object each time you make a load
function rqSuccess(json, status, jXHR){
// put same checking code as before, then you can also retrieve some particular variables
// here, 'this' should correspond to the request object used for the $.ajax so :
console.log('myTarget is : ', this.myTarget, ' , myVariable is : ', this.myVariable);
}
function rqError(jXHR, status, err){
// put same checking code
}
// then each time you want make one or many independant calls, build a new request object
var myRq = {url:'dispatcher.php',type:'POST',dataType:'json',
success:rqSuccess,
error:rqError,
myTarget:$('#myblock'),// any variable you want to retrieve in response functions
myVariable:'Hello !',// after all it is an object, you can store anything you may need, just be carefull of reserved variables of the ajax object (see jQuery $.ajax doc)
// the data object is sanitized and sended to your server script, so put only variables it will need
data : {'cmd':'loadfile',...}
}
$.ajax(myRq);
// you may load an other independant one without waiting for the response of the first
var myRq2 = {...myTarget:$('#anotherblock'), data:{'cmd':'anotheraction'}...}
$.ajax(myRq2);
As a first step, you should change the error handling on the serverside to produce a non-OK/200 response for error cases, e.g. throw a 500. Then have that handled as an actual error on the clientside, along with other errors, instead of putting it through the success-callback.
That way you can use jQuery's abstractions for global error handling: http://api.jquery.com/ajaxError
Related
I set up a CasperJS script to call my web service (running on localhost:9000).
The webservice containing parameter which is needed to be filled. Let's say an amount parameter, and my webservice saves amount data from its parameter. So I wrote my CasperJS script like this:
casper.then(function(){
val = this.evaluate(function(){
//do step #1 ajax request
var country_amount = 9;
var params = "amount="+country_amount;
var data = "amount="+country_amount;
var wsurl = "http://localhost:9000/TempCountryAmountREST/setCountryAmount?amount="+country_amount;
//window.__utils__.echo("Country Amount :"+country_amount);
return JSON.parse(__utils__.sendAJAX(wsurl, "POST" , null, false, { contentType: "application/json" }));
});
});
As you can see, in the fourth parameter of __utils__.sendAJAX, I set it up with false, which means asynchronous = false. Well, everything goes well with async = false. That val variable successfully returning json data.
But when I changed false to true, it's coming up with a weird thing. Sometimes it succeeds to save the data (with my webservice), but val doesn't return proper value (it's null, but it should be returning json data). But saving data is still successful when I see on my phpmyadmin. But sometimes too (almost always happened), it fails to save the amount data, and still, returning null (not json data)
So what happened with this, is there a problem with use async request in CasperJS sendAJAX?
That is the expected behavior when an execution is asynchronous. Take for example the following code which is asynchronous by using the setTimeout function:
function sendAsync(){
var returnVal;
setTimeout(function(){
returnVal = sendSync();
}, 100);
return returnVal;
}
Since setTimeout is asynchronous sendSync is called or even finished after sendAsync finishes. You simply cannot return something based on an asynchronous call. See this question for more information.
__utils__.sendAJAX() doesn't provide a callback which could be used to get the returned data asynchronously. Since you want the data to be returned, you have to set the asynchronous argument to false.
__utils__.sendAJAX() with the asynchronous argument set to true can only be used for so called fire-and-forget requests where you are not interested in the data returned. Usually you're interested in the returned data.
The reason it sometimes fails to properly send and save the data is probably because this is an asynchronous process and your CasperJS script exits before the request is completed, thus terminating your request. By specifying async as true, you essentially make a tiny break off from the control flow.
This questions was about Backbone 0.9.2
Since upgrading to Backbone 0.9.10 we've chosen to override Backbone.sync instead and it works like a charm.
December 2012 - (v0.9.9) Backbone.wrapError has been removed.
February 2013 - It looks like WrapError will be brought back in the next version of Backbone, but overriding backbone.sync is the way to go.
(sorry for the long read)
I'm modifying the Backbone.wrapError function and I'm baffling by a line. I know what the line does, but not WHY it is necessary.
resp = model === originalModel ? resp : model;
resp ends up being the textStatus/errorType ie: "error" "timeout"
"parse error"
model is the XHR request object
originalModel is a reference to the Backbone.Model instance which ultimately called this function
I have a good grasp on what Backbone.wrapError does, what it returns and how it is used but I can't seem to understand the purpose of the above line.
Backbone's documentation states that wrapError will 'Wrap an optional error callback with a fallback error event,' which is true. Additionally, I've learned is that Backbone.wrapError is called 4 times in the library from within the fetch, save, destroy and reset functions in order to ensure that AJAX errors do not go unnoticed by the library. For example, if an AJAX error callback is passed into the fetch method it will be executed with a few parameters passed along, if not, the model will trigger an error event with the same few parameters passed along.
Sample call:
options.error = Backbone.wrapError(options.error, model, options);
Backbone.wrapError:
Backbone.wrapError = function(onError, originalModel, options) {
return function(model, resp) {
resp = model === originalModel ? resp : model;
if (onError) {
onError(originalModel, resp, options);
} else {
originalModel.trigger('error', originalModel, resp, options);
}
};
};
The problem that arises with this line (resp = model === originalModel ? resp : model;) is that model and resp correspond to the first 2 parameters within the jQuery/Zepto error callback parameter list. The first problem I have is with the naming of these parameters (model, response), because while debugging I've seen that those 2 parameters are jqXHR/xhr and textStatus/errorType. The textStatus/errorType parameters usually end up being "error" but (according to the docs) can also be "timeout" "parse error" etc. The comparison of model === originalModel makes no sense to me. A hard comparison on an XHR object and a Backbone.Model instance will always fail, and model will be stored into response (resp), which is fine because the model is actually the XHR response object... this line just seems pointless to me, but I went ahead and included it in my modified wrapError method.
Because model === originalModel always evaluates to false, the line seems to be an elaborate version of resp = model; which is useless, because you could just remove the line entirely and the model parameter could be passed into originalModel.trigger('error', originalModel, resp, options); instead of resp.
Is there any instance where model === originalModel could possibly evaluate to true?
Anybody with more experience in Backbone.js, AJAX have an answer/explanation of why this line is necessary?
TLDR/CLIFFS:
The weird little line below is used to determine weather the error callback was triggered from a failed validation at the model level, or from a failed AJAX call from the fetch, save, or destroy methods (which all call Backbone.sync). If the failure is from validation, it does not change the resp variable because resp should already hold useful information returned by validate (such as an errors array or a string about the error). If the failure is from a bad AJAX request, the XHR object is stored into resp because the XHR is the most informative item available. Unfortunately, the XHR is passed into this function as model and Backbone documentation fails to point out that this parameter does not always represent a model. Resp is meant to hold useful response information about the error(s), and is sent to the error callback or a thrown error event.
Okay. I learned some things about this weird line.
resp = model === originalModel ? resp : model;
In Backbone there are AJAX errors and Validation errors. Conveniently, Backbone funnels both errors into the same function -- the AJAX error callback. The problem is that the arguments passed into these functions are inconsistent. When there is an AJAX error an XHR object is available, but not during a validation error.
If there is no callback present, Backbone will throw and 'error' event with the same parameters that would have been passed into the error callback. (line 7 and 9 below).
After a successful AJAX request, your JSON data can be optionally passed through the model's validate function. In Backbone, the validate function should return false or nothing at all when there are no errors. When there ARE errors, it is typical to return an array such as ['invalid username', 'password too long', 'etc...'] Anything returned from validate (usually an error messages array) is passed into the 'wrapped' error callback as the resp parameter and the model itself is passed as model!
The _validate function is a bit sloppy and has multiple return statements, but when validation fails, line 9 is hit. Line 9 of the _validate function passes this (the model), error (returned from the models validate method), options (ajax options, success, error, timeout etc). This differs from an AJAX error which will pass in xhr (xmlhttprequest object), errorType ('error' 'timeout' 'parse error' etc), options (ajax options).
validate error: error(model, validate_return_value, options)
ajax error: error(xhr, errorType, options)
1 _validate: function(attrs, options) {
2 if (options.silent || !this.validate) return true;
3 attrs = _.extend({}, this.attributes, attrs);
4 var error = this.validate(attrs, options);
5 if (!error) return true;
6 if (options && options.error) {
7* options.error(this, error, options);
8 } else {
9 this.trigger('error', this, error, options);
10 }
11 return false;
12 }
The strange line of code above is necessary because, this one function handles errors from 2 different methods. AJAX and Validation. Those 2 send it different parameters, so this is meant to normalize them and throw events with consistent parameter lists.
When a validation error occurs, the model does not change so the model that is passed into the error callback is exactly equal to the originalModel. The purpose of the resp parameter is to hold information about the error itself. When there's an AJAX error, 'timeout' 'parse error' or 'error' are simply not as informative as the XHR object.
That weird little line determines weather the error callback was accessed from _validate or through a normal AJAX error such as a 404. When it is accessed from validate, resp is the value returned from validate. It should be informative, and useful data for the front-end templates to display. When the resulting error is from a HTTP error, the most useful information about that error is the XHR object which is passed into this function as the MODEL parameter.
A hopefully simplified approach to the wrapError and validate functions
Backbone.wrapError = function(ajax_error_callback, model_or_xhr, ajax_options) {
return function(model_or_xhr, error_info) {
if there was an ajax error, error_info = the xhr object
if there was a validation error, error_info = whatever was returned from validate
if there's an error callback {
run the error callback with (the original model, error_info, ajax_options) as parameters
if there is not an error callback
throw an event called 'error' with (the original model, error_info, ajax_options) as parameters
}
};
};
original:
Backbone.wrapError = function(onError, originalModel, options) {
return function(model, resp) {
resp = model === originalModel ? resp : model;
if (onError) {
* onError(originalModel, resp, options);
} else {
originalModel.trigger('error', originalModel, resp, options);
}
};
};
The * shows that error callback called from here
I have a simple ExtJs (3.4) Grid with a Writer. When the user makes some changes the store is saved to the server as follows:
store.on('save', afterSave(resp));
All is fine. However, I want to get a response as to wheather the record has been saved successfully, failed or an update conflict happed. How to best do this?
Are you using Ext.data.proxy.Ajax to load your stores? If so, you can use the reader property to evaluate and handle the server responses.
Another option would be to make AJAX called directly and handle the responses from there as well
I used exception listener to parse the data as suggested here. But, is this the right way to do this.
Ext.data.DataProxy.addListener('exception', function(proxy, type, action,
options, res) {
if (type == 'response') {
var success = Ext.util.JSON.decode(res.responseText).success;
if (success) {
console.log('UPDATE OK');
} else {
console.log('UPDATE FAILED');
}
}
});
I need to search the CouchDB based on several criteria entered in a form. Name, an array of Tags and so on. I would then need various views to index on these fields. Ultimately, all the results will be collated in data.js and provided to mustache.html. Say there are 3 views - docsByName, docsByTags, docsById.
What I don't know is, how to query all these views in query.js. Can this be done and how ?
Or should the approach be of that to write one view that makes multiple emits for each search somehow ?
Thank you.
From what you say I assume you are using Evently, so I will quote from Evently primer:
The async function is the main star, which in this case makes an Ajax request (but it can do anything it wants). Another important thing to note is that the first argument to the async function is a callback which you use to tell Evently when you are done with your asynchronous action. [...] Whatever you pass to the callback function then becomes the first item passed to the data function.
In short: put your Ajax requests in async.js.
As a side note: Evently is only one of the possible choices to write a couchapp and it is not clear if it is maintained. However it works and it is easy to rearrange the code to not use it.
EDIT: here is a sample async function (cut&paste from an old program):
function(cb, e) {
var app = $$(this).app
;
app.db.openDoc('SOMEDOCID', {
error: function(code, error, reason) {
alert("Error("+code+" "+error+"): "+reason);
}
, success: function(doc) {
app.view('SOMEVIEWNAME', {
include_docs: true
, error: function(code, error, reason) {
alert("Error("+code+" "+error+"): "+reason);
}
, success: function(resp) {
resp.doc = doc;
cb(resp);
}
});
}
});
}
I've build a livesearch with the jQuery.ajax() method. On every keyup events it receives new result data from the server.
The problem is, when I'm typing very fast, e.g. "foobar" and the GET request of "fooba" requires more time than the "foobar" request, the results of "fooba" are shown.
To handle this with the timeout parameter is impossible, I think.
Has anyone an idea how to solve this?
You can store and .abort() the last request when starting a new one, like this:
var curSearch;
$("#myInput").keyup(function() {
if(curSearch) curSearch.abort(); //cancel previous search
curSearch = $.ajax({ ...ajax options... }); //start a new one, save a reference
});
The $.ajax() method returns the XmlHttpRequest object, so just hang onto it, and when you start the next search, abort the previous one.
Assign a unique, incrementing ID to each request, and only show them in incrementing order. Something like this:
var counter = 0, lastCounter = 0;
function doAjax() {
++counter;
jQuery.ajax(url, function (result) {
if (counter < lastCounter)
return;
lastCounter = counter;
processResult(result);
});
}
You should only start the search when the user hasn't typed anything for a while (500ms or so). This would prevent the problem you're having.
An excellent jQuery plugin which does just that is delayedObserver:
http://code.google.com/p/jquery-utils/wiki/DelayedObserver
Make it so each cancels the last. That might be too much cancellation, but when typing slows, it will trigger.
That seems like an intense amount of traffic to send an ajax request for every KeyUp event. You should wait for the user to stop typing - presumably that they are done, for at least a few 100 milliseconds.
What I would do is this:
var ajaxTimeout;
function doAjax() {
//Your actual ajax request code
}
function keyUpHandler() {
if (ajaxTimeout !== undefined)
clearTimeout(ajaxTimeout);
ajaxTimeout = setTimeout(doAjax, 200);
}
You may have to play with the actual timeout time, but this way works very well and does not require any other plugins.
Edit:
If you need to pass in parameters, create an inline function (closure).
...
var fun = function() { doAjax(params...) };
ajaxTimeout = setTimeout(fun, 200);
You will want some kind of an ajax queue such as:
http://plugins.jquery.com/project/ajaxqueue
or http://www.protofunc.com/scripts/jquery/ajaxManager/
EDIT:Another option, study the Autocomplete plug-in code and emulate that.(there are several Autocomplete as well as the one in jquery UI
OR just implement the Autocomplete if that serves your needs