I'm trying to avoid global scope for the following problem, but cannot see a way to avoid it.
I have a singleton Javascript object called "Application". This is an AMD module.
I then have many "Modules" (not to be confused with AMD modules) which are just javascript objects that I'd like to register with the "Application" instance.
For example:
require(['Application'], function(app) {
var module = {
name: "theme-switcher",
start: function() { console.log("started") } }
app.registerModule(module)
}
The architecture I am going for, is i'd like for each "Module" on the page to register itself with the "Application" instance.
Here is the tricky part: Only once ALL modules have been registered with the application, do I then want the "Application" instance, to loop through those registered modules and call their "Start()" methods.
The way I thought to do this was to just add another require block at the bottom of the page like this:
<script type="text/javascript">
require(['Application'], function (app) {
// start all the registered modules running.
app.start(() => {
// this could be a call back function once all modules started..
});
});
Niavely thinking, that just because this require call was last, that it would allways be executed last. But actually, sometimes this gets fired BEFORE the require calls above - so the Application attempts to Start() all registered modules BEFORE the modules themselves have all registered themselves with the Application.
No matter how I think about this problem, I am often led back to the fact that I need to keep some state in global scope, Something like this:
Module 1:
var app = Application.Instance;
var moduleStart = function(){
require(['jquery'], function(jquery) {
// do module goodness here.
}};
app.registerModule({name: "theme-switcher", start: moduleStart })
// later on in page - some other widget
// Module 2
var app = Application.Instance;
var moduleStart = function(){
require(['jquery'], function(jquery) {
// do second module goodness here.
}};
app.registerModule({name: "foo", start: moduleStart })
And then at the bottom of the page,
var app = Application.Instance;
app.Start(); // loops through registered modules calling start() method.
Surely there must be a way to do this avoiding global scope?
The reason for me wanting to do this, is that I want the "Application" to manage the lifecycle for registered modules on the page - including starting / pausing / stopping them etc. I'd also like the Application to publish an event once ALL modules gave been started - as this is when I would typically stop displaying my "loading" animation, and actually display the DOM - as modules will often manipulate the DOM in their start() methods and I don't want the page to be visible before everything is started.
This will do it. If you make every object you are wanting an AMD module, which I think you should be doing if you have RequireJS in place anyway, then you'll just need an array of strings defining those AMD module names to pass as an argument to the app's init.
Application.js : -
define(function (require, exports, module) {
"use strict";
var modules = {};
var moduleNames = [];
var numberOfModules = 0;
var loadedModules = 0;
exports.init = function (dependencies) {
numberOfModules = dependencies.length;
for (var i = 0; i < numberOfModules; i++){
var name = dependencies[i];
moduleNames.push(name);
require([name], function (moduleRef) {
loadedModules++;
modules[name] = moduleRef;
if (numberOfModules === loadedModules) {
exports.start();
}
});
}
};
exports.start = function () {
// all modules available
// use modules.myModuleName to access the module.
modules.myModuleName.functionName();
// or if they all have start() function and it needs calling
for (var i = 0; i < moduleNames.length; i++) {
modules[moduleNames[i]].start();
}
};
});
USAGE Depending on how you are loading your app, assuming you have an Application reference somewhere, just call:
// names of modules RequireJS uses to require, can be changed for each page.
var dependencies = ['moduleOne', 'moduleTwo', 'myModuleName'];
app.init(dependencies);
CodePen of this code, slightly altered to work on one page... http://codepen.io/owenayres/pen/MyMJYa
Related
I'm studying module pattern and have a question.
I want to use 'settings' anywhere.
script1.js
var MODULE = (function() {
var t = {};
this.settings = { // this == Window
users: ['john', 'emma']
}
return t;
})()
script2.js
MODULE.dom = function() {
var t = this; // this == MODULE
document.querySelector('p').textContent = t.settings.users[0]; // error
function _say() {
return t.settings.users[1] // error
}
return {
say: _say
}
}
MODULE.DOM = MODULE.dom.call(MODULE)
When use this.settings = {...}, 'this' means Window so code doesn't work.
When use t.settings = {...}, 'this' means MODULE so code works but when write MODULE in dev console, settings is exposed in MODULE variable. Is it ok?
I'd greatly appreciate any help, thank you!
When use t.settings = {...}, 'this' means MODULE so code works
That's the right way to do it.
but when write MODULE in dev console, settings is exposed in MODULE variable. Is it ok?
It's mostly OK.
If you're worried about the client being able to type in the variable name and see the code that gets run - there's no way to avoid that. They can just look at the devtools to see what the network requests are, and what is downloaded - or look at the Sources panel.
If you're worried about running into naming collisions with larger scripts, then - sometimes, libraries deliberately assign themselves to the window to allow other parts of the code to access them. Perhaps you'd like your MODULE to be like this. If not, then you should utilize JavaScript modules instead, which allow for scripts to be imported inside other scripts without polluting the global namespace at all or having any possibility of naming collisions. For example, you could do
// script1.js
export const MODULE = {
settings: {
users: ['john', 'emma'];
}
};
// script2.js
import { MODULE } from './script1.js';
// proceed to use MODULE
And you can do import { MODULE } from './script1.js'; from any script file, not just script2.js.
Personally, I consider the IIFE module pattern in JavaScript to be mostly obsolete nowadays. For any reasonable-sized script, better to write code in separate files and then import and export as needed. (A 1000 line file is somewhat hard to debug and refactor. Ten 100 line files are easier to debug and refactor.)
If I want to span my JavaScript project across multiple source files, but have each file have access to the same private variable, how would one do that?
For example, if I have the following code:
APP = (function () {
var _secret = {},
app = {};
// Application part 01:
app.part01 = (function () { /* function that uses _secret */ }());
// Application part 02:
app.part02 = (function () { /* function that uses _secret */ }());
//
return app;
}());
How do I put app.part01 and app.part02 in seperate files, but still have access to _secret?
I don't want to pass it as an argument. That's just giving the secret away, as app.part01() could be replaced by any other function.
Maybe I am asking the impossible, but your suggestions might lead me in the right way.
I want to work with multiple files, but I don't know how. Copying and pasting everything inside a single function each time before testing is not something I want to do.
How do I put app.part01 and app.part02 in seperate files, but still have access to _secret?
That's impossible indeed. Script files are executed in the global scope, and don't have any special privileges. All variables that they will be able to access are just as accessible to all other scripts.
Copying and pasting everything inside a single function each time before testing is not something I want to do
What you are looking for is an automated build script. You will be able to configure it so that it bundles your files together, and wraps them in an IEFE in whose scope they will be able to share their private state. The most simple example:
#!/bin/sh
echo "APP = (function () {
var _secret = {},
app = {};" > app.js
cat app.part01.js >> app.js
cat app.part02.js >> app.js
echo " return app;
}());" >> app.js
The only way that you can share _secret is attaching it to the application object and then application object to the window object. Here is an example.
// FIRST JS FILE...
var application; // will be attached to window
(function(app) {
app.secret = "blah!"; // will be attached to application
})(application || (application = {}));
// ANOTHER JS FILE
var application;
(function(app) {
app.method1 = function(){ console.log(app.secret); }; // will be attached to application;
})(application || (application = {}));
console.log(application.method1()); // will display 'blah!' on the console
Working example on jsbin
One way I was able to accomplish this was to create a JS file that contained the global object.
// Define a global object to contain all environment and security variables
var envGlobalObj = {
appDatabase: process.env.YCAPPDATABASEURL,
sessionDatabase: process.env.YCSESSIONDATABASEURL,
secretPhrase: process.env.YCSECRETPHRASE,
appEmailAddress: process.env.YCAPPEMAILADDRESS,
appEmailPassword: process.env.YCAPPEMAILPASSWORD
}
module.exports = envGlobalObj
Then in the files I wish to reference this object, I added a require statement.
var envGlobalObj = require("./envGlobalObj.js");
This allowed me to centralize the environment and secrect variables.
I'm trying to figure out how RequireJS works, but the RequireJS website is very unclear on explaining the basics, especially concerning how to use the 'require' and 'define' methods.
I have the following example running:
//module1.js
define([], function () {
var returnedModule = function () {
var _name = 'whoa I am a module';
this.getName = function () {
return _name;
}
};
return returnedModule;
});
//main.js
require(['module1'], function(temp_reference){
var m = new temp_reference();
console.log("created module: " + m.getName());
});
// ... some other code ...
// now, if I need the module1.js again, do I need to require it again??
My question: do I need to "require" this module1.js file every time I want to do something with it? (in this case creating a new m() object from the temporary reference).
Can't I keep the result of the require call somewhere instead of only having it available inside the callback?
EDIT
I thought I could solve the issue by keeping a reference this way:
// keep the required module available
var myModule = require("module1");
But this generates an error: module1 is not yet loaded.
Require JS is an AMD - Async module definition meaning the modules are loaded into the document when you require it. It kinda provides a functional scope/modular approach to your JavaScript code, say like import keyword in Java or Using key word in C#.
Now answer to your question : Yes you need to reference the defined module in your require module to get its reference as a functional parameter.
For example consider the below code
http://jsfiddle.net/NssGv/52/
define('a', {
add: function(x, y){
return console.log(x + y);
}
});
// Use the module (import)
require(['a'], function(a){
a.add(1, 2);
});
require(['a'], function(a){
a.add(4, 6);
});
In this code context , a - is module definition that is imported by the other modules to access add() method of the defined module.
Require JS constructs a module tree and saves all the defined module in this tree with the name of the module. In tis example its - a (this is called named module)
This tree can be accessed as below through dev console
window.requirejs.s.contexts._.defined.a
And my output would like this:
When ever your load an external modular file Require JS creates a <script> tag and appends it into the document <head>
Consider your example:
Working plunker link : http://plnkr.co/edit/eNQkcfwftkYSY00wMPeI?p=preview
When the below entry point codes are executed
HTML:
<script src="xdomain/require.js" data-main="script"></script>
JS:
require(['module1'], function(temp_reference){
var m = new temp_reference();
console.log("created module: " + m.getName());
});
Require JS attaches 2 files namely script.js (referenced in script tag in HTML head as main script file) and second is module1.js (referenced in script.js)
After attaching this file to the head asynchronously the code in the modules is executed and the resultant of the module will be pushed to the requirejs module tree as mentioned earlier.
Later these modules are injected into the reference modules based on your dependencies definition you pass as array to the require function.
require([{{YOUR_DEPENDENCIES}}], function({{INJECTED_REFERENCES}}){
--Your code--});
This is what you are trying to achieve(Not suggested)
http://plnkr.co/edit/5SYoNf8xNL1DcMfjuy0Y?p=preview
var myModule; //Global variable
require(['require', 'module1'], function(require){
myModule = require("module1");
var m = new myModule();
console.log("created module: " + m.getName());
});
You can also try this Hacky!
http://plnkr.co/edit/Rr34HlhOiyja6XnwH8fw?p=preview
var myModule; //Global variable
require(['module1'], function(){
myModule = window.requirejs.s.contexts._.defined.module1;
var m = new myModule();
console.log("created module: " + m.getName());
});
Finally
Require JS is giving the javascript modularity approach and on demand loading of scripts rather than preloaded in the memory. This saves some memory and also contribute to the speed of your web app. Your code becomes structured automatically and will be easy to maintain.
Running require(['pages/home']) will work once but if I use require(['pages/home']) again then it won't run.
The module "pages/home" is a file named "home.js" in a directory named "pages".
main.js
require(['pages/home']);
pages/home.js
define('pages/home', function() {
console.log('running pages/home module');
});
RequireJS modules are singletons. It loads a module once and only once. If a module has been loaded already, what you get if you load it again is a reference to the same module as originally loaded. The factory function you pass to define won't be run a second time.
So what you are seeing is exactly what is expected.
Static code in modules isn't supposed to be evaluated more than once, just like a script loaded through a normal <script> tag won't be run more than once during the page load.
Imagine if a module contained code like:
define('my-module', function () {
var foo = foo || 0;
var bar = ++foo;
});
You should expect bar and foo to both === 1, but if the module was run repeatedly and a global foo existed, that may not be the case. Admittedly, this is a very contrived example, but evaluating a module repeatedly could cause serious problems.
Make it return a function/object that can be executed after you require it.
define('pages/home', function() {
return function(){
console.log('running pages/home module');
};
});
require(['pages/home'], function(resultFunc){
window.YourFunc = resultFunc;
});
Now you can execute your function whenever you want
I've got a Backbone/AMD app and what I would like to do is have a model/object be fetched in the main module when the app loads, then be either accessible to or be able to be loaded into subsequent modules without the overhead of re-fetching it (it's a permissions model and thus is required pretty much everywhere).
Is there a clean way of doing this?
Simply make it its own dependency:
define(["backbone", "text!api/v1/user/permissions"],
function(Backbone, permissionJSON) {
return new Backbone.Model(JSON.parse(permissionJSON));
});
If you require a module for which you want to keep state you can do as follows
stateMod.js
require(['jquery', 'lib1','lib2'], function($, l1,l2) {
var thisVariableHoldsStateBetweenModules = "initValue"
var thisIsTheAPIOfYourModule = function(newValue) {
thisVariableHoldsStateBetweenModules = newValue
}
var getInternalState = function () {
return thisVariableHoldsStateBetweenModules
}
return {
set: thisIsTheAPIOfYourModule,
get: getInternalState
}
}
})
What you get back when you require this module is thisIsAPIOfYourModule, so you can just use it to change thisVariableHoldsStateBetweenModules:
otherMod.js
require(['stateMod'], function(stateMod) {
stateMod.set("Hello world")
})
and later on, in module nextMod.js:
require(['stateMod'], function(stateMod) {
stateMod.get() // outputs Hello World
})
requirejs saves the object returned from the first time it is required, and this why you don't re-load (network wiise) d3js and jQuery or other common libs every time you need them in a module, otherwise it would be pretty crap.
Give this a go and let me know how it worked for you.