Confused about require.js - javascript

I've been looking at require.js and I am a bit confused on how to use it for my widgets system.
My widgets system basically allows the user to choose which widget s/he wants to see which gets stored in the database.
So when the user loads the page on the next visit, s/he will see only the widgets s/he selected. In effect only loading the html, javascript needed for the displayed widgets instead of loading all the html and javascript for all the widgets and then hidding the stuff which is not needed.
What I am confused about is how to do this with require.js
Am I supposed to dynamically generate the require.js code below? If the user has saved widget_1, widget_5 and widget_70, I am assuming I need to dynamically generating javascript like so?
require(['widget_1','widget_5','widget_70'], function(w1, w5, w70){
// then do something here?
});
the require line, is that supposed to be dynamically generated based on an sql query?
So if they only select a single widget to save for next time viewing, they will only get:
require(['widget_90'], function(w90){
// then do something here?
});
Is that what I am supposed to be doing?

Always define path's to the libraries that your modules rely on too often, something like below:
<script data-main="scripts/main" src="scripts/require.js"></script>
File: scripts/main.js
require.config({
paths: {
jQuery_1_7_2: 'lib/jquery.1.7.2',
underscore: 'lib/underscore',
.....
.....
}
});
Note: You need to include libraries with-in a module and return as an object, something like below:
File: lib/jquery.1.7.2.js
define([ 'lib/jquery/jquery.1.7.2.min' ], function() {
return jQuery.noConflict(true);
});
Same approach can be followed for other libraries that does not follow modular approach [AMD spec]
Note: The above step of defining library modules can be avoided by using shim feature
Since your page is dynamic, you never know what modules will be loaded.
You can invoke multiple require() calls to load modules. Since requirejs load's scripts asynchronously, the below approach will not harm your page performance.
Module-1 with in HTML view
<html>..
<div id="module_1">
//contents of module 1
</div>
<script type="text/javascript">
require(["scripts/widgets/module_1"], function( module_1 ){
module_1.init();
})
</script>
..</html>
File: scripts/widgets/module_1.js
define(["jQuery_1_7_2","underscore"], function($, _){
// Module functionality goes here
return {
init: function(){
// Module initialiser
}
}
});
Module 2 with-in HTML view
<html>..
<div id="module_2">
//contents of module 2
</div>
<script type="text/javascript">
require(["scripts/widgets/module_2"], function( module_2 ){
module_2.init();
})
</script>
..</html>
File: scripts/widgets/module_2.js
define(["jQuery_1_7_2","underscore"], function($, _){
// Module functionality goes here
return {
init: function(){
// Module initialiser
}
}
});
If you do not mind initializing the module after DOM load, you could use controljs and change the MIME type of the script tags. I believe that would not make too much of difference on the page performance.

I assume:
you have defined path to widgets in require config somewhere
your widgets are sandboxed (can be run without dependencies)
your application can run without widgets
You could generate the array on the server and populate it to js (e.g. as inline script) and then publish some kind of app.ready event, like so:
<script>
require(/*array from the server*/, function(){
// publish application ready event
});
</script>

Related

Lazily Load CSS and JS

I have written this piece of JS and CSS loading code and I would like some advice on it. Anything some of the Javascript Gurus could possibly point out would be much appreciated. The code works, but I have not done extensive testing, because I am concerned about replacing functions in this manner.
A single javascript file containing JQuery as well as the below code will be included on all the pages. We write all the components in house and keep them very modular separated into their own folder with the corresponding JS and CSS. You can imagine starting to use for instance a dropown, dialog and a datepicker on one page would require us to add 6 includes and this quite frankly is annoying, because I want the dependencies to resolve automatically and using JSP includes could possibly make multiple calls to the same resources.
Below is the src to load a single datepicker lazily
;(function($){
//All Lazily loaded components go here
$.fn.datepicker = function(settings){
console.log("This should only be displayed once");
loadCSS("/res/component/datepicker/datepicker.css");
var elem = this;
return loadJS("/res/component/datepicker/datepicker.js",
function(){return elem.datepicker(settings)});//After Load Completion the $.fn.datepicker is replaced
//by the proper working implementation, execute it and return it so we maintain the chain
};
}(jQuery));
function loadCSS(absoluteUrl){
if(loadCSS[absoluteUrl])
return;//Css already loaded
$('<link>')
.appendTo('head')
.attr({type : 'text/css', rel : 'stylesheet'})
.attr('href', absoluteUrl);//Appending entire element doesn't load in IE, but setting the href in this manner does
loadCSS[absoluteUrl] = true;//Memoize
}
function loadJS(absoluteUrl, onComplete){
if(loadJS[absoluteUrl])
return;//Script already loaded
loadJS[absoluteUrl] = true;//Memoize
var result;
jQuery.ajax({
async : false,//Synchronized because we need to maintain the JQuery chain
type :'GET',
url : absoluteUrl,
dataType :'script',
success : function(){
result = onComplete();
}
});
return result;
}
Have you looked in to Require JS, it will send async requests for only the modules you need for a given module.
In addition, because dependencies are scoped to the callback function, namespaces clashing is less of an issue
Typically you would have:
require(["jquery", "foo", "bar"], function($, foo, bar){...});
which allows your code to remain modularized both server side, and client side, in separate locations.
Of course, you need to set up require on your server with a config (described in the webpage), and wrap your resources in define blocks:
define("foo", ["jquery"], function($){...});
The downside is performance on pages that require many modules. In this situation you benefit more from having all resources in combined files, but note that query strings will cause the browser not to cache files in any case.. which is also another performance consideration.
Hope that helps
ps. In terms of CSS lazy loading, you could always use javascript to inject link tags into the head adhoc, and provide some javascript interface functions that your other code can call in order to request a CSS dependency dynamically.

Autoloading Javascript

I have a interesting concept I was working on and looking over, through various stack questions on auto loading JavaScript. I dint want to use a third party tool, aside form jquery, so I thought I would role my own. The concept I have is:
var scripts = {
'name' : 'path/to/script_dir/' // Load all scripts in this file.
}
requireScripts(scripts); // Requires all scripts
// Call your classes, methods, objects and so on ....
The requireScript() function would work something like:
function requireScript(hash){
$.each(hash, function(key, value)){
$.ajax({
url: value,
dataType: "script",
async: false,
error: function () {
throw new Error("Could not load script " + script);
}
});
});
}
Note: The above is just a concept, I don't think it will work.
The above would let you load SPECIFIC scripts. so in essence your hash key value would be 'name' : 'path/to/specific/script'. The issue this posses is that your hash would get rather large ....
The other issue I ran into is what if I simplified this to "php pear naming standard" so, as the trend seems to be - we would create a class, and it would be named after its location:
var some_folder_name_class = function(){}
Would be translated by the autoloader as: some/folder/name/class.js and then loaded that way.
To wrap up and get to my point there are two ways of loading javascript file I am looking at, via rolling my own "require" method. One is loading a directory of javascript files via the hash idea implemented above. (the provided code sample of how this hash would be walked through would have to be changed and fixed....I dont think it works to even load a single file)
OR
to have you just do:
new some_class_name() and have a global function listen for the new word, go find the file your trying to call based on the name of the class and load it, this you never have to worry - as long as you follow "pear naming standards" in both class and folder structure your js file will be loaded.
Can either approach be done? or am I dreaming to big?
I see a lot of frameworks do a bunch of require('/path/to/script') and if I could role my own autoloader to just allow me to either load a directory of js files or even have it where it listens for new before a class instantiation then I could make my life SO MUCH easier.
Have you consider using requirejs and probably Lazy loading.
http://www.joezimjs.com/javascript/lazy-loading-javascript-with-requirejs/
Here is sample version:
You can download here.
The sample is based on this folder structure :
public
index.html
scripts
app.js
lib
** jquery-1.10.2.js
** require.js
3 . From Code:
html
`<!DOCTYPE html><html>
<head><title>Sample Test</title>`
<script src="scripts/lib/require.js"></script> <!-- downloaded from link provide above-->
<script src="scripts/app.js"></script></head>
`<body><h1>My Sample Project</h1><div id="someDiv"></div></body></html>`
application configuration app.js
requirejs.config({
baseUrl: 'scripts',
paths: {
app: 'app',
jquery: 'lib/jquery-1.10.2' //your libraries/modules definitions
}
});
// Start the main app logic. loading jquery module
require(['jquery'], function ($) {
$(document).on('ready',function(){
$('#someDiv').html('Hello World');
});
});
jQuery-only option
If you are looking for a jQuery-only solution, have a look at jQuery.getScript(). It would be a great candidate for handling the script loading portion of your problem. You could then write a very small wrapper around it to load all the scripts—something like you wrote above:
var loadScripts = function(scripts) {
$.each(scripts, function(name, path) {
jQuery.getScript("/root/path/" + path + ".js");
})
}
If you are interested in more information on this approach, read this article by David Walsh.
Other great libraries
I strongly recommend taking a look at the current batch of script-loading libraries. I think that you will pleasantly surprised by what is out there. Plus, they come with the benefit of great community support and documentation. RequireJS seems to be the front runner but David Walsh has great articles on curl.js and LABjs.

How can I include javascript assets selectively in SailsJS?

In a Sails.js application, how can I include javascript assets selectively?
For instance, if I have an admin page and admin.js lives inside the assets/js directory. How do I keep the admin.js from loading on the public index page?
I'm aware that I could move the js out to the public directory, and include the script in my admin view's template. But I'm still unable to include it after the assets.js() call inserts it's javascript. I need it to be inserted after the sails.io.js script is loaded.
Is there any way to selectively load scripts and still have access to the sails.io.js which is automatically included with the assets.js() function call? Is there a better paradigm for this kind of situation?
EDIT:
Since the release of SailsJS 0.9 and the restructuring of the asset management system, this question doesn't really apply anymore.
Sailsjs uses asset-rack to serve /assets. With the default layout page, sailsjs serves pages that look like (dummy2.js is included with an explicit < script >):
<html>
<head>
...
<script type="text/javascript" src="/assets/mixins/sails.io-d338eee765373b5d77fdd78f29d47900.js"></script>
<script type="text/javascript" src="/assets/js/dummy0-1cdb8d87a92a2d5a02b910c6227b3ca4.js"></script>
<script type="text/javascript" src="/assets/js/dummy1-8c1b254452f6908d682b9b149eb55d7e.js"></script>
</head>
<body>
...
<script src="/public/dummy2.js"></script>
...
</body>
</html>
So sailsjs does not concatenate files (at least not in development mode). sails.io (socket-io) is always included before /assets/js in layout, and before < script > on the page.
It looks like your admin.js is expecting a condition which sails.io has not yet set, perhaps its negotiating a transport with the server? Try waiting for the condition to be set.
In a Sails.js application, how can I include javascript assets selectively?
I selectively load js assets using a wrapper around assets.js(). This snippet uses Jade's "-" and "!{...}". EJS would instead use "<%...%>" and "<%=...%>"
<!-- JavaScript and stylesheets from your assets folder are included here -->
!{assets.css()}
-function selectAssets (assetsjs, files, ifInclude) {
- return assetsjs.split('\n').reduce(function (prev, curr, i) {
- var frag = curr.match(/src="\/assets\/js\/\w{1,}\-/);
- if(frag) {
- var file = frag[0].substring(16, frag[0].length - 1);
- if ((files.indexOf(file) === -1) == ifInclude) { return prev; }
- }
- return prev + curr + '\n';
- }, '');
-}
//!{assets.js()} this line is replaced by:
!{selectAssets(assets.js(), ['dummy1', 'dummy5', 'dummy6'], false)}
The above would not include /assets/js/dummy1.js, dummy5, dummy6 with the layout. If you wanted to include dummy1 and dummy5 on a particular page, you would place
!{selectAssets(assets.js(), ['dummy1', 'dummy5'], true)}
on that page.
Note: The code assumes file name don't contain "-". Its straighforward to generalize for css and templates. sails-io would be a special case for mixins.
I know this is a old question but since people are still looking for this.
Sails has a folder called tasks in the root after you create a new project.
The file you are looking for is
pipeline.js
That file holds a variable called jsFilesToInject
// Client-side javascript files to inject in order
// (uses Grunt-style wildcard/glob/splat expressions)
var jsFilesToInject = [
// Load sails.io before everything else
// 'js/dependencies/sails.io.js',
'js/sails.io.js'
// Dependencies like jQuery, or Angular are brought in here
'js/dependencies/**/*.js',
// All of the rest of your client-side js files
// will be injected here in no particular order.
'js/**/*.js'
];
Just put your script that you want loaded before sails.io.js.
This is relevant for sails 0.11.x
another way that is valid is to create a views/partials folder
and create a new file like mapScripts.ejs
drop the script tags there and in your view use
<% include ../partials/mapScripts %>
Yes, you put a condition in template. :)
or add another "block" for your js.
extends ../layout
block body
<h1>hello</h1>
block jsscripts
<scripts> ..... </script>
To answer part of your question about where included files are located.
In 0.10 version order of the files is set in file tasks/values/injectedFiles.js as I recall in previous versions it was determined in Gruntfile.js itself.
You can add reference of your custom file in tasks/pipeline.js
var jsFilesToInject = [
'js/dependencies/sails.io.js',
'js/dependencies/**/*.js',
'js/**/*.js',
'js/yourcustomFile.js'
];

Loading JavaScript in order of dependencies with Require.js

I'm diving into Require.js to build a Google Maps application using this link and this link as a guide. My goal is to build a modular application that loads "base" functionality first, and then I can "plug-in" client-specific functionality - without duplicating code. So every project will use the "base" JS, but the client-specific JS will be different each project. Below is a list of my loading dependencies. This is the order I need things to load, with the previous item in the list needing to be fully loaded before moving onto the next:
Load jQuery and Google Maps API (I got this one working)
Load JavaScript to initialize my map on the page with base application functionality
Load additional/client-specific JavaScript.
I can get 1 and 2 to work just fine using this:
main.js:
require.config({
paths:{
jquery: "jquery-1.7.1.min",
jqueryui: "jquery-ui-1.8.22.custom.min",
async: "async",
requiremap: "requiremap"
}
});
require(
[ "jquery", "jqueryui", "requiremap" ],
function( $, jqueryui, requiremap ) {
}
);
requiremap.js:
define(
[ "async!http://maps.google.com/maps/api/js?sensor=false" ],
function() {
require(['js/basemapfunctionality.js'], function() {
});
}
);
But now I need to wait until #2 is completely loaded before loading #3. Is this possible with Require.js, and if so, how? (and if not, are there alternative frameworks that can do this) I tried adding another nested require method to load the additional functionality (illustrated below), but it acts like #2 hasn't loaded yet.
define(
[ "async!http://maps.google.com/maps/api/js?sensor=false" ],
function() {
require(['js/basemapfunctionality.js'], function() {
require(['js/additionalfunctionality.js'], function() {
// now everything should be loaded, but it ain't
});
});
}
);
Got this to work. Just had to break up the loading of Google Maps API, base functionality, and additional functionality into different modules and declare each in main.js.
main.js
require.config({
paths:{
jquery: "jquery-1.7.1.min",
jqueryui: "jquery-ui-1.8.22.custom.min",
async: "async",
requiremap: "requiremap",
basemapfunctionality: "basemapfunctionality",
additionalfunctionality: "additionalfunctionality"
}
});
require(
[ "jquery", "jqueryui", "requiremap", "basemapfunctionality", "additionalfunctionality" ],
function( $, jqueryui, requiremap, basemapfunctionality, additionalfunctionality ) {
}
);
requiremap.js
define([ "async!https://maps.google.com/maps/api/js?sensor=false" ], function() {});
basemapfunctionality.js
define(['requiremap'], function(requiremap) {
// basemap functionality here
}
additionalfunctionality.js
define(['requiremap', 'basemapfunctionality'], function(requiremap, basemapfunctionality) {
// additional functionality here
}
I'm not much into RequireJS, but this issue and the commentary implies a way to resolve your question.
Relevant quote from the link:
This is due to the interaction of $.extend and the order of execution of modules.
In a built file, the 'other/config' module is evaluated before the inner call to
require(['other/controller'], an since $.extend only mixes in properties, it causes the problem.
You can avoid it by using the Object.create() type of approach, where you create a new object whose
prototype is the BaseConfig, so that later, any properties you add to the BaseConfig will show up
automatically on that new object.
See Asynchronously Loading the API. There is a callback parameter where you can indicate a callback function that will be called when the API is fully loaded.
Did you try with Order.js plugin ?

Building a remote jQuery UI widget with requirejs dependencies

I've created a jQuery UI widget which is dependent on some other custom JavaScript modules. I fetch these modules using requirejs during the "_create" method of the widget. This actually works great if, I have my end consumers define my "data-main" property. However, in the situation where my consumers are using requirejs on their own, and defining their own "data-main" property, this doesn't work.
Since I'm using requirejs to inject scripts via my widget from a totally different server, I run into a few problems with requirejs's normal way of dealing with this.
First, I can't use a package.json file unless I assume that all of my consumers have a package.json which contains the exact same resources as I have. On top of that, I've got DEV, TEST and PROD server URLs to deal with.
Second I can't use require.config to set my baseUrl during a load on their server, cause it may break everything that they are using require for.
The current implementation I have working requires the consumer to add a script reference to require with my data-main location (external server). Then add a script ref to my widget (external server). This works because nobody else at my company has ever even heard of requirejs :). The second I start showing them how to bundle all of their code into reusable JavaScript modules my solution is broken.
I want to come up with a solution whereas the end consumer can simply reference my single JavaScript widget, which in turn loads everything it needs to function.
Any suggestions on how to do this? I've thought about hacking my own version of require with a static data-main, then just assume they can have multiple requirejs libs. I WOULD HATE TO DO THAT...but I can't really think of a better way.
Here is what I am going to do...
Couple of notes:
I'm using the jQuery UI widget factory pattern (but this isn't exactly a widget)
The widget code lives on a remote server and consumers only reference it, don't download it
I'm using requirejs to load widget dependencies
I want the greatest ease-of-use for the consuming developer
Since it's required that my jQuery UI widget be loaded ASAP so that the consumer has the context of the widget right away ( $(selector).mywidget ) I've decided to tackle my problem inside of the _create method.
This code basically installs requirejs if it doesn't exist, then uses it to install an array of requirements which the aforementioned widget needs to consume. This allows me to assume that the end user can reference my "widget" script by URL, tack on a "data-requiremodule" attribute of the same name, and get a complete list of remote dependencies.
_create: function () {
var widget = this;
widget._establish(widget, function () {
widget._install(widget);
});
},
_getBaseURL: function (scriptId, callback) {
var str = $('script[data-requiremodule="' + scriptId + '"]').attr('src');
if (callback) callback(str.substring(str.search(/scripts/i), 0));
},
_require: function (requirementAry, baseUrl, callback) {
require.config({ baseUrl: baseUrl });
require(requirementAry, function () {
if (callback) callback();
});
},
_establish: function (widget, callback) {
if (typeof require === 'undefined') {
widget._getBaseURL(widget._configurations.widgetName, function (baseUrl) {
var requireUrl = baseUrl + 'scripts/require.min.js';
baseUrl = baseUrl + 'scripts/';
$.getScript(requireUrl, function (data, textStatus) {
widget._require(widget._configurations.requiredLibs, baseUrl, function () {
callback(textStatus);
});
});
});
}
},
I'm not showing my "_configurations" object here...but you get the idea. I hope this helps someone else besides me :).

Categories