I have a few questions about Best Practises using javascript in external files and namespacing.
Let's have a namespace MyCompany, global configuration stuff, code for individual pages and maybe some "API"s.
var MyCompany = {};
Global configuration in HTML
MyCompany.root = "/";
Which approach is better
First
MyCompany.Page = {};
(function(ns} {
ns.init = function() {
var root = MyCompany.root;
ajax(root+"somepage.html");
};
}(MyCompany.Page.Home = MyCompany.Page.Home || {});
and in html use
<script>
$( function() {
MyCompany.Page.Home.init();
});
</script>
Second (Page as an Class and its instance)
MyCompany.Page.Home = function() {
var root = MyCompany.root;
this.init = function() {
ajax(root + "somepage.html");
};
};
in html
<script>
var page = new MyCompany.Page.Home();
$( function() {
page.init();
});
</script>
Submodules and Mixing API with Page javascript
If our Homepage has some reviews.
MyCompany.Page.Home.Reviews = function() {
this.init = function() {
load_stuff();
}
};
And now inside Page init use
MyCompany.Home.Page = function(data) {
var reviews = new MyCompany.Home.Page.Reviews();
this.init = function() {
reviews.init();
};
};
Could that cause troubles?
It's obvious that Reviews extends MyCompany.Home.Page, but MyCompany.Home.Page requires Reviews.
It shouldn't cause troubles if instance on MyCompany.Home.Page is created after MyCompany.Home.Page.Reviews are loaded, right? Because Reviews in fact will extend the function object, is that right?
I guess this depends on answer to first question.
It also could be
(function(ns) {
ns.init = function() { MyCompany.Page.Home.Reviews.init(); };
})(MyCompany.Page.Home = MyCompany.Page.Home || {} );
(function(ns) {
ns.init = function() { load_stuff(); };
})(MyCompany.Page.Home.Reviews = MyCompany.Page.Home.Reviews || {});
Also should I somehow separate API of Page javascript?
Such as
MyCompany.APIS.Maps = function(location) {
/* Private variables */
var _location = location;
/* Private functions */
function search_address(address) { .. do search .. }
/* Public interface */
this.search = search_address;
do some initialization ...
};
I'd be glad if anyone reads it all to leave some comment.
Thank you in advance.
Which approach is better? Revealing singleton module (first) or a constructor function/class and its instance (second)?
Depends on your use case. If you don't expect multiple page objects to exist at once (and you hardly seem to), the singleton (with an init function) is really fine. Everything else could be considered wrong or at least overkill.
Same thing holds true for your MyCompany.Page.Home.Reviews (or MyCompany.Home.Page.Reviews?) class module, of which you seem to need only one instance.
It shouldn't cause troubles if instance on MyCompany.Home.Page is created after MyCompany.Home.Page.Reviews are loaded, right? Because Reviews in fact will extend the function object, is that right?
Yes.
(function(ns) {
ns.init = function() { MyCompany.Page.Home.Reviews.init(); };
})(MyCompany.Page.Home = MyCompany.Page.Home || {} );
If you have that ns shortcut available, you should use it:
(function(ns) {
ns.init = function() { ns.Reviews.init(); };
})(MyCompany.Page.Home = MyCompany.Page.Home || {} );
Also should I somehow separate API of Page javascript?
For development: Yes, in every case. Each module should have its own file. When deploying, you might concatenate them together for faster loading, but that's a different question.
Related
I'm working on a Flask app where some data gets passed to a template, and I want to make that data available to multiple instances of an object. Here's what it would look like if I just hardcoded the desired data into my .js file:
var Module = function(selector) {
var targetDiv = selector,
targetData = 'lorem ipsum sit dolor',
show = function() {
$('<p>' + targetData + '</p>').appendTo(targetDiv);
};
return {
show: show,
};
};
$(document).ready(function() {
firstModule = new Module($('#first'));
secondModule = new Module($('#second'));
firstModule.show();
secondModule.show();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id='first'></div>
<div id='second'></div>
I can't just call a function on an unconstructed Module object, so my next step is to create a static-like ModuleFactory that I can load with data from jinja, and then create Modules from there:
var ModuleFactory = function() {
var targetData = null,
setData = function(data) {
targetData = data;
},
create = function(selector) {
return new Module(selector, data);
};
return {
setData: setData,
create: create,
};
} ();
Then I attempt to call ModuleFactory.setData({{data}}); from a <script> tag in the HTML, and do something like ModuleFactory.create($('#first')).show(); in the .js
But of course because I have to include my .js file before using the ModuleFactory in the HTML, I end up constructing the objects before the factory is initialized.
(Past this point, my workaround attempts stop being relevant to the problem.)
Anyway, what's the correct way of getting data from Jinja to my JS module? There has to be a common pattern or something.
This feels awful, but it works:
Have an object with a runProgram method, which takes the desired data as a parameter, then executes the logic that had previously been inside the document ready function. e.g.
var ProgramRunner = function() {
var runProgram = function(data) {
ModuleFactory.setData(data);
firstModule = ModuleFactory.create($('#first'));
firstModule.show();
};
return {
runProgram: runProgram;
};
};
Then just
<script>
$(document).ready(function() { ProgramRunner.runProgram({{data}}) });
</script>
in the HTML.
(Leaving question open, because I suspect there's a much better way of handling this.)
I have and external JS scripts file with all my objects in that runs once the document is ready something like this...
jQuery(function($) {
var Main = {
run: function () {
myFunction.setup();
}
}
var myFunction = {
setup: function() {
//Do some stuff here
}
}
Main.run();
});
I want to be able to run myFunction.setup() only if im on a certain page though otherwise I get errors if that method is looking for elements on the page that don't exist e.g a slideshow, menus etc.
At the moment I have got round this by checking if the element exists with .length and if it does then running the rest of the method but I was wondering if there was a nicer way? Maybe like if it was possible to send variables to the scripts file when it loads based on the page im on so it knows what to methods run?
Any help would be really appreciated.
Thanks
Giles
Paul Irish has a great way of doing exactly this, using ID and classes from the body tag to execute certain blocks of code:
http://paulirish.com/2009/markup-based-unobtrusive-comprehensive-dom-ready-execution/
This kind of thing might help:
Page specific
var page_config = {
setup_allowed: true
// ... more config
};
Generic
var Main,
myFunction;
(function ($, _config) {
myFunction = (function () {
var _public = {};
if (_config.setup_allowed === true) {
_public.setup = function () {
};
}
return _public;
})();
Main = (function () {
var _public = {};
if (typeof myFunction.setup !== "undefined") {
_public.run = function () {
myFunction.setup();
};
// Run it as we had Main.run() before
_public.run();
}
return _public;
})();
})(jQuery, page_config);
This way Main.run() and myFunction.setup() are only available if specified in page_config.
Here's a working example you can have a play with. This may be a bit verbose for your particular requirement but hopefully it'll help in some way :-)
I am attempting to use namespaces to better organize my sites very complicated code. I have:
var myApp = {};
myApp.fp = {
brandCarousel: null,
init: function() {
// initialize brand carousel
this.brandCarousel = new Pluit.Carousel('brand-scroll-outer', { circular: false });
}
};
document.observe("dom:loaded", function() { myApp.fp.init(); });
I am using prototype 1.7 and the Pluit Carousel library.
If I try to call one of Pluit Carousels methods (moveNext) by typing:
myApp.fp.brandCarousel.moveNext()
I get a JavaScript error:
TypeError: Object # has no method 'moveNext'
Is there something I am getting wrong when it comes to scope or namespacing? How can I access this internal function?
Nothing about your particular example looks especially wrong (except that it switches between myApp and masApp (typo?)). Here's a stripped-down, dummy example to show that your approach works (and that your problem must be in another location):
var myApp = {};
myApp.fp = {
brandCarousel: null,
init: function() {
// initialize brand carousel
this.brandCarousel = new (function() {
this.moveNext = function() {
alert("moving!");
};
})
}
};
myApp.fp.init()
myApp.fp.brandCarousel.moveNext()
The above code alerts moving! as expected.
Does anyone know what the pattern is for being able to create a module pattern but with being able to set the name space that the module lives under dynamically.
So instead of what is below:-
var MODULE = (function () {
var my = {},
privateVariable = 1;
function privateMethod() {
// ...
}
my.moduleProperty = 1;
my.moduleMethod = function () {
// ...
};
return my;
}());
MODULE could be set to anything you like, I remember seeing it done i a screencast but can't remember where...
Basically i'd like to create a library that could be assigned to any namespace the implementer likes.
I'd think you'd just be able to add a method that lets you set it, and nullify MODULE.
http://jsfiddle.net/yrsdR/
my.namespace = function( ns ) {
window[ns] = my;
window.MODULE = null;
}
then:
window.MODULE.namespace( "myNamespace" );
window.MODULE; // null
window.myNamespace // object
or you could have it return the module to whatever variable you want.
http://jsfiddle.net/yrsdR/1/
my.namespace = function() {
window.MODULE = null;
return my;
}
window.myNamespace = MODULE.namespace();
window.MODULE; // null
window.myNamespace // object
Funny to come across this now, I just set up a new code base this way. This is how I approached it (it depends on 1 extra global variable however):
// ability to rename namespace easily
var AXS_NS = 'App';
window[AXS_NS] = (function (app, $, Modernizr) {
app.config = {
debug: false
};
app.init = function() {
console.log('init');
};
return app;
})(window[AXS_NS] || {}, jQuery, Modernizr);
Then you could do:
jQuery(function($){
App.init();
});
I know this is a long ways after the original question. And I'm not sure of the relevance of having a dynamically named javascript object. But the following pattern does this is a reasonable way by allowing you to set up the object namespace name in the script element of your html page.
Something like
<script src="js/myscript.js" namespaceName="myObject"><script>
Which allows you to then call myObject.hello() in your javascript.
Example javascript that uses this solution.
/**
* Dynamic mechanism for setting a javascript namespace.
*
* This works by adding the namespaceName as an attribute to the script
* element on your page. Something like
*
* **<script src="js/myscript.js" namespaceName="myObject"><script>**
*
* When the script has loaded it will have created a new javascript object
* with the nemespace name "myObject".
*
* You can now use myObject.hello() which returns "ns.hello() called"<br/>
*
* This works on later versions of chrome, firefox and ie.
*/
(function (ns) {
ns.hello = function () {
return "ns.hello() called";
}
} (window[document.getElementsByTagName('script')[document.getElementsByTagName('script').length-1].attributes['namespaceName'].value]=
window[document.getElementsByTagName('script')[document.getElementsByTagName('script').length-1].attributes['namespaceName'].value] || {}));
The
window[document.getElementsByTagName('script')
[document.getElementsByTagName('script').length-1]
.attributes['namespaceName'].value]
Is used to pull the attribute namespaceName value from the script load
this one for the javascript gurus:
I'm experimenting with a new javascript framework. Its structure is inspired by none other than the mighty jQuery.
It has a global variable called $j, and .. oh, just see it in action:
/* file: jfoo.js */
$j = new (function(){
var modules=[];
this.loadModule = function(mod) {
modules[ modules.length ] = mod;
}
this.hasModule = function(mod) {
for( ind in modules )
if( modules[ind]==mod ) return true;
return false;
}
})();
You see that modules is a private variable. No problem; that just what I intended.
Now, I want to write a plugin that adds a $j.loadBatch method to my system. So..
/* file: loadBatch.jfoo.js */
!$j || (function(){
$j.loadBatch = function(batch) {
for(ind in batch)
modules[ modules.length++ ] = batch[ind];
}
})();
But, since this method is not part of the closure in file jfoo.js, this is not possible.
I also tried this version:
/* file: jfoo.js */
$j = new (function(){
var modules=[];
this.loadModule = function(mod) {
modules[ modules.length ] = mod;
}
this.hasModule = function(mod) {
for( ind in modules )
if( modules[ind]==mod ) return true;
return false;
}
this.extend = function(extensions) {
for( ext in extensions )
this[ext] = extensions[ext];
}
})();
and
/* file:loadBatch.jfoo.js */
!$j || (function(){
$j.extend( {
loadBatch : function(batch) {
for(ind in batch)
modules[ modules.length++ ] = batch[ind];
}
});
})();
But I got no better results.
So, the questions:
Is there any way that I can write the loadBatch method in a separate file and still have it access the private method modules? (I expect the answer to be a resounding NO, but, who knows?)
is there any other way by which I can achive the desired effect without making modules public? (I know how to proceed if I make it public)
does jQuery use private members? how does it get over its "private accessibility" problems then?
Thanks,
jrh
AFAIK there's no way to give access to a "private" variable (variable only within the scope of a closure) to some execution contexts outside its scope, but not others. (e.g. you could write an accessor method but couldn't limit who gets access)