Escaping from the $.ajax({ success: function() }) dungeon - javascript

So I have a common scenario where everything depends on AJAX responses, followed by possibly more AJAX responses.
What ends up happening is lots and lots of presentation (page-specific) code gets thrown inside the success() callback:
$.ajax({
...
success: function (response) {
// too much $('#something').html() crap goes in here!
}
});
What is the best practice for removing this "data access" code completely from the presentation code but maintaining the loading sequence?
I've used deferred calls like $.when().then() but that still seems sloppy to me. Is there anything better?
To make an answer even simpler, let's say I want to retrieve information about a Person object and segregate all that logic into its own area. Example:
note: this code will not work - I am aware of that
Person.js:
var Person = {
getByID: function(id) {
// ajax call to return a person object (format is arbitrary)
// { id: 12345, name: 'Joe Smith', email: 'joe#smith.com }
}
};
SomePage.html
var myID = 12345; // get ID from wherever
var person = Person.getByID(myID);
$('#person .name').html(person.name);
$('#person .email').html(person.email);
EDIT: My solution
Although many of the answers were helpful, I chose to pass callback functions that separate all the various pieces of logic from each other. Example code:
JS:
Person = {
get: function(params, callback) {
$.ajax({
url: '/person/get',
data: params,
success: callback
});
}
};
Pages = {
Person: {
render: function(person) {
// in reality I'm using templates, this is for simplicity
$('#person .name').html(person.name);
$('#person .email').html(person.email);
}
}
};
SomePage.html
$('#someElement').someEvent(function() {
var params = {
id: 12345
};
Person.get(params, Pages.Person.render);
}
I should also add I found this slide deck extremely informative:
http://speakerdeck.com/u/addyosmani/p/large-scale-javascript-application-architecture

You'll have to have something in the success callback, but you don't need presentation details there. The success call could simply call:
Person.render(attributes, element)
And the details would be in the render function.

As SLaks says, what you want to do is impossible in the asynchronous context of AJAX.
However, nothing speaks against keeping the majority of the code in separate objects that do all the grunt work. You would then call those objects and their methods from your success callbacks and pass them all the necessary data. That would ensure that your callbacks contain only the bare minimum of code.

It sounds to me that you may wish to introduce the Model-View-Controller design pattern into your application. In the simplest form the Model would be responsible for fetching the required data which is then passed through to the View to be rendered; the Controller's job is to process incoming requests, invoke the Model and shuffle the data into the View in the expected format.
As others have mentioned there are plenty of lightweight MVC frameworks out there for JavaScript; but you should be able get the basic concept up and running just by using jQuery and a templating engine (such as Mustache.js) so you can get some separation between Model (fetching the data and building a common object graph) and View (rendering a template and adding the rendered HTML to the DOM).
As for you proposed Person example, you could make use of jQuery's Deferred to allow Person.getById to return a Promise, eg:
var Person = {
getByID: function(id) {
var result;
// Return a Promise which we will resolve once the AJAX call completes.
return $.Deferred(function (dfd) {
$.ajax(...)
.done(function (response) {
// Parse the AJAX response into your app's model data.
result = {
id: response.id,
name: response.firstName,
email: response.email
};
// Resolve the Promise and supply the Person object.
dfd.resolve(person);
});
}).promise();
}
};
Your Controller can then invoke the Model and then supply the result to the View (this is where you could make use of templates).
Person.getByID(myID)
.done(function(person) {
$('#person .name').html(person.name);
$('#person .email').html(person.email);
});

You can slightly change your example to make it work asynchronously - still cleaner code than shoving everything in to the success callback function:
var myID = 12345; // get ID from wherever
Person.getByID(myID, function(person) {
$('#person .name').html(person.name);
$('#person .email').html(person.email);
});

I now know the correct answer is:
There are over 9000 libraries created specifically for this, a common problem known as AMD.
-TerryR
http://speakerdeck.com/u/addyosmani/p/large-scale-javascript-application-architecture

Related

Read json file as javascript variable on page load

I'm trying to read data from a json file into JavaScript so it can be used by other functions that are called when a user interacts with the page. I've tried, using jQuery and JS:
var products = null;
$.getJSON("products.json", function(data) {
products = data;
console.log(data);
});
console.log(products);
Which produces, as expected:
null
Array [ {…}, {…} ]
I understand this is because of the asynchronous nature of the jQuery code execution. I've read into it a bit, but I'm just struggling to wrap my head around how to re-structure my code (coming from pretty much exclusively Python).
This is how I'm seeing it:
var products = null;
$.getJSON("products.json", function(data) {
products = data;
console.log(data);
});
function do_stuff(){
// Access the attributes of data and do stuff
}
function do_more_stuff(){
// Do some more stuff with the data
}
I would probably have do_stuff() execute when the page body loads, and let's say do_more_stuff executes when the user selects something in a dropdown menu. Both need to access the data in products.json. I know there are many examples out there, but I'm just not getting it. How can I re-structure the code to actually work?
I believe you are looking for something like this:
function get_json(cb) {
$.getJSON("products.json", function(data) {
cb(data);
});
}
function cb(data) {
// do stuff here if you want
console.log(data)
}
get_json(cb)
Create a callback function cb (or call it do_stuff if you'd like). Pass a reference to that function to your async function (get_json). When the async operation is complete, call your callback function with the data you received.

How do I make sure a function will be called before another in a deeply modularized Backbone, Require.js and D3 app?

Here is my headache. I'm developing a modularized javascript app and everything seems pretty neat except for one detail: I just can figure out how to load my initialize function only after my models have been fetched from the server with this Backbone.collection.fetch() method.
Please have a look into this file and possibly the whole project if you will, I appreciate a lot your collaboration.
fetch has a success callback, as Reed Spool pointed out. Here's how you could use it:
var View = Backbone.View.extend({
initialize: function () {
// Preserve *this* in closures
var that = this;
this.collection.fetch({
success: function(data){
console.log(data);
that.render();
},
error: function(){
console.log('error');
}
});
},
render: function() {
//render whatever you need from your fetched collection
}
});
If I'm hearin you right, I think you need to use a "Success" callback function in fetch. See here #2 http://chilipepperdesign.com/2013/01/15/backbone-js-bind-callback-to-successful-model-fetch/
If it's decoupling function timing in modularized code you're worried about, as your question's header suggests, I suggest using Backbone's Events. See the Event Aggregator, or Pub-Sub design patterns.
The conventional way to get around this using Backbone, is to use events. I've based this off the various blog posts of MarionetteJS founder Derek Bailey, so I'll add references when I find the relevant ones later...
Your view would do this:
var view = Backbone.View.extend({
...
_actuallyInitialize: function () {
...
}
initialize: function (attr, options) {
...
this.listenTo(this.model, 'data:fetched', this._actuallyInitialize);
...
}
});
And the code that is fetching your model, would do this:
var jqXHR = model.fetch();
jqXHR.done((function (bindModel) {
return function (response, textStatus, jqXHR) {
...
bindModel.trigger('data:fetched');
};
}(model)));
This way, you maintain the high-level modular approach, decouple the code (any number of views can update themselves on fetch now), and maintain readability. Your Model does stuff to itself, and your View does stuff to itself. Pretty neat, in my opinion, and one of the patterns I love using in Backbone. Thanks for the Q.

Passing data into the render Backbone.js

is it possible to pass the questions variable into the view render?
Ive attempted calling this.render inside the success on the fetch however I got an error, presumably it's because this. is not at the correct scope.
app.AppView = Backbone.View.extend({
initialize: function(){
var inputs = new app.Form();
inputs.fetch({
success: function() {
var questions = inputs.get(0).toJSON().Questions;
app.validate = new Validate(questions);
app.validate.questions();
}, // End Success()
error: function(err){
console.log("Couldn't GET the service " + err);
}
}); // End Input.fetch()
this.render();
}, // End Initialize
render: function(){
el: $('#finder')
var template = _.template( $("#form_template").html(), {} );
this.$el.html(template);
}
The success callback is called with a different this object than your View instance.
The easiest way to fix it is to add something like this before you call inputs.fetch:
var self = this;
And then inside the success callback:
self.render();
I'm not quite sure what you're trying to achieve, but if your problem is calling render from the success callback, you have two options, Function#bind or assigning a self variable.
For more information about "self" variable, see var self = this? . An example:
var self = this;
inputs.fetch({
success: function () {
self.render();
},
...
});
You should probably do some reading on JavaScript scopes, for example "Effective Javascript" or search the topic ( for example this MDN article ) online to get a better idea what happens there.
For Function#bind(), see the MDN article about it. With Backbone I suggest you use Underscore/LoDash's _.bind instead though, to make sure it works even where Function#bind() is not supported.
As for more high-level concepts, the fetching operation looks like it belongs to the model or router level and instead you should assign the questions variable as the model of your view. Ideally views don't do data processing / fetching, they're just given a model that has the methods necessary to perform any data transformations you might need.
The views shouldn't even need to worry about where the model comes from, this is normally handled by a router in case of single page applications or some initialization code on the page.

Does javascript have the equivalent of `pthread_once`

I'm trying to initialize a variable in javascript (specifically, I want to use a remote template with the jQuery template plugin) and then have multiple asynchronous callbacks wait for it to be initialized before proceeding. What I really want is to be able to link to the remote template via a <script type="text/x-jquery-tmpl" src="/my/remote_template"> tag, but barring that I could get away with the javascript equivalent of a pthread_once.
Ideally, the api would look something like:
$.once(function_to_be_called_once, function_to_be_called_after_first)
And used like:
var remote_template = "";
function init_remote_template() {
remote_template = $.get( {
url: "/my/remote/template",
async: false
});
}
$.once(init_remote_template, function () {
// Add initial things using remote template.
});
And then later, elsewhere:
$.get({
url: "/something/that/requires/an/asynchronous/callback",
success: function () {
$.once(init_remote_template, function () {
// Do something using remote template.
}
}
});
Does such a thing exist?
Looks like jQuery's promises can help you out here:
var templatePromise = $.get({
url: "/my/remote/template"
});
templatePromise.done(function(template) {
// Add initial things using remote template.
});
and elsewhere you can do:
$.get({
url: "/something/that/requires/an/asynchronous/callback",
success: function () {
templatePromise.done(function(template) {
// Do more things using remote template.
});
}
});
Usually $.get (and $.ajax, etc) are used with success: and error: callbacks in the initial invocation, but they also return a promise object that acts just like a $.Deferred, documented here: http://api.jquery.com/category/deferred-object/ which allows you to do what you're asking. For error handling, you can use templatePromise.fail(...) or simply add error: ... to the initial $.get.
In general it's best to avoid synchronous AJAX calls because most browsers' interfaces will block while the HTTP request is being processed.
If I understand correctly, jQuery will do what you want of it by way of Deferreds/promises.
You can even generalise the remote template fetcher by
using a js plain object in which to cache any number of templates
renaming the function and passing a url to it, get_remote_template(url)
js :
var template_cache = {};//generalised template cache
//generalised template fetcher
function get_remote_template(url) {
var dfrd = $.Deferred();
if(!template_cache[url]) {
$.get(url).done(function(tpl) {
template_cache[url] = tpl; //we use the url as a key.
dfrd.resolve(tpl);
});
}
else {
dfrd.resolve(template_cache[url]);
}
return dfrd.promise();
}
Then :
var url1 = "/my/remote/template"; //the url of a particular template
get_remote_template(url1).done(function(tpl) {
// Add initial things using tpl.
});
And, earlier or later :
$.get({
url: "/something/that/requires/an/asynchronous/callback",
success: function(data) {
init_remote_template(url1).done(function (tpl) {
// Do something using tpl (and data).
});
}
});
Note how get_remote_template() returns a promise. If the requested template is already cached, the promise is returned ready-resolved. If the template is not yet in cache (ie. it needs to be downloaded from the server), then the promise will be resolved some short time later. Either way, the fact that a promise is returned allows a .done() command to be chained, and for the appropriate template to be accessed and used.
EDIT
Taking #SophieAlpert's points on board, this version caches the promise associated with a tpl rather than the tpl itself.
var template_cache = {};//generalised cache of promises associated with templates.
//generalised template fetcher
function get_remote_template(url) {
if(!template_cache[url]) {
template_cache[url] = $.get(url);
}
return template_cache[url];//this is a promise
}
This version of get_remote_template() would be used in the same way as above.

Best way to track initial mapping using KnockoutJs

I find that in my application I have the following pattern repeated a lot (see below code).
I have to call BindMyEvents(true) for the first load, and then BindMyEvents(false) for subsequent data retrieval.
It is lazily loaded in, so I don't want the data serialised into the HTML source. Is there a better way than having to pass in a boolean flag into my Bind() method? Is there a standard pattern to achieving this with knockout?
I was thinking should I just set viewAlertsModel.alerts = null inside the view model definition, then let the Bind function check this. If set to null then call the mapping method followed by the applyBindings()?
function BindMyEvents(initialMap) {
// get alerts, map them to UI, then run colorbox on each alert
$.getJSON("/Calendar/MyEvents/", {},
function (data) {
if ( initialMap ) {
// set-up mapping
viewAlertsModel.alerts = ko.mapping.fromJS(data);
ko.applyBindings(viewAlertsModel,$("#alertedEventsContainer")[0]);
} else {
// update
ko.mapping.fromJS(data, viewAlertsModel.alerts);
}
});
}
I would re-arrange your code for a different flow.
first - define you data once.
viewAlertsModel.alerts = ko.observable();
Second, bind your data
ko.applyBindings(viewAlertsModel,$("#alertedEventsContainer")[0]);
Third, now work with your data
$.getJSON("/Calendar/MyEvents/", {},
function (data) {
ko.mapping.fromJS(data, viewAlertsModel.alerts);
});
Steps one and two can be done during an initialization phase. The key here is first define viewAlertsModel.alerts as an observable.
Step three is your run-time code. Now, your initialization code is completely separate from your run-time code. This the more the normal knockout style.
edit
With regards to your comments about using ko-mapping, I use the following code
var tempVar = ko.mapping.fromJS(data);
viewAlertsModel.alerts(tempVar); // !important - do not use = operator.
This is the convention that most people use. Your use of ko-mapping is normally used for specialized situations.

Categories

Resources