Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to organize jQuery/JavaScript code (2013) [closed]

The Problem

This answer has been answered before but are old and not up to date. I have over 2000 lines of code in a single file, and as we all know this is bad practice, especially when i'm looking through code or adding new features. I want to better organize my code, for now and for the future.

I should mention that I'm building a tool (not a simple website) with lots of buttons, UI elements, drag, drops, action listeners/handlers and function in the global scope where several listeners may use the same function.

Example code

$('#button1').on('click', function(e){     // Determined action.     update_html(); });  ... // Around 75 more of this  function update_html(){ .... }  ... 

More example code

Conclusion

I really need to organize this code for best use and not to repeat myself and be able to add new features and update old ones. I will be working on this by myself. Some selectors can be 100 lines of code others are 1. I have looked a bit at require.js and found it kinda repetitive, and actually writing more code than needed . I'm open to any possible solution that fit this criteria and link to resource / examples are always a plus.

Thanks.

like image 350
Kivylius Avatar asked May 24 '13 13:05

Kivylius


People also ask

Is jQuery modular?

jQuery was separated into modules to encourage usage of these subcomponents. If you're extremely concerned about bytes, and you only need a part of jQuery, only declare dependencies on the pieces you need.

Is jQuery open source?

jQuery is a JavaScript library designed to simplify HTML DOM tree traversal and manipulation, as well as event handling, CSS animation, and Ajax. It is free, open-source software using the permissive MIT License. As of Aug 2022, jQuery is used by 77% of the 10 million most popular websites.


2 Answers

I'll go over some simple things that may, or may not, help you. Some might be obvious, some might be extremely arcane.

Step 1: Compartmentalize your code

Separating your code into multiple, modular units is a very good first step. Round up what works "together" and put them in their own little encased unit. don't worry about the format for now, keep it inline. The structure is a later point.

So, suppose you have a page like this:

enter image description here

It would make sense to compartmentalize so that all the header-related event handlers/binders are in there, for ease of maintenance (and not having to sift through 1000 lines).

You can then use a tool such as Grunt to re-build your JS back to a single unit.

Step 1a: Dependency management

Use a library such as RequireJS or CommonJS to implement something called AMD. Asynchronous Module Loading allows you to explicitely state what your code depends on, which then allows you to offload the library-calling to the code. You can just literally say "This needs jQuery" and the AMD will load it, and execute your code when jQuery is available.

This also has a hidden gem: the library loading will be done the second the DOM is ready, not before. This no longer halts load-up of your page!

Step 2: Modularize

See the wireframe? I have two ad units. They'll most likely have shared event listeners.

Your task in this step is to identify the points of repetition in your code and to try to synthesise all this into modules. Modules, right now, will encompass everything. We'll split stuff as we go along.

The whole idea of this step is to go from step 1 and delete all the copy-pastas, to replace them with units that are loosely coupled. So, instead of having:

ad_unit1.js

 $("#au1").click(function() { ... }); 

ad_unit2.js

 $("#au2").click(function() { ... }); 

I will have:

ad_unit.js:

 var AdUnit = function(elem) {      this.element = elem || new jQuery();  }  AdUnit.prototype.bindEvents = function() {      ... Events go here  } 

page.js:

 var AUs = new AdUnit($("#au1,#au2"));  AUs.bindEvents(); 

Which allows you to compartmentalize between your events and your markup in addition to getting rid of repetition. This is a pretty decent step and we'll extend this further later on.

Step 3: Pick a framework!

If you'd like to modularize and reduce repetitions even further, there are a bunch of awesome frameworks around that implement MVC (Model - View - Controller) approaches. My favourite is Backbone/Spine, however, there's also Angular, Yii, ... The list goes on.

A Model represents your data.

A View represents your mark-up and all the events associated to it

A Controller represents your business logic - in other words, the controller tells the page what views to load and what models to use.

This will be a significant learning step, but the prize is worth it: it favours clean, modular code over spaghetti.

There are plenty of other things you can do, those are just guidelines and ideas.

Code-specific changes

Here are some specific improvements to your code:

 $('.new_layer').click(function(){      dialog("Create new layer","Enter your layer name","_input", {              'OK' : function(){                      var reply = $('.dialog_input').val();                      if( reply != null && reply != "" ){                              var name = "ln_"+reply.split(' ').join('_');                             var parent = "";                              if(selected_folder != "" ){                             parent = selected_folder+" .content";                             }                              $R.find(".layer").clone()                             .addClass(name).html(reply)                             .appendTo("#layer_groups "+parent);                              $R.find(".layers_group").clone()                             .addClass(name).appendTo('#canvas '+selected_folder);              }          }      });  }); 

This is better written as:

$("body").on("click",".new_layer", function() {     dialog("Create new layer", "Enter your layer name", "_input", {          OK: function() {              // There must be a way to get the input from here using this, if it is a standard library. If you wrote your own, make the value retrievable using something other than a class selector (horrible performance + scoping +multiple instance issues)               // This is where the view comes into play. Instead of cloning, bind the rendering into a JS prototype, and instantiate it. It means that you only have to modify stuff in one place, you don't risk cloning events with it, and you can test your Layer stand-alone              var newLayer = new Layer();              newLayer                .setName(name)                .bindToGroup(parent);           }      }); }); 

Earlier in your code:

window.Layer = function() {     this.instance = $("<div>");     // Markup generated here }; window.Layer.prototype = {    setName: function(newName) {    },    bindToGroup: function(parentNode) {    } } 

Suddenly, you have a way to create a standard layer from anywhere in your code without copy pasting. You're doing this in five different places. I've just saved you five copy-pastes.

One more:

// Ruleset wrapper for actions

var PageElements = function(ruleSet) { ruleSet = ruleSet || []; this.rules = []; for (var i = 0; i < ruleSet.length; i++) {     if (ruleSet[i].target && ruleSet[i].action) {         this.rules.push(ruleSet[i]);     } } } PageElements.prototype.run = function(elem) { for (var i = 0; i < this.rules.length; i++) {     this.rules[i].action.apply(elem.find(this.rules.target)); } }  var GlobalRules = new PageElements([ {     "target": ".draggable",     "action": function() { this.draggable({         cancel: "div#scrolling, .content",         containment: "document"         });     } }, {     "target" :".resizable",     "action": function() {         this.resizable({             handles: "all",             zIndex: 0,             containment: "document"         });     } }  ]);  GlobalRules.run($("body"));  // If you need to add elements later on, you can just call GlobalRules.run(yourNewElement); 

This is a very potent way to register rules if you have events that are not standard, or creation events. This is also seriously kick-ass when combined with a pub/sub notification system and when bound to an event you fire whenever you create elements. Fire'n'forget modular event binding!

like image 146
Sébastien Renauld Avatar answered Oct 19 '22 01:10

Sébastien Renauld


Here is a simple way to split your current codebase into multiple files, using require.js. I will show you how to split your code into two files. Adding more files will be straightforward after that.

Step 1) At the top of your code, create an App object (or whatever name you prefer, like MyGame):

var App = {}

Step 2) Convert all of your top-level variables and functions to belong to the App object.

Instead of:

var selected_layer = "";

You want:

App.selected_layer = "";

Instead of:

function getModified(){ ... } 

You want:

App.getModified = function() {  } 

Note that at this point your code will not work until you finish the next step.

Step 3) Convert all global variable and function references to go through App.

Change stuff like:

selected_layer = "."+classes[1]; 

to:

App.selected_layer = "."+classes[1]; 

and:

getModified() 

to:

App.GetModified() 

Step 4) Test Your code at this stage -- it should all work. You will probably get a few errors at first because you missed something, so fix those before moving on.

Step 5) Set up requirejs. I assume you have a web page, served from a web server, whose code is in:

www/page.html 

and jquery in

www/js/jquery.js 

If these paths are not exactly like this the below will not work and you'll have to modify the paths.

Download requirejs and put require.js in your www/js directory.

in your page.html, delete all script tags and insert a script tag like:

<script data-main="js/main" src="js/require.js"></script> 

create www/js/main.js with content:

require.config({  "shim": {    'jquery': { exports: '$' }  } })  require(['jquery', 'app']); 

then put all the code you just fixed up in Steps 1-3 (whose only global variable should be App) in:

www/js/app.js 

At the very top of that file, put:

require(['jquery'], function($) { 

At the bottom put:

}) 

Then load page.html in your browser. Your app should work!

Step 6) Create another file

Here is where your work pays off, you can do this over and over.

Pull out some code from www/js/app.js that references $ and App.

e.g.

$('a').click(function() { App.foo() } 

Put it in www/js/foo.js

At the very top of that file, put:

require(['jquery', 'app'], function($, App) { 

At the bottom put:

}) 

Then change the last line of www/js/main.js to:

require(['jquery', 'app', 'foo']); 

That's it! Do this every time you want to put code in its own file!

like image 25
Lyn Headley Avatar answered Oct 19 '22 02:10

Lyn Headley