A common pattern observed in javascript writing is to wrap your javascript code in a closure to control scope and namespace your "objects" to prevent variables being overwritten.
File structure looks like...
- /javascript
- house-app.js
- house-initializer.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function() { | |
// initial | |
this.HouseApp = { | |
Models: {}, | |
Controllers: {} | |
}; | |
var House = HouseApp.Models.House = function(address) { | |
this.address = address; | |
this.doors = {}; | |
this.windows = {}; | |
}; | |
House.prototype.addDoor = function(door, location) { | |
this.doors[location] = door; | |
}; | |
House.prototype.addWindow = function(window, location) { | |
this.windows[location] = window; | |
}; | |
HouseApp.Models.Door = function(color) { | |
this.color = color; | |
}; | |
// private util used only here and scoped by the closure | |
var convertSize = function(size) { | |
return size * 2 | |
}; | |
HouseApp.Models.Window = function(size) { | |
this.size = convertSize(size); | |
}; | |
var HouseController = HouseApp.Controllers.HouseController = function(house) { | |
//initializer | |
this.house = house; | |
}; | |
HouseController.prototype.render = function() { | |
//rendering... | |
}; | |
HouseController.prototype.save = function() { | |
//save house | |
}; | |
}).call(window); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function() { | |
this.HouseApp = function() { | |
var house = new window.HouseApp.Models.House; | |
var aWindow = new window.HouseApp.Models.Window(52); | |
var aDoor = new window.HouseApp.Models.Door(32); | |
house.addDoor(aDoor); | |
house.addWindow(aWindow); | |
this.houseContoller = new window.HouseApp.Controllers.HouseController(house); | |
}; | |
}).call(window); |
With this approach, there are a couple of problems.
- Javascript file load order becomes important - if script block (a) depends on something from script block (b), script block (b) must be ensured to load before (a). In a complex app/site - you can end up with hundreds of javascript objects and managing one large file can become burdensome. Also, if you break these into multiple files, circular dependencies can become an issue.
- All javascript in the closure will always always run when the files are loaded (when #call is executed).
- Global scope is still relied heavily on to attach all "objects" to be used.
To solve these problems I recommend using a module pattern in developing your javascript such as CommonJS or AMD. If you have used node.js, then you have used the CommonJS implementation and if you have used "require.js", then you have used the AMD implementation of the module pattern.
I like the permise of the AMD implementation which provides a convention to asynchronously load required javascript files from a server as this prevents the need to have a large payload initial sent to a page to get up and running (faster page load time) but this advantage becomes moot after someone has visited the page once (with caching). Also, creating a javascript "app" that can easily run in an offline mode becomes more challenging because you have to deal with multiple files in a cache manifest file, etc. However, require.js has a solution to this and provides a utility to create one javascript file but relies on node.js to work (potentially introduces an additional dependency into your development/deploy env) and introduces another step into your deploy if using another asset bundling solution (asset pipeline, jammit, etc).
The way I prefer to handle the module pattern implementation is based on the CommonJS spec and is based off a node.js package called "stitch". In using this solution, the only thing you have to worry about in load order is your initial library dependencies (stitch-header.js, jquery.js, backbone.js, etc) and then the rest of your javascript can be loaded in any order. This simplifies specifying your javascript includes for your packaging solution and ensures that javascript only gets executed when necessary.
Here is the example from above using the the CommonJS pattern.
File structure now looks like
File structure now looks like
- /javascript
- stitch-header.js
- /app
- house-app.js
- /models
- house.js
- window.js
- door.js
- /controllers
- house_controller
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function(/*! Stitch !*/) { | |
if (!this.require) { | |
var modules = {}, cache = {}, require = function(name, root) { | |
var path = expand(root, name), indexPath = expand(path, './index'), module, fn; | |
module = cache[path] || cache[indexPath] | |
if (module) { | |
return module; | |
} else if (fn = modules[path] || modules[path = indexPath]) { | |
module = {id: path, exports: {}}; | |
cache[path] = module.exports; | |
fn(module.exports, function(name) { | |
return require(name, dirname(path)); | |
}, module); | |
return cache[path] = module.exports; | |
} else { | |
throw 'module ' + name + ' not found'; | |
} | |
}, expand = function(root, name) { | |
var results = [], parts, part; | |
if (/^\.\.?(\/|$)/.test(name)) { | |
parts = [root, name].join('/').split('/'); | |
} else { | |
parts = name.split('/'); | |
} | |
for (var i = 0, length = parts.length; i < length; i++) { | |
part = parts[i]; | |
if (part == '..') { | |
results.pop(); | |
} else if (part != '.' && part != '') { | |
results.push(part); | |
} | |
} | |
return results.join('/'); | |
}, dirname = function(path) { | |
return path.split('/').slice(0, -1).join('/'); | |
}; | |
this.require = function(name) { | |
return require(name, ''); | |
} | |
this.require.define = function(bundle) { | |
for (var key in bundle) | |
modules[key] = bundle[key]; | |
}; | |
this.require.modules = modules; | |
this.require.cache = cache; | |
} | |
return this.require.define; | |
}).call(this) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
window.require.define("app/models/house": function(exports, require, module) { | |
var House = function(address) { | |
this.address = address; | |
this.doors = {}; | |
this.windows = {}; | |
}; | |
House.prototype.addDoor = function(door, location) { | |
this.doors[location] = door; | |
}; | |
House.prototype.addWindow = function(window, location) { | |
this.windows[location] = window; | |
}; | |
module.exports = House; | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
window.require.define("app/models/door": function(exports, require, module) { | |
var Door = function(color) { | |
this.color = color; | |
}; | |
module.exports = Door; | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
window.require.define("app/models/window": function(exports, require, module) { | |
// private util used only here and scoped by the closure | |
var convertSize = function(size) { | |
return size * 2 | |
}; | |
var Window = function(size) { | |
this.size = convertSize(size); | |
}; | |
module.exports = Window; | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
window.require.define("app/controllers/house_controller": function(exports, require, module) { | |
var HouseController = function(house) { | |
//initializer | |
this.house = house; | |
}; | |
HouseController.prototype.render = function() { | |
//rendering... | |
}; | |
HouseController.prototype.save = function() { | |
//save house | |
}; | |
module.exports = HouseController; | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
window.require.define("house_app": function(exports, require, module) { | |
var Window = require("app/models/window"); | |
var Door = require("app/models/door"); | |
var House = require("app/models/house"); | |
var HouseController = require("app/controllers/home_controller"); | |
var HouseApp = function() { | |
var house = new House; | |
var aWindow = new Window(52); | |
var aDoor = new Door(32); | |
house.addDoor(aDoor); | |
house.addWindow(aWindow); | |
this.houseContoller = new HouseController(house); | |
}; | |
module.exports = HouseApp | |
}); |
Then to use the above the house app in the particular page that needs the house app, put a small script block at the top of the page that reads, "var HouseApp = require('house_app'); window.app = new HouseApp;"
No comments:
Post a Comment