javascript asynchronous calls that populate some property - javascript

I have an javascript object that includes some image strings as its property. Upon instantiating this object, an AJAX call is made immediately to populate this property.
My view functions, at the same time, tries to read newly instantiated instances and tries to display this object. As expected, the AJAX call may not have finished by then yet, so it will not be able to read the right image url.
One solution to this problem is to pass in some call back function with the AJAX call that modifies the source of image upon completion, but this is introducing a lot of dependency on the model and the view and I'm trying to keep my MVC as separate as possible, so my view function right now just takes that object as an parameter, reads all the properties, and shows it.
Making the AJAX synchronous isn't an option because I have quite a lot of these objects being initialized and only one will be displayed at a time, so making all AJAX calls synchronous will be too expensive of a tradeoff.
What's a good way to solve this problem? The view function is something in the form of:
function displayObj(object) {
var prop1 = object.getProp1();
// this could be the image file that depends on AJAX result
var prop2 = object.getProp2();
}
Ideally, I would like to delegate this task to the getter, so that the view function doesn't have to worry about the interval state of the the object. The getter could handle this by checking whether the image is there yet, if not, wait, then only return if the actual image string is there. However, a blocking wait would for sure block the AJAX process so it's a deadlock, and non-blocking wait will let the getter return and the view function will get null data.
Please shed some light on this situation or suggest alternative ways that I can organize my code. Thank you very much!

I'd be a little bit less strict with the MVC roles and let the model object return the DOM <img> object.
// model object starts loading upon instantiation
var loader = new Loader();
// however the img tag is available immediately.
// the loader will update the src when ajax
// calls complete.
document.body.appendChild(loader.getImg());

Since, from your comments, you are using jQuery I recommend you call the done() method of the request returned from the ajax() method. For example,
var request = $.ajax("example.php");
request.done(notifyWhenSuccess);
request.fail(notifyWhenFailed);
This request returned by jQuery is a superset of the Promise pattern from CommonJS. Also, just because it is in jQuery don't cosider the ajax call part of the view but rather view it as part of the model. Use the done() method to update the model then notify the view with a data changed event.

Related

Jquery: Change HTML content of jqXHR from Get Request

I have a separate HTML file that I am loading in via $.get() and then turning into a Bootstrap modal dialog:
$.get("src/html/foobar.html", function (data) {
var md = $(data).modal({
backdrop:'static',
keyboard: false
});
});
However, before I turn this into a modal dialog and show it, I want to load in some content from the server. We know from jQuery's documentation that $.get() returns a jqXHR object. Is there a way to somehow change the content of the HTML returned in the data before I show it as a modal?
EDIT: I should be a little more specific and ask what operations can I perform on the object to edit its contents.
You could, of course, just modify data inline within your callback function before passing it to .modal
However a neater mechanism is to just chain a .then call from the Promise that is the jqXHR object:
$.get(url).then(function(data) {
// modify the data here
...
return modified_data;
}).then(function(data) {
// show data modally
});
NB: this assumes jQuery 1.8 or later, with the improved (i.e. corrected) semantics for .then
To avoid writing lots of inline functions, make the modifier function and the modal function separate named functions, then you can write:
$.get(url).then(modify).then(display);
Promises are the "new" way (since jQuery 1.5) to allow for separation of responsibility - as you can see from the example above the Promise chain allows you to completely detangle the three separate acts of retrieving, modifying and subsequently displaying the remote data.

Globally listen for Backbone.js Model.save

I'd like to append Backbone's native Model.save() method with a custom logging method that logs success and errors. I know that on a per model basis I can call something like:
myModel.save().success(function() {
// do something cool
});
But rather than adjusting every call to various models' save events, I'd like to simply listen for the save event on any model. One way that I think I want to avoid is actually modifying the Backbone.Model.prototype.save method (although if someone has an elegant way to do this I'm open to it).
Any thoughts on how to create such an event listener?
If all your models/collections are using the default Backbone.sync method, you could create a new sync method to do the logging.
var originalSync = Backbone.sync;
var loggingSync = function(method, model, options) {
// call original Backbone.sync
var promise = originalSync(method, model, options);
promise.done(function() {
// if method is 'update' or 'create', log success
});
promise.fail(function() {
// if method is 'update' or 'create', log failure
});
return promise;
};
Backbone.sync = loggingSync;
By default, Model.sync and Collection.sync both proxy through to Backbone.sync, so if you are using default sync, this change would take care of it.
var GlobalModel = Backbone.Model.extend({
save: function() {
}
});
myModel = GlobalModel.extend({
//..your model
})
If all you want to do is log AJAX success/failures, there's actually a much better way of doing it that doesn't even involve Backbone at all: $.ajaxComplete.
jQuery has this wonderful function called ajaxComplete that takes a function argument, and then runs that function every time a $.ajax call completes (failure or success). By using this method (ie. "binding an event handler to the ajaxComplete pseudo-event") you:
A) don't have to muck with Backbone internals
B) are guaranteed to catch EVERY AJAX request (even those made without Backbone)
C) keep this logging code completely separate from the rest of your code
There's probably some other upsides I'm missing, but you get the idea.
If on the other hand you truly do care only about save and not just about AJAX requests, then ... well you could still use $.ajaxComplete and just do some filtering inside it to ignore non-saves. But you could also make your own Model base class (as #salexch suggested) or worse you could muck with Backbone's internals, eg. by replacing Backbone.Model.prototype.save or Backbone.sync (as #Paul Hoenecke suggested), but then you're setting yourself up for trouble with future Backbone upgrades or 3rd party Backbone libraries you might want to use.
If you're interested, here's the doc page for ajaxComplete:
http://api.jquery.com/ajaxComplete/
Oh, and there's also some similarly named $.ajaxSomething methods (eg. ajaxError) for targeting only failures/successes/request starts/etc.

How can I get an asynchronous service call to update the view when it returns in AngularJS?

This is a follow up to my previous AngularJS question.
I am trying to replicate the functionality of the $resource service that is mentioned in this video tutorial: link.
In this tutorial, it is shown that you can update a scope variable and its view using an asynchronous call. First, here's the resource that's set up:
$scope.twitter = $resource('http://twitter.com/:action',
{action: 'search.json', q: 'angularjs', callback: 'JSON_CALLBACK'},
{get: {method: 'JSONP'}});
Then the get function is called on the resource to make the ajax call and update the necessary scope variable:
$scope.twitterResult = $scope.twitter.get();
What's happening here is that $scope.twitter.get() immediately returns an empty object reference. Then, when the ajax request returns, that object is updated with the data from Twitter. The view then updates all {{twitterResult}} instances and you see the data that was returned.
What I don't get is, when the object is updated in the asynchronous callback, how does the scope know that the value has changed?
I don't think it does - the callback must call some kind of update function on the scope, right? But if so, how is this done? I have heard of a function called $apply that might be the answer - but how do you reference $scope.$apply() from inside the resource?
I have created a JSFiddle to illustrate the problem: http://jsfiddle.net/Qcz5Y/10/
(Edit: Here is a newer JSFiddle with a different async call to illustrate the problem using an ajax call instead of setTimeout: http://jsfiddle.net/Qcz5Y/14/)
In this example, you will see a controller and a resource. The controller's scope has a property, fruit, which starts out set to an object. When you click 'Get new fruit', that property is set to fruitResource.get(), which immediately returns an empty object. It then makes an asynchronous call, and finally executes a callback which updates the value of the object that was originally returned.
When this happens, the value of $scope.fruit is properly updated to what was set in the callback. But it isn't reflected in the view.
If you click "Print current fruit", it will log to the console the current value of $scope.fruit. This was just for checking; it has the side-effect of updating the view.
I feel like I need to be doing something like $scope.$apply() at the bottom of the get_callback function (you'll see it in the code). Would that be the right thing to do? If so, how do I get a reference to $scope inside the fruitResource? I know I can probably pass $scope into fruitResource.get() as a parameter and then reference it that way, but the $resource.get() function in the example mentioned at the top doesn't need to do this, so I was hoping that AngularJS provides some way to get the scope from services automatically.
Thanks!
I figured out that you can pass $rootScope into a service and then call $apply on that. So when you want to update your view, you just do $rootScope.$apply().
Here is the updated JSFiddle: http://jsfiddle.net/Qcz5Y/11/
Actually simulating behavior of Resource is quite simple and boils down to doing async call and returning an empty results immediately. Then, when data do arrive from the server a returned reference needs to be updated. Here is the example using your use-case:
get: function() {
var fruit = {};
// Asynchronous call that executes a callback. Simulation of ajax/db request
$timeout(function() {
fruit.type = ['banana', 'strawberry', 'watermellon', 'grape'][Math.floor(Math.random() * 4)];
fruit.count = [1, 2, 3, 4][Math.floor(Math.random() * 4)];
}, 2000);
return fruit;
}
Here is the complete jsFiddle: http://jsfiddle.net/pkozlowski_opensource/sBgAT/1/
Please note that I'm using angular's $timeout service to simulate async call since $timeout will take care of calling $apply when it is needed and thus will trigger UI re-paint without any manual intervention. You were bumping into issues since you were trying to use setTimout that needs a call to $apply.

javascript - how to call a function newly added from ajax

I have a coding difficulty which have been asked in this forum before:
Calling a JavaScript function returned from an Ajax response
But I didn't find the answers quite satisfying. To be more precise of the problem I'm dealing, here is the detail:
I dynamically load a document (HTML and javascript) using jquery
var url = 'document.php';
$('#container').load(url);
Example of what the document.php looks like:
<form>
<input name="firstname"></input>
</form>
<script>
function dosomething()
{
console.log($('input[name=firstname]').val());
}
</script>
*The dosomething() function is the one I'd like to call later
And then I want to call the functions from that document.php. Due to my requirement, I don't want to call the function after the documents' loaded, but rather to call it later when I need it. Because it was dynamically loaded, the DOM doesn't recognize the functions. How to properly call this function?
Thank you
the DOM doesn't recognize the functions
This sounds like your other functions are wrapped in $(document).ready() in the remote page. If that is the case they are out of scope for you to call them from code in the main page and you need to move them out of the ready handler to make them globally accessible.
EDIT: Other possibilities
Script tags in head- move to body after html, or use $.getScript in ajax callback to retrieve
I think that you're trying to implement the technique called on-demand javascript (or lazy-loading). In other words, your page should initially load just a small script - but use a bunch of objects and functions, which are available in some other files, but will be loaded when they're required.
If that's the case, I have to warn you: you'll probably need to update your existing code. Instead of just calling some function right as it is, in all gun-blazing glory, you should check for its existence first - and if it's not available, wait for its loading:
if (typeof lazyObjects.someLazyFunction !== 'function') {
lazyLoad('lazyFunction.js');
}
lazyObjects.someLazyFunction();
The key point here is that lazyLoad should be synchronous. In other words, you'll have to wait until the script containing your function is actually loaded. Otherwise someLazyFunction just won't be defined when it's called, even with this sort of checks.
lazyFunction.js, in turn, will contain some code that will alter lazyObjects, adding to them the required method as a property:
// in lazyFunction.js
lazyObjects.someLazyFunction = function() { ... }
While it's technically possible to use global (=window) object for these cases, I usually don't do this - and won't recommend doing it either.
See, it's not that simple. ) I'd recommend reading this article to find out more about this technique - and actually using some established components to implement it in your code (some of them are mentioned in the linked article).

Pass a callback in ExternalInterface

I want to call a Javascript function from Flash, which I can do with ExternalInterface, but the Javascript function takes a callback. Is there a way to give it a Flash callback?
I've thought of something like this:
ExternalInterface.addCallback("foo", function(){...});
ExternalInterface.call("theFunction", "foo");
But that wouldn't work since theFunction would attempt to do foo(), while it should really do swfObject.foo(). The problem is the page and its Javascript are not under my control (though I can request changes if really needed).
This is closely related to the first question in the related questions section.
Along the same lines as the answer to that question, you can do:
ExternalInterface.addCallback("foo", function() { /* ... */ }); // The callback
ExternalInterface.call("theFunction(function() { swfObject.foo(); })");
You're misunderstanding the documentation, I think. callback in this instance is just a reference to a function inside Flash, not a callback to something you call.
Basically, you use .call() to call a JS function from AS; and you use .addCallback() to tell the Flash Player which AS function should be called based on the name.
On your example, theFunction would get one parameter as being 'foo', which is the name that references your anonymous AS function. Not sure why you would want to pass the function like that, but if you need, you could just call it from JavaScript with
function theFunction(callback) {
// .. do something...
swfObject[callback]();
}
Now, if you don't have control over the JS/HTML side, I'm not sure if you can do that. Not sure why you'd need, anyway - JS calls are synchronous, as if they were running on the same thread, meaning the Flash Player will execute the JS code and only then return to the Flash Player... you don't have to wait for execution or anything.
Also, if you really need to control the page without touching the JS/HTML side, remember you can inject entire pieces of JS code via .call - it doesn't need to be a simple function call. You can create your entire functions from inside the SWF. For example,
var js:XML = <script><![CDATA[
// Javascript code...
]]></script>;
ExternalInterface.call(js);
Or, if you need the return data, you don't need a callback either - just do a simple call as in
// JS
function isNumberZero(__num) {
return __num == 0;
}
// AS
trace ("Is number zero = " + ExternalInterface.call("isNumberZero", 10));
Not sure if this helps at all. If not, it'd be good to have more information on what exactly you're trying to do.

Categories

Resources