Calling methods in RequireJs modules from HTML elements such as onclick handlers - javascript

I'm changing a project from an "old" browser-style module structure to a "new" browser-or-server-side-javascript module structure with require.js.
On the client I'm using an offsite hosted jQuery, so I started from the example they give in the "use priority config" technique of the README:
<title>My Page</title>
<script src="scripts/require.js"></script>
<script>
require({
baseUrl: 'scripts',
paths: {
jquery: 'http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min',
jqueryui: ...,
...
... // bunch more paths here
},
priority: ['jquery']
}, [ 'main' ]);
</script>
This is actually working all right. But I'd like to export functionality from main to the HTML webpage itself. For instance:
<a class="button" href="#" onclick="MyApi.foo();">
<img src="foo.png" alt="foo" />Click for: <b>Foo!</b>
</a>
Before fitting into the AMD module pattern, I'd exposed functionality from my various files by creating a dictionary object in the global space:
// main.js
var MyApi = {};
jQuery(document).ready(function($) {
// ...unexported code goes here...
// export function via MyApi
MyApi.foo = function() {
alert("Foo!");
};
});
But I don't know what the right approach in require.js is. Is it okay to put in the HTML more require statements inside of <script> tags, and then name modules so that it can be used from within the webpage? Or should this always be done dynamically inside of main.js, like $('#foobutton').click(...)?

One benefit from using AMD is to drop the need for namespaces. Trying to create them again with RequireJS will go against the patterns AMD promotes.
With regard to using main.js, this is only really appropriate when you have a single page app and all your code be reference from one place. You're free to make additional calls to require and load other modules as you need them.
Using your example from above, you could approach it this way:
foo.js
define(['jquery'], function($) {
// Some set up
// code here
// Return module with methods
return {
bar: function() {
}
}
});
page.js
require(['foo'], function(foo) {
// jQuery loaded by foo module so free to use it
$('.button').on('click', function(e) {
foo.bar();
e.preventDefault();
});
});
Then in your page request the page.js file with require.
Using your example above:
require({
// config stuff here
}, [ 'page' ]);
Or loading it in the page further down:
<script>
require(['page']);
</script>
Some additional points
Using the pattern above, page.js could easily require many other
modules to load other page related functionality.
Where before you would attach members to your global namespace, you
now split that code into separate modules that can be re-used on
any page. All without reliance on a global object.
Using require this way to attach events to your DOM elements will
most likely rely on the DOM Ready module provided by RequireJS

You can also set the reference on javascript's window class.
At the bottom of the application module window.MyApi = MyApi;
<a class="button" href="#" onclick="MyApi.foo();"></a>

putting onclick="" inside your markup feels dirty -- mixing presentation with content. I'd recommend you add a <script> block and put the require in it, and once inside the callback, and inside another inner $(document).ready() if necessary then wire up your event handlers in the normal way: $('#node').click(...). If you can do this generically enough that it's applicable to multiple pages, then put it inside an external file that is minified, combined, and cached. But typically these things end up being page-specific, so a <script> tag at the bottom of the page is a good solution to avoid yet another external resource request.

Another solution is just use code like that (of course requirejs must be added to the page):
<input type="button" onclick="(function() { require('mymodule').do_work() })()"/>

Related

Using jQuery and RequireJS at start of processing

I often start my JavaScript apps like this:
jQuery(function($) {
... code for the app ...
});
I'm just starting to use RequireJS, and will start the app like this:
define(['jquery'], function($) {
... code for the app ...
});
Now, as I don't want the app to start processing until all the HTML has been loaded, I've combined the two like this:
require(['jquery'], function($) {
$(function($) {
... code for the app ...
});
});
Is that the way to do it?
The RequireJS documentation touches on this and offers has a slightly more convenient option:
require(['domReady!'], function (doc) {
//This function is called once the DOM is ready,
//notice the value for 'domReady!' is the current
//document.
});
Note that you will need to download the domReady plugin, and if you have a very complex page you may wish to use the "explicit function call" method that they also show... Although then it looks an awful lot like what you're already doing with jQuery.
So the main diferences between define and require in this scenario is, one declare a module and the second one call this defined module, then if it is not loaded, the browser download the js library.
To take control about when your require files will download, you need to use the domReady plugin.
You need to put the js library at you require.config, I usually put at the same directory as declared at the baseUrl property, for example:
require.config({
baseUrl: "js/lib",
paths:{
filter:"../src/filter",
addPanel: "../src/edit-panel"
}
}
I put the domReady.js at the js/lib/ folder.
So, then you can use the require method at any place of you html file:
require(['jquery!'], function ($) {
});
Note that I use the symbol ! to indicate that this library is load after the completely page load.
As the box at the top of the page, my question is answered here:
Requirejs domReady plugin vs Jquery $(document).ready()?
The other answers here essential repeat what's in the above link. But, thanks!

Simplest way to selectively activate generic jQuery/javascript modules for a specific page?

It's oft recommended optimization to consolidate your javascript into fewer parts and reduce the number http requests. On a smaller site you could easily arrive at structure like:
/js/jQuery-xx.js
/js/plugins.js
/js/app.js
Even in a modest site (with just some jQuery polish) that app.js can get pretty heavy. For organization it's easy to break apart the implementations into logcal bits:
..
/js/app-global.js
/js/app-contact.js
/js/app-products.js
/js/app-splash.js
Now as separate files it's trivial to require them for a specific context, though their individual sizes are quite small, around 1–3 kb a piece, so it occurs to me it'd be pretty smart to recombine them back into a single file simply served as app.js.
But it occurs to me that in app.js file with about 20 or so jQuery selectors and misc javascript that goes unused on most pages. Seems like wasted CPU cycles to have these jQuery selectors firing when they target elements that don't exist in most contexts.
Which is the more pressing optimization: • reducing http requests? • reducing unused jQuery selectors?
I'm using Chrome's Audit tool to profile and it recommends fewer network request for javascript files. Any guidelines for measuring bloat in jQuery.ready(…);?
Update
Weighing the evidence presented the simplest thing to do is to serve the implementation code as a single file and then activate different modules using a routing system. In my case I just hooked into the simple php router that I've already built:
root.php
<script type="text/javascript">
var myApp = {
modules: {}
bootstrap: function()
{
<?php
if ( $pageId == 'contact' )
echo 'myApp.modules.contact();';
if ( $pageId == 'index' )
echo 'myApp.modules.splash();';
if ( preg_match( '~^help/~iu', $pageid ) )
echo 'myApp.modules.faq();';
?>
}
}
</script>
<script type="text/javascript" src="/js/jquery1.9.1.js"></script>
<script type="text/javascript" src="/js/jquery-plugins.js"></script>
<script type="text/javascript" src="/js/myApp.js"></script>
myApp.js
$(document).ready(function()
{
myApp.bootstrap();
//… global jQuery stuff …
});
// Define modules
myApp.modules.splash = function() {
$('#splash').click(
//…etc…
);
}
myApp.modules.faq = function() {
$('.faq').show(
//…etc…
);
}
myApp.modules.contact = function() {
$('.contact').bind(
//…etc…
);
}
Serving your static content in as few files as possible is always the recommendation. If you're worried about executing on content that doesn't exist then you should organize your code in such a way that it doesn't all automatically fire on page load, but fires when you tell it to. That way you only execute the code you want to on any given page. The easiest way would be to wrap each pages content in it's own function:
window.aboutPage = function() {
$('').css(); //some stuff
}
then at the bottom of the about page, after your load all your external scripts fire off the appropriate function
<script>$(window.aboutPage) //fire aobutPage stuff on ready</script>
There are frameworks that can accomplish this modularization of code also
If you're using modules (I'm guessing RequireJS?) you can avoid putting a bunch of functions on the global/window object by implementing a simple "router". Have your main app module define a hash of "routes" something like:
var routes = {
'/contact': contact,
'/products': products,
'/splash': splash
};
Where each of contact, products, and splash, is the function you want to call "on DOM ready". Then elsewhere in your same app module pass the appropriate function as jQuery's ready handler something like:
$(routes[window.location.pathname])
Note that:
You might need to do something fancy to make sure you're only using the part of the URL/path that you really want.
You could drop the jQuery dependency (if you want) and use RequireJS's domReady. They apparently work about the same.

how should I write my define to work with curl.js?

I'm reading Addy Osmani's excellent blog post about writing AMD modules. I start with a simple chunk of js that I lifted from his post:
define('modTest', [],
// module definition function
function () {
// return a value that defines the module export
// (i.e the functionality we want to expose for consumption)
// create your module here
var myModule = {
doStuff:function(){
console.log('Yay! Stuff');
}
}
return myModule;
}
);
I took out the dependencies on foo and bar. Just want a simple object that logs to the console.
So I save that in /js/modTest.js and then try to load it:
curl(['/js/modTest.js'])
.then(function(mt) {
console.log("Load complete");
console.log("mt:");
console.log(mt);
mt.doStuff()
}, function(ex) {alert(ex.message);})
Result: error: Multiple anonymous defines in URL. OK that didn't work. Tried adding in a namespace: define('myCompany/modTest', [],, same result. Tried adding an empty string in the dependency array, same result.
Also tried curl(['modTest.js'], function(dep){console.log(dep)}); with the same result.
Is the code in Addy's blog post incorrect? Am I doing something wrong? Maybe a bug in curl?
Update 5/24: I ditched curl.js in favor of require.js. Zero odd errors, very little work to change over. I did have to deal with amdefine a bit to get my code running client and server side (one object is in both places, so grunt had to be configured to take care of that). My defines generally look like:
define(->
class AlphaBravo
...
And never have any trouble loading.
You asked curl() to fetch a module called "/js/modTest.js". It found the file and loaded it and found a module named "modTest", so it complained. :) (That error message is horribly wrong, though!)
Here's how you can fix it (pick one):
1) Remove the ID from your define(). The ID is not recommended. It's typically only used by AMD build tools and when declaring modules inside test harnesses.
2) Refer to the module by the ID you gave it in the define(). (Again, the ID is not recommended in most cases.)
curl(['modTest'], doSomething);
3) Map a package (or a path) to the folder with your application's modules. It's not clear to me what that would be from your example since modTest appears to be a stand-alone module. However, if you were to decide to organize your app's files under an "app" package, you packages config might look like this:
packages: [ { name: 'app', location: 'app' } ]
Then, when you have code that relies on the modTest module, you can get to it via an ID of "app/modTest".
curl(['app/modTest'], doSomething);
I hope that helps clear things up!
Fwiw, Addy's example could actually work with the right configuration, but I don't see any configuration in that post (or my eyes missed it). Something like this might work:
packages: [ { name: 'app', location: '.' } ]
-- John
I've just had a similar problem which turned out to be the include order I was using for my other libraries. I'm loading handlebars.js, crossroads.js, jquery and a few other libraries into my project in the traditional way (script tags in head) and found that when I place the curl.js include first, I get this error, but when I include it last, I do not get this error.
My head tag now looks like this:
<script type="text/javascript" src="/js/lib/jquery.js"></script>
<script type="text/javascript" src="/js/lib/signals.js"></script>
<script type="text/javascript" src="/js/lib/crossroads.js"></script>
<script type="text/javascript" src="/js/lib/handlebars.js"></script>
<script type="text/javascript" src="/js/lib/curl.js"></script>
<script type="text/javascript" src="/js/main.js"></script>
You have a problem with your define call. It is NAMED
See AMD spec for full story on how to write defines, but here is what I would expect to see in your js/modTest.js file:
define(/* this is where the difference is */ function () {
// return a value that defines the module export
// (i.e the functionality we want to expose for consumption)
// create your module here
var myModule = {
doStuff:function(){
console.log('Yay! Stuff');
}
}
return myModule;
}
);
Now, the boring explanation:
CurlJS is awesome. In fact, after dealing with both, RequireJS and CurlJS, I would say CurlJS is awesome-er than RequireJS in one category - reliability of script execution ordering. So you are on the right track.
On of the major things that are different about CurlJS is that it uses "find at least one anonymous define per loaded module, else it's error" logic. RequireJS uses a timeout, where it effectively ignores cases where nothing was defined in a given file, but blows up on caught loading / parsing errors.
That difference is what is getting you here. CurlJS expects at least one anonymous (as in NOT-named) define per loaded module. It still handles named defines fine, as expected. The second you move the contents of "js/modTest.js" into inline code, you will have to "name" the define. But, that's another story.

How can I combine my JavaScript files and still have my callbacks wait for a ready state?

I have lots of functions and event handlers that are split across multiple javascript files which are included on different pages throughout my site.
For performance reasons I want to combine all of those files into 1 file that is global across the site.
The problem is I will have event handlers called on elements that won't necessarily exist and same function names.
This is an example of a typical javascript file...
$(document).ready(function(){
$('#blah').keypress(function(e){
if (e.which == 13) {
checkMap();
return false;
}
});
});
function checkMap() {
// code
}
function loadMap() {
// code
}
I would need to seperate this code into an object that is called on that specific page.
My thoughts are I could re-write it like this:
(function($) {
$.homepage = {
checkMap: function(){
// code
},
loadMap: function(){
//code
}
};
})(jQuery);
And then on the page that requires it I could call $.homepage.checkMap() etc.
But then how would I declare event handlers like document.ready without containing it in it's own function?
First of all: Depending on how much code you have, you should consider, if serving all your code in one file is really a good idea. It's okay to save http-requests, but if you load a huge chunk of code, from which you use 5% on a single page, you might be better of by keeping those js files separated (especially in mobile environments!).
Remember, you can let the browser cache those files. Depending on how frequent your code changes, and how much of the source changes, you might want to separate your code into stable core-functionality and additional .js packages for special purposes. This way you might be better off traffic- and maintainance-wise.
Encapsulating your functions into different objects is a good idea to prevent unnecessary function-hoisting and global namespace pollution.
Finally you can prevent calling needless event handlers by either:
Introducing some kind of pagetype which helps you decide calling only the necessary functions.
or
checking for the existence of certain elements like this if( $("specialelement").length > 0 ){ callhandlers}
to speed up your JS, you could use the Google Closure Compiler. It minifies and optimizes your code.
I think that all you need is a namespace for you application. A namespace is a simple JSON object that could look like this:
var myApp = {
homepage : {
showHeader : function(){},
hideHeader : function(){},
animationDelay : 3400,
start : function(){} // the function that start the entire homepage logic
},
about : {
....
}
}
You can split it in more files:
MyApp will contain the myApp = { } object, maybe with some useful utilities like object.create or what have you.
Homepage.js will contain myApp.homepage = { ... } with all the methods of your homepage page.
The list goes on and on with the rest of the pages.
Think of it as packages. You don't need to use $ as the main object.
<script src="myapp.js"></script>
<script src="homepage.js"></script>
<-....->
<script>
myApp.homepage.start();
</script>
Would be the way I would use the homepage object.
When compressing with YUI, you should have:
<script src="scripts.min.js"></script>
<script>
myApp.homepage.start();
</script>
Just to make sure I've understood you correctly, you have one js file with all your code, but you want to still be in control of what is executed on a certain page?
If that is the case, then the Terrific JS framework could interest you. It allows you to apply javascript functionality to a module. A module is a component on your webpage, like the navigation, header, a currency converter. Terrific JS scans the dom and executes the js for the modules it finds so you don't have to worry about execution. Terrific JS requires OOCSS naming conventions to identify modules. It's no quick solution to your problem but it will help if you're willing to take the time. Here are some more links you may find useful:
Hello World Example:
http://jsfiddle.net/brunschgi/uzjSM/
Blogpost on using:
http://thomas.junghans.co.za/blog/2011/10/14/using-terrificjs-in-your-website/
I would use something like YUI compressor to merge all files into one min.js file that is minified. If you are looking for performance both merging and minifiying is the way to go. http://developer.yahoo.com/yui/compressor/
Example:
Javascript input files: jquery.js, ads.js support.js
run yui with jquery.js, ads.js, support.js output it into min.js
Javascript output files: min.js
then use min.js in your html code.

How to organize javascript file into smaller pieces?

I currently have one large external javascript file that is used on the page. I currently wrap the code in a self-invoking function because I have other sections that are loaded using ajax tabs, so I want to avoid naming clashes with those other external js files.
The code in the file is organized like below. I would like to split some of the code inside the plannerTab namespace into smaller files, yet still have it be part of that namespace.
How could I do this? Or, do you guys recommend a different approach? Thanks!
// Document Ready
$(function ()
{
// initializes table
plannerTab.plannerTable.init();
});
var plannerTab = (function ()
{
// All the code for the page is in here. I would like to extract sections
// from in here and put them into their own external files while still keeping
// the namespacing
}();
Update
How could I separate parts from within the plannerTab variable into smaller external js files, and still maintain that they are part of the plannerTab namespace? A small example below.
// Scope: plannerTab.config - Would like to store configuartion into a separate file
var config = {
selectors: {
tableId: '#plannerTable',
addTaskId: '#AddTask',
editTaskSelector: '#plannerTable .edit',
dateFilterSelector: '#plannerTable_TimeFilter li',
deleteTaskClass: '.delete',
searchFilter: '#plannerTable_filter',
selectedDateFilter: 'selected-dateFilter',
taskCellSelector: '#plannerTable .task-col',
taskClass: '.taskId'
},
urls: {
addTaskFormURL: '/Planner/Planner/LoadAddTaskForm',
editTaskFormURL: '/Planner/Planner/LoadEditTaskForm',
deleteTaskURL: '/Planner/Planner/DeleteTask',
getTasksByDateRangeURL: '/Planner/Planner/GetTasksByDateRange',
viewTaskURL: '/Planner/Planner/ViewTask'
}
};
Look at this example (from google)
<script type="text/javascript">
function importScript(url){
var tag = document.createElement("script");
tag.type="text/javascript";
tag.src = url;
document.body.appendChild(tag);
}
window.onload = function(){
// imports go here
importScript("foo.js"); // example
};
</script>
I'm assuming that plannerTab becomes an object return result of the self executing function. If you need to add properties or methods to that object dynamically, you can take a look at jQuery.extend() http://api.jquery.com/jQuery.extend/
You would need to modify the external JS to use the jQuery extend method to add onto existing properties and methods of plannerTab. As long as you keep plannerTab a global variable, you will continue adding to it as you import more external js files.
If you are using the module pattern to maintain private variables in plannerTab, be sure to test how those values behave once you use jQuery.extend().

Categories

Resources