yepnope's complete callback question - yepnope

I am using a "2-step view", where I have a layout template (common for all pages) with yepnope's load (in the html head) of jQuery and some plugins. Something like:
yepnope(['/path/to/jquery.js', '/path/to/jquery.plugin-common.js']);
Sometimes I need another plugin, so within the inner template I do additional (in the html body):
yepnope('/path/to/jquery.plugin-additional.js');
Now that I need to do the actual js magic, can I safely do just:
yepnope({
complete: function(){...}
});
So, the questions are in fact two:
Is the complete callback fired upon the load completion of the global resources stack? So it's safe to "register" this complete callback anywhere assuming that all needed resources have been registered before?
Can I safely call yepnope just with the "complete" callback option? I mean, as long as I'm not "testing" anything and my resources have been registered already...
I have tried it and it worked, but I'm not fully aware if it's internals, so I just want to make sure that I'm not doing something wrong... Thanks in advance.
--
And one last thing. The manual under preload! says:
yepnope({
load: 'preload!jquery.1.5.0.js',
callback: function (url, result, key) {
window.jQuery; // undefined (but it's cached!);
}
});
Can you please explain what is this about? I am completely missing the point here...

I can help on the preload! question.
The idea of preload! is that yepnope will download the file but will not execute it.
It transfers the jQuery file, but it will still be undefined after callback is being called, as it was not injected as a script into the page.

In my opinion, you are doing this wrong. I'm surprised that it work, but maybe your scripts are loaded before the call of the "complete" function. I think you should do that:
yepnope({
load: ['/path/to/jquery.js', '/path/to/jquery.plugin-common.js']
callback: {
"jquery.js": function () {
console.log("jquery loaded!");
},
"jquery.plugin-common.js": function () {
console.log("plugin loaded!");
}
}
});
And for the additional plugin in the html body:
yepnope({
load: '/path/to/jquery.plugin-additional.js'
callback: function () {
$(document).ready(function(){
console.log("plugin-additional loaded!");
});
}
});
Of course, replace console.log() by your code related to each plugin that you can safely execute in this context.
for the last question, i can't say anything, because i didn't succeed to make preload! working, maybe it's buggy, maybe i didn't understand how it work...

Related

Can someone explain how Meteor.defer() works?

So... Meteor.defer(function(){ // stuff }) isn't in the docs:
https://github.com/meteor/meteor/issues/2176
But this links seems to say that it's simply equivalent to
Meteor.setTimeout(function(){ // stuff }, 0);
If that's the case, how does this do, um, anything? It's basically saying "wait for 0 ms and then run the function".
So... it runs the function instantly.
What am I missing here? Is this kind of like Tracker.afterFlush or something? Does it somehow wait for "things" (what kind of things?) to finish before running?
I see Meteor.defer() a lot on SO being used as a bit of a hack on added helper methods to run after the dom is (somewhat) loaded - basically to get the same effect as running code inside of a Template.foo.rendered method.
However, the main (and best) use of Meteor.defer is to run a task asynchronously.
Let's say we have an app where we are sending an email. On the server, it may take several seconds for that to process inside of a meteor method, slowing down your application drastically. However, if you wrap that process in a Meteor.defer the email processing won't block execution, the email still sends (when it gets a chance, not instantly), but everything runs much faster, since the code that follows isn't waiting. There is a great example lesson about deferring execution at Bulletproof Meteor.
You can actually get the same effect with a setTimeout(f,0) - if you have a slow function, you could wrap it in the setTimeout and the rest of the code will complete, and 'defer' the slow process in the timeout, so although it doesn't seem like it, setTimeout(f,0) does actually have a pretty useful purpose!
To see an example of this in action, here's a fiddle, open the console and watch where 'foo' logs.
I faced some issue in my project because of asynchronous callback. Inside onCreated i was making a server Meteor.call and set the response inside reactiveVar. And i was doing something inside onRendered with that reactiveVar. Every time reactiveVar was showing undefined.
So i used Meteor.defer(function(){...}) inside onRendered and it sloved my issue.
Here is some demo with and without using Meteor.defer()
Template.myTemplate.onCreated(function () {
var instance = this;
instance.myTemplateModel = new ReactiveDict();
Meteor.call('all-user', function(err, res){
if(res){
console.log('inside callback');
instance.myTemplateModel.set('users', res);
}
});
});
Template.myTemplate.onRendered(function () {
var instance = this
console.log('rendered start');
Meteor.defer(function(){
console.log(instance.myTemplateModel.get('users'));
});
console.log('render end');
});
Console:
/*Without Meteor.defer()*/ | /*With Meteor.defer()*/
render start | inside callback
undefined | render start
render end | render end
inside callback | [Object, Object, Object]

Correctly stopping an autorun() function when template is destroyed

I have a template that contains a chart, rendered using MorrisJS. The chart should update when the currentData session variable is changed, so I have made it a reactive data source with:
Template.chart.rendered = function() {
var template = this;
Deps.autorun(function(c) {
// Stop if the template is removed from the dom
// Q: Is this really right?
if(template.__component__.dom.parentNode() === null) {
c.stop();
return;
}
var results = Session.get('currentData');
// ... render a chart with `results` as the data
Morris.Bar({element: template.$(".chart-container"), data: results, ...});
});
};
Notice how I do a fairly horrid check for when to stop the autorun above. This was necessary because without this, when I navigate away from the page using the template (I'm using iron-router) to another page and back, I get warnings in the log like "Can't select in removed DomRange". I'm pretty sure this is happening because the template instance is removed, but the autorun is still running.
I feel like I'm doing something wrong here, though. Is there (a) a better place to put the autorun so that it doesn't have this problem or (b) a better way to stop the computation when the template instance is removed from the DOM?
I tried to find a way to do it with created and destroyed handlers, but I couldn't figure out how.
Tracker.autorun returns a handle that you can store as a template instance property, then call its stop method in the onDestroyed lifecycle event.
Template.chart.onRendered(function(){
this.computation = Tracker.autorun(function(){...});
});
Template.chart.onDestroyed(function(){
this.computation.stop();
});
EDIT 29-09-2014
In newer versions of Meteor (0.9 onward), there is a new autorun function available on template instances which provide simpler code to achieve the same result : no need to store and stop the computation manually, this is taken care of by the framework.
Template.chart.onRendered(function(){
this.autorun(function(){...});
});
With the new autorun
Template.chart.onRendered(function(){
this.autorun(function(computation){
...
computation.stop();
});
});
but with this autorun, when chart template is removed from the DOM it is removed automatically.
This is in Meteor documentation here:
The Computation is automatically stopped when the template is destroyed.

RequireJS loads a .js, but is inline code still async?

From what I have seen using a debugger, calling:
require(["menu/main-menu"], function(util) {
Will load the main-menu.js file, but the function is called before the global code in the required .js file is executed? Is this correct?
If so, what is the best way to have all that code executed before my function is called?
The problem I am trying to solve is I want the code in mani-menu.js to all be in a module. But I can't call any method in that module until the global code in there is executed which creates the module.
I can call a global method in there which then creates everything, but that then requires a global init() method in every .js file (each with a unique name).
What's the best way to handle all this?
Update: There's a more basic question here (maybe). In writing javascript (and I use Sencha Ext JS & TypeScript), I need to create my objects. So when I go to create say my main menu, I want to call a method in my main-menu.js file to get that Ext JS derived menu object I created.
I think all the code in main-menu.js should be in a namespace, including the method I call to get the menu object. Is that correct? In addition, the way most Ext JS code is set up is you have several Ext.define() calls as well as other variable instantiations, and then the function that takes all that, builds the full menu, and returns it. But that requires all that code has executed in main-menu.js before I call it.
Am I approaching this correctly? My experience to date is Java & C# and I may be trying to fit that model incorrectly to javascript.
Let's suppose menu/main-menu.js contains this:
define(function () {
// Module factory function
return [... whatever you want to expose...];
});
And your application does this:
require(["menu/main-menu"], function (util) {
// Require callback.
});
What happens is:
The require call loads menu/main-menu.js.
The define in menu/main-menu.js is executed.
The module factory function (the function passed to define) is executed.
The require callback is executed with the symbol util set to
what the factory function in menu/main-menu.js returned.
As for simulating namespaces, there are multiple ways to do it. For
instance, you can do it like this:
define(function () {
return {
foo: function () {},
bar: function () {},
[...]
};
});
This exports an object with two functions in it. You can then use it
like this:
require(["menu/main-menu"], function (util) {
util.foo();
util.bar();
});
RequireJS also supports a CommonJS-style of defining modules:
define(function (require, exports, module) {
exports.foo = function () {};
exports.bar = function () {};
[...]
});
This is functionally equivalent to the first way I defined the module
earlier: you get the same two functions and you use them in the same
way as I've shown above.
Unfortunately, I can't speak about Ext JS specifically because I don't
use it.

Queue.js with progress event

I want to load multiple files to use in D3.js. Queue.js seems to be a nice tool for that. Since d3.js supports more advanced XHR functionalities in v3, I want to load multiple files with Queue.js and show the loading progress, and abort loading of all files on error.
This is how you check the progress and how to use Queue.js: https://github.com/mbostock/d3/wiki/Upgrading-to-3.0
I don't know how to combine these pieces of code.
This is what I have until now.
JSFiddle
I think it is better that there would be a progress event handler on Queue.js, but I don't know how to implement this.
Example code:
queue()
.defer(d3.json, "file1.json") // https://api.github.com/repos/mbostock/d3")
.defer(d3.json, "file2.json")
.progress(function() { console.log(d3.event.loaded/d3.event.total; }) // or use argument?
.error(function(error) { this.abort(); console.log(error); })
.await(function(data) { console.log(data); });
The object returned by queue() in queue.js doesn't have the methods "progress" and "error". Here is a link to the source code: https://github.com/mbostock/queue/blob/master/queue.js.
As queue.js takes an xhr object and uses 'apply' to execute the function, the following workaround worked for me. It involves using the get() method of an xhr object to execute the function.
Sample code:
queue().defer(d3.json("file1.json")
.on("progress", function({console.log(d3.event.loaded);})
.get, /*First argument*/ "error")
.await(function (error, file1_data) {console.log(file1_data);});
Hope this helps.

How to delay AngularJS App Initialization?

I have some data that is being processed asynchronously in the background and want to delay the initialization of the entire AngularJS application until this finished.
BackgroundData.initialized is a Q promise, so something like this:
BackgroundData.initialized.then(AngularDoYoStuff)
The problem I run into is the home page's controller starts its initialization procedure, hits BackgroundData and either it has the wrong/no data.
What function can I wrap Angular's initialization in so, instead of just dom-ready, it waits for both dom-ready and BackgroundData.initialization?
UPDATE
I have gotten closer with the documentation on manual bootstrapping:
angular.element(document).ready ->
setupGA()
window.BackgroundData = new DataMachine()
BackgroundData.initialized.then ->
angular.bootstrap(document)
But when the controller files load (after this file), they are still getting initialized before BackgroundData is defined
UPDATE 2
Removing the ng-app directive in the HTML seems to have fixed the problem (since that was telling Angular to auto-init) but now it just ignores all of my angular.module calls
The problem was that I had left the ng-app directive in the html tag, which tells Angular to auto-initialize that scope. Removing it allowed my manual initialization to run correctly.
as Chris mentioned, it can be done with angular.bootstrap and not mentioning the ng-app:
<div id="appArea" ng-controller="someCtrl">
{{name}}
</div>
<script>
angular.module('someApp', [])
.controller('someCtrl', function($scope) {
$scope.name = "test name";
})
setTimeout(function() {
angular.bootstrap(document, ['someApp']);
}, 2000);
</script>
One way to approach this if you are using routes in your app is to have the app initialize but wait on defining routes until the data is available.
I.e. provide a user-friendly 'loading' message, and then load functionality after.
You are able to inject $route where required, and then call something like:
$route.routes["/RouteName/:param"] = {templateUrl:"template.html", reloadOnSearch:true, controller:"ControllerName"};
After that, call $route.reload() or $rootScope.apply() to refresh.
More information at https://groups.google.com/forum/?fromgroups=#!msg/angular/AokZpUhZ6mw/x2kPIN2VAC0J

Resources