I have a 300 line javascript file that sets up jQuery event handlers and other needed functions for a partial view that's used by multiple views within a ASP.NET MVC application. The event handlers handle 99% of everything identically regardless of which view is using the partial. This question is about that 1% difference.
Since JavaScript doesn't have interfaces is it safe to define a function to be called by one or more of the event handlers that processes the things that are different in a separate file that is loaded depending on which view is used? If not, what would be the best way to handle this situation? In other languages I'd use interfaces and/or abstract classes in this situation.
Example:
shared file
$(document).ready(function() {
//shared variables here for methods
$(document).on('click', '.selectable-table tbody tr', function() {
//do shared actions
mySpecificFunction();
//finish shared actions (if necessary)
});
});
Definition1.js
function mySpecificFunction() {
//do stuff
}
Definition2.js
function mySpecificFunction() {
//do other stuff
}
The views would load the appropriate scripts as such:
<script src="definitionX.js"></script>
<script src="sharedScript.js"></script>
The "signature" (term being used generously because javascript) of mySpecificFunction() would be the same for each definition, but something in my gut is telling me that this is bad practice. Is there a better/correct way to do this or a design pattern for this purpose?
I think you can use OOP approach here and you don't need the abstract classes or interfaces for that, instead you can use objects (which are more flexible than in other languages).
For example, you can have a base View prototype with shared code and then load specific view1.js, view2.js where the base prototype will be extended with specific code:
$(document).ready(function() {
// view is a view instance coming from the specific view.js
view.init();
});
// sharedScript.js, view prototype
var View = {
init: function() {
$(document).on('click', '.selectable-table tbody tr', function() {
// do shared actions
// ...
// do specific actions
this.mySpecificFunction();
});
},
mySpecificFunction: function() {
//do specific things, can be left empty in the "prototype" object
return;
}
};
// view1.js
var view = Object.create(View);
view.mySpecificFunction = function() {
alert('view 1');
}
// view2.js
var view = Object.create(View);
view.mySpecificFunction = function() {
alert('view 2');
}
And the views would load shared and specific scripts:
<script src="sharedScript.js"></script>
<script src="view1.js"></script>
This is just a rough idea which can be improved, for example, you may want to concatenate and compress all your js code into the single file for production. In this case the global view variable coming from view1.js, view2.js, etc would become a problem.
An improvement can be some kind of "router" which will detect what view should be instantiated:
$(document).ready(function() {
router.when('/', function() {
view = HomePageView();
}).when('/about', function() {
view = AboutPageView();
});
view.init();
});
The approach outlined above will work but it's not the best approach in terms of maintainability. Adding one file or another via a script tag to import the specific function
doesn't necessarily make it clear to another developer that you have actually changed the behaviour of the event handlers in the shared code.
A simple alternative could be that within each view you would wrap the partial view within a containing element that has an identifying css class to differentiate between the behaviour required at that point.
Then assign event handlers individually for those different css classes:
$(document).ready(function() {
//shared variables here for methods
$(document).on('click', 'div.type1 .selectable-table tbody tr', function() {
//do shared actions
mySharedActions();
mySpecificFunction1();
//finish shared actions (if necessary)
});
$(document).on('click', 'div.type2 .selectable-table tbody tr', function() {
//do shared actions
mySharedActions()
mySpecificFunction2();
//finish shared actions (if necessary)
});
});
This would allow you to keep all your specific functions together in one place and makes the changing behaviour predicated by the css class explicit
for future developers to see.
Related
I have a functionality that I had running in the
window.addEventListener('load', function() {
var variable_name_1 = localStorage.getItem('var_1');
...
}
and I would like to move the functionality such that it only runs when the user clicks a button, in here:
function maketempuser() {
...
}
I can get the function to call when I want. But the function utilizes tons of variables from the load function. Is there a clean way to "globalize" these variables? Or must I find some way to add all these variables in the html:
<button ... onclick='maketempuser(variable_name_1, variable_name_2, ...);' >
NOTE: the javascript will run the same file, I just don't want it to keep re-running every time the user reloads the page since there is an ajax mysql insert that occurs because this page is one in a line of pages that enables a user to register.
To not pollute the global scope with a lot of variables (which can be overridden by other apps), I recommend you create an object with an app specific name, maybe something like this
var myAppVar = {};
window.addEventListener('load', function() {
myAppVar.var_1 = localStorage.getItem('var_1');
...
}
Just define them in global scope:
var variable_name_1;
window.addEventListener('load', function() {
variable_name_1 = localStorage.getItem('var_1');
...
}
This, however, is not a particularly healthy technique, since it's prone to name collisions. Best thing to do is have a custom object (cO, or with your initials, something unlikely to be used by anything else) and use it as a placeholder for all your custom vars:
var cS = {
var_1:null // or some default value...
};
window.addEventListener('load', function() {
cS.var_1 = localStorage.getItem('var_1');
...
}
Since localStorage is already global just retrieve the values you need in your handler from there.
function maketempuser() {
var variable_name_1 = localStorage.getItem('var_1');
}
No need to add anything extra to the global scope at all.
I'm developing a client using JQuery based on lightweighted plugin pattern as listed here.
https://github.com/jquery-boilerplate/jquery-patterns/blob/master/patterns/jquery.basic.plugin-boilerplate.js
I've been working on one file, but it's getting bloated with over 1000 lines of code. So I've decided to split scripts, but I haven't been able to locate best practice for keeping multiple scripts with jQuery.
My main script is the following:
;(function($, window, document, undefined) {
function MainClass(){
this.other = new Otherclass(); // Otherclass is defined in separate script
}
MainClass.prototype = {
...
}
$.fn.mainclass = function(){
...
}
})(jQuery, window, document);
HTML is the following:
<html>
<head>
// JQuery included
<script type="text/javascript" src="mainclass.js></script>
<script>
$(function() {
$("body").mainclass();
});
</script>
</head>
</html>
Question: I need to define otherclass on the separate file. What is the best way to accomplish this? If Plugin Pattern wasn't meant to have multiple scripts, are there any other practice suitable for this?
Thank you.
The module pattern that you are using is a good first step in the right direction. The plugin pattern was really intended to encapsulate one specific functionality for a given set of elements and follows the open/closed principle pretty well, by design (open for extension). However, it isn't a good approach for multiple object interaction due to its primary behavior as an extension method of the jQuery object.
One thing that I was able to do to split my JavaScript out into pages/multiple files was to use a combination of Namespacing and Module Augmentation/Importing/Exporting.
The namespacing was great for importing and dereferencing other portions of the application and the module pattern helped with selection of exposure and exporting just the right amount of reusable members of an object. From there, I could dereference any object that was in the namespace, create new instances from that, and so forth:
//In some common site-wide file, declare a common namespace and known base objects within it:
var App = {
View: {},
Utilities: {}
};
// view.js
App.View = (function($, window, document, undefined) {
var localProp = "Hi, i'm a private property for App.View to access";
function doSomething(){
// a private method for use
}
return {
reuseableMethod: function() {
// exported for access through App.View.reusableMethod()
}
};
})(jQuery, window, window.document, undefined);
// another script, more specific, different file
// NOTE: the import and export of App.View and view
(function($, window, document, view) {
// consume your other objects after importing them
var me = Object.create(view);
me.reuseableMethod();
function localFunction() {
//do something private
}
})(jQuery, window, window.document, App.View);
I'm using the so called garber-irish technique for splitting up my javascript files.
My question is, I have a Model (Item say) and have an init function which is in app/assets/javascripts/item/item.js
e.g.
MYAPP.items = {
init: function() {
alert("do something");
}
};
Now.. lets say I have an administration side to this app, and I don't really want to include the admin javascript in the main bulk. So.. I have a different system_adminstration.js which requires the regular javascripts/item/item.js above, but also requires a javascripts/admin/item/item.js which would look something like:
MYAPP.items = {
init: function() {
alert("also do this");
}
};
I want to load both the common javascripts above, and the administation specific ones - effectively merging the two init functions and keeping things nicely dry.
Questions:
Is this a sensible approach?
It it possible?
Keen for comments - but what I have done (for the moment) is change the init function to :
UTIL.exec( "common" );
UTIL.exec( controller );
UTIL.exec( "admin_"+controller );
UTIL.exec( controller, action );
UTIL.exec( "admin_"+controller, action );
(so, I'm adding in an "admin_") and then for the admin javascript files I've simply added in an admin prefix:
MYAPP.admin_items = {
init: function() {
....
Slightly nasty but I think it'll do me until someone has a nicer suggestion!
I'm trying to write 'better' javascript.
Below is one pattern I've found, and am trying to adopt. However, I'm slightly confused about its use.
Say, for example, I've got a page called "Jobs". Any JS functionality on that page would be encapsulated in something like:
window.jobs = (function(jobs, $, undefined){
return {
addNew: function(){
// job-adding code
}
}
})(window.jobs|| {}, jQuery);
$(function(){
$('.add_job').on('click', function(event){
event.preventDefault();
window.jobs.addNew();
});
});
As you can probably deduct, all I've done is replaced all the code that would have sat inside the anonymous event-handler function, with a call to a function in my global jobs object. I'm not sure why that's a good thing, other than it's reduced the possibility of variable collisions and made the whole thing a bit neater, but that's good enough for me.
The - probably fairly obvious - question is: all my event-binding init-type stuff is still sitting outside my shiny new jobs object: where should it be? Inside the jobs object? Inside the return object inside the jobs object? Inside an init() function?
I'm just trying to get a sense of a stable, basic framework for putting simple functionality in. I'm not building JS apps, I'd just like to write code that's a little more robust and maintainable than it is currently. Any and all suggestions are warmly welcomed :)
You can break down your application in whatever number of modules / objects you like too.
For instance, you can have another object / module which caches and defines all your DOM nodes and another one, which just handles any event. So for instance:
(function ( win, doc, $, undef ) {
win.myApp = win.myApp || { };
var eventHandler = {
onJobClick: function( event ) {
event.preventDefault();
myApp.addNew();
}
};
var nodes = (function() {
var rootNode = $( '.myRootNode' ),
addJob = rootNode.find( '.add_job' );
return {
rootNode: rootNode,
addJob: addJob
};
}());
$(function() {
myApp.nodes.addJob.on( 'click', myApp.handler.onJobClick );
});
myApp.nodes = nodes;
myApp.handler = eventHandler;
}( this, this.document, jQuery ));
It doesn't really matter how you create singletons in this (module) pattern, either as literal, constructor, Object.create() or whatnot. It needs to fit your requirements.
But you should try to create as many specific modules/objects as necesarry. Of course, if makes even more sense to separate those singletons / modules / objects into multiple javascript files and load them on demand and before you can say knife, you're in the world of modular programming patterns, dealing with requireJS and AMD or CommonJS modules.
Encapsulation-wise, you're fine: you could even just declare addNew in the jQuery closure and you'd still avoid the global scope. I think what you're getting at is more of implementing something close to an MVC architecture.
Something I like to do is create an object that you instantiate with a DOM element and that takes care of its own bindings/provides methods to access its controls etc.
Example:
// (pretend we're inside a closure already)
var myObj = function(args){
this.el = args.el; // just a selector, e.g. #myId
this.html = args.html;
this.bindings = args.bindings || {};
}
myObj.prototype.appendTo = function(elem){
elem.innerHTML += this.html;
this.bindControls();
};
myObj.prototype.remove = function(){
$(this.el).remove(); // using jQuery
};
myObj.prototype.bindControls = function(){
for(var i in this.bindings){ // event#selector : function
var boundFunc = function(e){ return this.bindings[i].call(this,e); };
$(this.el).on(i,boundFunc);
}
};
The way you are doing it right now is exactly how I do it also, I typically create the window objects inside the anonymous function itself and then declare inside that (in this case: jClass = window.jClass).
(function (jClass, $, undefined) {
/// <param name="$" type="jQuery" />
var VERSION = '1.31';
UPDATED_DATE = '7/20/2012';
// Private Namespace Variables
var _self = jClass; // internal self-reference
jClass = window.jClass; // (fix for intellisense)
$ = jQuery; // save rights to jQuery (also fixes vsdoc Intellisense)
// I init my namespace from inside itself
$(function () {
jClass.init('branchName');
});
jClass.init = function(branch) {
this._branch = branch;
this._globalFunctionality({ globalDatePicker: true });
this._jQueryValidateAdditions();
//put GLOBAL IMAGES to preload in the array
this._preloadImages( [''] );
this._log('*******************************************************');
this._log('jClass Loaded Successfully :: v' + VERSION + ' :: Last Updated: ' + UPDATED_DATE);
this._log('*******************************************************\n');
};
jClass._log = function() {
//NOTE: Global Log (cross browser Console.log - for Testing purposes)
//ENDNOTE
try { console.log.apply(console, arguments); }
catch (e) {
try { opera.postError.apply(opera, arguments); }
catch (e) { /* IE Currently shut OFF : alert(Array.prototype.join.call(arguments, ' '));*/ }
}
};
}(window.jClass= window.jClass|| {}, jQuery));
The reason I leave them completely anonymous like this, is that let's say in another file I want to add much more functionality to this jClass. I simply create another:
(function jClass, $, undefined) {
jClass.newFunction = function (params) {
// new stuff here
};
}(window.jClass = window.jClass || {}, jQuery))
As you can see I prefer the object.object notation, but you can use object literals object : object, it's up to you!
Either way by leaving all of this separate, and encapsulated without actual page logic makes it easier to have this within a globalJS file and every page on your site able to use it. Such as the example below.
jClass._log('log this text for me');
You don't want to intertwine model logic with your business logic, so your on the right path separating the two, and allowing for your global namespace/class/etc to be more flexible!
You can find here a comprehensive study on module pattern here: http://www.adequatelygood.com/JavaScript-Module-Pattern-In-Depth.html It covers all the aspects of block-scoped module approach. However in practice you gonna have quite a number files encapsulating you code, so the question is how to combine them property. AMD... multiple HTTP requests produced by every module loading will rather harm your page response time. So you can go with CommonJS compiled to a single JavaScript file suitable for in-browser use. Take a look how easy it is http://dsheiko.github.io/cjsc/
I have a website with multiple pages, each of them with custom event handlers attached to certain elements. Each page has an id in the html markup (#page1, #page2, ...).
In my javascript file for the website I have seperated the functions for each site in self executing modules, simplified like so:
//module 1 for stuff that happens on #page1
(function() {
// stuff to happen, events, delegations, preparations, etc
})();
I thought I could execute certain page related modules only if the #id is found in the current document like:
if( $("#page1").length ) {
// call module 1
};
... because a lot of event delegation happens in some modules.
Is this a common/good approach to speed up things? Or is it better to include the modules in seperated js files only on the subsites where they are needed? Or any other method/ideas?
Put all the code in a single js file so it is cached. Put an id on the body or similar element, then put each module of code in a separate function. Then have an object to hold module references linked to body element ids, then in the onload:
var modules = {
id1: function() { /* stuff for page 1 */},
id2: function() { /* stuff for page 2 */},
...
}
var id = document.body.id
if (id && id in modules) {
modules[id]();
}
Then you just give the body the id of the function that should run for that page.