using requirejs to only load jquery plugins on user interaction - javascript

I am starting to play with require js / modular development for the first time and am liking what I see.
What I am trying to achieve is basically only load certain custom jQ modules when needed. My main goal is page performance. At present I am only loading require.js (which in turns loads jQ async) then other jQ code/plugins only fire on user interaction.
Would the following code be considered good/bad practice? Is there anything anyone would change? (super basic example below)
MAIN.JS
require.config({
paths: {
"jquery": "//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min"
}
});
require(["jquery"], function($) {
// overlay plugin
$("a").on("click", function(e){
var self = this;
require(["overlay"], function (overlay) {
overlay.init(self);
});
e.preventDefault();
});
});
OVERLAY.JS
define(function () {
return {
init: function(self) {
$.ajax({
url: self.href,
success: function (data) {
$("#results").html($(data).filter('#details').html());
},
dataType: 'html'
});
$('#results').fadeIn();
}
}
});
Cheers,
Adi.

Your method of loading overlay is a correct use of require, however a couple of things:
Overlay.js should list jQuery as a dependency. Ensure your modules have all the code they need to run. In this case it's fine (as you're grabbing jQuery in the require) but say you used document.addEventListener to attach your click then you're no longer sure jQuery will be available for use by the $.ajax. It's nice to know your modules ask for everything they need rather than getting it by luck.
One rule I try to follow is to keep all my DOM related stuff in main. So for example:
Overlay
// Example code, and not complete
define(function(require) {
var $ = require('jquery');
return {
init: function(elements) {
this.trigger = $(elements.trigger);
this.target = $(elements.target);
this.trigger.on('click', this.someEvent.bind(this));
},
someEvent: function() {
this.getAjax();
}
}
});
And then in main.js just pass in the DOM elements
require(['overlay'], function(overlay) {
overlay.init({
trigger: 'a',
target: '#results'
})
});
Keeping the DOM elements separate and in one place makes updating them breeze. You could also pass in an options object for other things (such as class names) much like a jQuery plugin does.
Finally, in your example code your $('#results').fadeIn(); is outside the success callback and would run immediately.

Related

How to implement a plugin, that modifies the original module only when required?

I have a plugin extending an original module.
It should only modify the module, when explicitly required.
Problem:
As soon as it is required once, the original module is modified forever, also for cases where the plugin is not a dependency.
The order doesn't matter here, it's enough to require the plugin once.
Example:
define("main", [], function() {
return {opt: "A"};
});
define("plugin", ["main"], function(obj) {
obj.opt = "B";
});
require(["main", "plugin"], function(obj) {
console.log(obj.opt); // should log B
});
require(["main"], function(obj) {
console.log(obj.opt); // should log A but logs B
});
I guess the way to go is to somehow tell require to always reload main from source instead of using the cached version.
I have no idea how, though.
Or maybe there's an even more elegant way?
Please enlighten me, guys.
Fiddle: http://jsfiddle.net/r75e446f
UPDATE: Some might find it important to know that I need this for my karma unit test environment to test a module with and without the plugin.
UPDATE2: Look below for my own solution.
RequireJS modules are singletons. If you load main once, twice, 10 times, you are always going to get the same module. And so if you modify its state, it is modified for all modules that use it. It is possible to tell RequireJS to undefine the module but I do not recommend it as it will just make your code obscure.
If I wanted to do what you are trying to do I'd design my code something like this:
<script>
define("main", [], function() {
function Main (opt) {
this.opt = opt;
}
return Main;
});
define("plugin1", [], function() {
return {
install: function (main) {
main.opt += " plugin1";
}
};
});
define("plugin2", [], function() {
return {
install: function (main) {
main.opt += " plugin2";
}
};
});
// Case 1: no plugins
require(["main"], function(Main) {
var main = new Main("A");
console.log(main.opt);
});
// Case 2: only plugin1
require(["plugin1", "main"], function (plugin1, Main) {
var main = new Main("A");
plugin1.install(main);
console.log(main.opt);
});
// Case 3: only plugin2
require(["plugin2", "main"], function (plugin2, Main) {
var main = new Main("A");
plugin2.install(main);
console.log(main.opt);
});
// Case 4: plugin1 and plugin2
require(["plugin1", "plugin2", "main"], function (plugin1, plugin2,
Main) {
var main = new Main("A");
plugin1.install(main);
plugin2.install(main);
console.log(main.opt);
});
Basically, make what is common to all cases a Main class which can be initialized at construction and which can be modified by plugins. Then each plugin can install itself on Main. The code above is a minimal illustration of how it could be done. In a real project, the final solution would have to be designed to take into account the specific needs of the project.
If you don't want the original module to be modified for all modules which use it, then your plugin should not modify the original module. Instead, have the plugin return a modified copy of the original module instead.
define("main", [], function() {
return {opt: "A"};
});
define("plugin", ["main"], function(obj) {
var decorator = {}
for (var key in obj) { decorator[key] = obj[key];}
decorator.opt = "B";
return decorator
});
require(["main", "plugin"], function(obj, plugin) {
console.log(plugin.opt); // should log B
});
require(["main"], function(obj) {
console.log(obj.opt); // should log A but logs B
});
This will work without any complications if your original object is a simple struct-like object without any functions. If there are functions, or if your original object was constructed using the Module Pattern, then there is a strong possibility of subtle errors in the copy, depending on how the methods were defined.
EDIT 2015-01-13: The OP clarified his question that he would like a way for his tests to be able to run both modified and unmodified original module without having to reload the page. In that case, I would recommend using require.undef to unload the main module and then reload it without having to reload the entire page.
I would like to bring up my understanding for discussion.. when we AMD, we define the module. Unless the module (in this case is main, the defined source) is able to accepting the plugin and allow the plugin to change the properties, i guess we're not able to hijack it or it will be an anti-pattern?
what about we extend/clone the defined source, like
define("plugin", ["main"], function(obj) {
return $.extend(true, {}, obj, {opt: "B"});
});
http://jsfiddle.net/r75e446f/6/
here the plugin module is always using the main module as its dependency, then when using the module we just directly use the plugin module and no longer need to require the main module? (by the way i understand this defeat the definition of 'plugin', but my direction is that we're dealing with the module as what the requireJs designed to be)
--2nd approach--
if you do really want to do it the plugin way, how about the loader plugin? like the text plugin we always using `require('text!abc.json'). So write a loader and register it to the requireJs's config, and then we could use it. (more)
So I found out how to achieve, what I want and thought I'd share it here.
The answer is called 'context', which is an option in the requirejs config.
http://requirejs.org/docs/api.html#multiversion
Here's how I implemented my solution:
var reqOne = require.config({
context: 'one'
});
var reqTwo = require.config({
context: 'two'
});
reqOne(["main", "plugin"], function(obj) {
console.log(obj.opt); // logs B
});
reqTwo(["main"], function(obj) {
console.log(obj.opt); // logs A
});
Unfortunately this doesn't work in the fiddle, because the second require will try to load the main module externally and I can't upload files to jsfiddle.
But the mere fact, that he tries to do that and doesn't use the 'main' module already loaded in the other require, should be proof that his method works.
If the definitions are outsourced to single files this works like a charm.
Thanks for everybody who chimed in.
A more elegant solution would be to take an object-oriented approach, where:
the modules return constructors rather than instances, and
plugin can then be a subclass of main.
This is the implementation (fiddle):
console.clear();
define("Main", [], function() {
return function() { this.opt = "A"; };
});
define("Plugin", ["Main"], function(Main) {
return function() {
Main.call(this);
this.opt = "B";
};
});
require(["Plugin"], function(Plugin) {
console.log((new Plugin()).opt); // should log B
});
require(["Main"], function(Main) {
console.log((new Main()).opt); // should log A
});

RequireJS - using module returns undefined

I'm trying to use RequireJS in my app. I'm including the requirejs script from cdnjs like this:
<script src="//cdnjs.cloudflare.com/ajax/libs/require.js/2.1.10/require.min.js"></script>
on my page I have a button and I register an event for it:
$('#btnSpeedTest').on('click', function (e) {
require([baseUrl + 'Content/js/tools/speedtest.js'], function (speedTestModule) {
alert(speedTestModule);
});
});
If I watch with Fidler - I see that upon clicking the button speedtest.js is loaded.
speedtest.js contains the following:
define('speedTestModule', function () {
function SpeedTest(settings, startNow) {
// basic initialization
}
var fn = SpeedTest.prototype;
fn.startRequest = function (download, twoRequests) {
// logic
}
return SpeedTest;
});
The alert(speedTestModule); command returns "undefined". I saw a tutorial on RequireJS and in that tutorial everything was in the same directory as well as files with names of modules (which is not my case since I'm loading it from CDN).
I even tried to return a simple string, but it did not work. What am I missing?
Thanks
Don't use a named define. Instead of this:
define('speedTestModule', function () {
do this:
define(function () {
and let RequireJS name your module. You typically want to let r.js add names to your modules when you optimize them. There are a few cases where using names yourself in a define call is warranted but these are really special cases.

Why do I recieve both "$jQval is undefined" and "$.validator.unobtrusive is undefined" when using RequireJS?

This has had me puzzled for a few hours now. When the script is in a non-requirejs javascript file it works fine. When I use it with RequireJS it fails to work and gives me the error messages in the question title (though the firebug console).
I was just trying to get it to "work" with RequireJS before attempting to refactor into a module.
The Html is rendering correctly. The scripts are loading correctly. Also, I'm using the require-jquery.js bundle download, which is referenced in the layout template across all pages.
main.js:
require.config({
paths: {
"maximum-filesize": "modules/validation/maximum-filesize"
}
});
require(["maximum-filesize", "domReady!"], function (maxFileSize) {
});
maximum-filesize.js
require.config({
paths: {
"jquery-validate": "libs/jquery/jquery.validate",
"jquery-validate-unobtrusive": "libs/jquery/jquery.validate.unobtrusive"
}
});
define(["jquery", "jquery-validate", "jquery-validate-unobtrusive", "domReady!"], function ($) {
$.validator.unobtrusive.adapters.add(
'filesize', ['maxsize'], function(options) {
options.rules['filesize'] = options.params;
if (options.messages) {
options.messages['filesize'] = options.message;
}
});
$.validator.addMethod('filesize', function (value, element, params) {
if (element.files.length < 1) {
// No files selected
return true;
}
if (!element.files || !element.files[0].size) {
// This browser doesn't support the HTML5 API
return true;
}
return element.files[0].size < params.maxsize;
}, '');
});
Edit 1
I just tried commenting out all of the above code, and replaced it with a simple:
$('#Name').val("Hello");
This rendered "Hello" correctly in the #Name textbox, so JQuery is working.
You should use requires shim option to tell requires to load jquery before jquery validate. Otherwise load order is undefined.
Another possible problem is calling requirejs.config 2 times. Requirejs has problems with merging configs

Require and Backbone - Loading jQuery mobile dynamically

I'm working on an app that uses Backbone and RequireJS (using the Backbone boilerplate).
What I'd like to do is detect if the user is on a mobile device (currently using Modernizr to check for touch), and if so then load jQuery Mobile's css and js, and have it applied to all modules and their templates.
I'm not sure of the best way to do this with RequireJS.
Cheers
Edit: After re-reading your question, I'm not sure this is what you're asking for. Sorry for the noise.
I'm currently in a similar situation. You can set properties on Backbone's View/Router/Model/Etc prototypes and they'll filter down the chain. So, for example:
//Assuming we're in 'mobile' mode, you'd do this in whatever code gets loaded.
Backbone.View.prototype.useMobileView = true;
Then you can do whatever is necessary if this.useMobileView == true. For example:
ResponsiveView = Backbone.View.extend({
getTemplate: function () {
if(this.useMobileView) {
return this.mobileTemplate;
} else {
return this.template;
}
}
});
SomeView = ResponsiveView.extend({
render: function () {
var template = this.getTemplate();
//do stuff
}
});

Revealing module pattern with jQuery not working

I've been playing around with the revealing module patter. I originally started using the Singleton pattern but from reading around the module pattern seems to be the better option.
So i've tested the following:
var test = (function() {
var init = function () {
alert('hello');
};
return {
init: init
};
})();
and this works fine when calling
<script>test.init();</script>
However, i want to use jQuery so i tried:
var test = (function($) {
var init = function () {
$("#samplediv").html('test');
alert('hello');
};
return {
init: init
};
})(jQuery);
but this doesn't work. It does work when using:
<script>$(function() { test.init(); });</script>
How can i get it to work without the added jQuery when calling it?
Usually, anything that touches the DOM needs to done in the $(document).ready(fn) callback. $(fn) is a shortcut for this.
So the reason it doesn't work is because you are searching for DOM elements that don't exist yet. So you have 2 choices. Either you must use the $() wrapper, or find another way to run your code only after the DOM elements exist. Another way to do this is to move your script tag the bottom of the body tag so that the DOM elements can exist when it's ran.
<body>
<!-- your page elements here -->
<script>test.init();</script>
</body>

Categories