h1. Readme h2. About Ninjs is a command line application written in ruby that leverages the "Sprockets":http://getsprockets.org JavaScript compiler to create modular javascript applications without having to compile your scripts manually. Ninjs also contains a "'Good Parts'":http://www.amazon.com/JavaScript-Good-Parts-Douglas-Crockford/dp/0596517742/ref=sr_1_1?ie=UTF8&qid=1294628522&sr=8-1 JavaScript framework to encourage best practices like name-spacing and modular separation. h2. Installation You can install Ninjs using RubyGems. This is the easiest way of installing and recommended for most users.
$ gem install ninjsIf you want to use the development code you should clone the Git repository and add the binary to your path:
$ git clone git://github.com/textnotspeech/ninjs.git $ export PATH=/path/to/ninjs/bin:$PATHh1. Create a Ninjs application
$ ninjs create myapplicationThis will create a Ninjs application in the current working directory. Now we can create Ninjs modules by adding them in the modules directory. h1. Create a Ninjs module Create a module file in the /modules directory. By convention, we'll name the file with a suffix of .module. An example of a module named hello would look like this: /modules/hello.module.js The basic functionality of a module is to encapsulate specific logic into a container. Think of a module as a class in the sense that it allows to name-space properties and methods. A Ninjs module is an extremely lightweight object that contains a very simple api which helps you write clear, concise code. The following is the bare minimum you need to have a working module a.k.a Ninjs' "Hello World":
myapplication.add_module('hello'); myapplication.hello.actions = function() { this.say_hello(); }; myapplication.hello.say_hello = function() { alert('Hello World'); }; myapplication.hello.run();The run method will execute the actions method. Please note that the "run" method will wait for the DOM to be ready before it is executed. If you wish the actions to be executed immediately, you may call the execute method like so:
myapplication.hello.execute();This pattern allows you to write in a literate style while making your intentions clear and methods succinct. However, if you prefer a shorter syntax or make your module completely protable (transplant to any application), Ninjs defines two aliases to work with your application and modules that are more terse. Your application object will be aliased as "_" (underscore). When using either the run or execute methods to call the module's actions, the module will be available through the "__" alias. The previous hello module would look like the following:
_.add_module('hello'); _.hello.actions = function() { __.say_hello(); }; _.hello.say_hello = function() { alert('Hello World'); }; _.hello.run();This not only makes the module less cumbersome to write, it also allows you to port modules directly into other Ninjs application's and run them without any renaming. Remember one underscore is a reference to the application and two underscores is a reference to the current module. You may ask why we are calling say_hello() in the actions method instead of just alerting the string in actions itself. Let's set aside the fact that this is a trivial example and assume we will be adding many more methods to the module. If we simply added all of our module code inside actions, we'd quickly have a soup of code in there which would be difficult to follow. The Ninjs javascript framework encourages syntactic clarity. It's preferable for the actions method to be a list of methods called in the module. This let's my module tell a consistent story from top to bottom. The actions method serves as a table of contents. Consider a slightly more sophisticated hello module:
myapplication.add_module('hello'); myapplication.hello.actions = function() { this.define_properties(); this.say_hello(); }; myapplication.hello.define_properties() = function() { this.greeting = 'Hello'; this.name = 'World'; } myapplication.hello.say_hello = function() { var message = this.greeting_string(); alert(message); }; myapplication.hello.greeting_string = function() { return this.greeting + ' ' + this.name + '!'; }; myapplication.hello.run();We can see what this module does by simply glancing at the actions method. From there, if methods are kept short and follow the "single responsibiliy principle":http://en.wikipedia.org/wiki/Single_responsibility_principle, it will be easy to follow and test. h2. Create module elements Another common best practice that Ninjs encourages is cacheing your element selectors. For example, when using jQuery to select a DOM element, it's best practice to assign the result of the selection to a variable in case you need it again. Here's what it looks like in practice:
// Bad no-caching $('#some-element').css({ 'background-color': '#FF0000' }); $('#some-element').html("I turned red"); // Good caching var some_element = $('#some-element); some_element.css({ 'background-color': '#FF0000' }); some_element.html("I turned red");When we cache our selections, we only have to search the DOM once, improving performance. The only problem with this is that we tend to manipulate a lot of selections and our code can become littered with them. At worst, they're strewn about the file wherever they are first used, making it easy to accidentally "re-cache" them. At best, all selections are cached in one place and easy to see, which prevents us from accidentally caching them twice. Ninjs goes a step further by putting these cached selectors in their own file in the elements folder. Elements belong to a module and can be added using the elements method. To add elements to the hello module, let's add a hello.elements.js file in the elements folder. Next add the elements to the module with the elements method:
myapplication.hello.elements(function() { this.message_box = $('#message-box'); });And that's it for the elements code. We've added a cached element with the id of "message-box" to our module. All there is left is to add the elements to the module using the "Sprockets require directive":http://getsprockets.org/installation_and_usage#specifying_dependencies_with_the_require_directive
myapplication.add_module('hello'); //= require '../elements/hello.elements.js' ...Be sure to require the elements file after the "add_module" method is called. Now all elements defined in the elements method will be available to the module. Let's take our hello example and instead of alerting the greeting, let's put it in the message_box element (assuming an html page with this element):
... myapplication.hello.say_hello = function() { var message = this.greeting_string(); this.message_box.html(message); }; ...Again, this pattern keeps the logic very clear and our code very concise. It's easy to read, test, and refactor. Be careful when naming your cached elements, be sure you're not overwriting another property of the module. With time you'll develop your own naming conventions and standards. The important thing is to focus on good semantic names that accurately describe the properties and behavior of your application. Most modules will be exactly like the one we just created, only with more methods. However, there is one more piece that helps you achieve greater modularity, which is Ninjs models. h2. Create a Ninjs model Ninjs models are simply files in the models directory that define a data structure. By convention models are simply object literals that are useful for reusing inside your modules and throughout your application. Let's suppose I have multiple "jQueryUI dialog windows":http://jqueryui.com/demos/dialog/ that I want to share a certain default configuration. Instead of creating an options object each time I call dialog on an element, I can use a model. Let's see how this might look in our hello example. Let's create the model in /models/hello.model.js:
myapplication.hello.set_data('dialog_settings', { width: 300, height: 150, autoOpen: false });The set_data method will add the dialog_settings object to the module's data property (one of the only default module properties). You could add objects directly to the data property, but the set_data method has more syntactic clarity. You can think of this method as setting the modules "instance" variables. Next we include the model in the module:
myapplication.add_module('hello'); //= require '../elements/hello.model.js' //= require '../elements/hello.elements.js' ...Now whenever I create a dialog in my module, I can use the dialog_settings object like so:
// assumes we have elements "error_dialog" and "notice_dialog" // defined in the elements file myapplication.hello.create_dialogs = function() { this.error_dialog.dialog(this.data.dialog_settings); this.notice_dialog.dialog(this.data.dialog_settings); }This way we don't have to keep redefining the same properties each time we call dialog. If we want to modify the defaults, we can use jquery's merge method
// assumes we have elements "error_dialog" and "notice_dialog" // defined in the elements file myapplication.hello.create_dialogs = function() { this.error_dialog.dialog(this.data.dialog_settings); this.notice_dialog.dialog($.extend(myapplication.hello.data.dialog_settings, { height: 300, autoOpen: true })); }The model provides a default base that we can build from, helping use to keep our code DRY. There's also an opportunity here to illustrate the utility of the "ninja" aliases. Notice that inside the dialog function we are unable to use the this.dialog_settings shorthand. Using "this" in Javascript can be a bit tricky. In this case, "this" is referring to the dialog and not the module. That's why Ninjs defines the double underscore ( __ ) variable to refer to the current module. The previous example can be rewritten more succinctly like so:
MyApplciation.hello.create_dialogs = function() { __.error_dialog.dialog(__.dialog_settings); __.notice_dialog.dialog($.extend(__.dialog_settings, { height: 300, autoOpen: true })); }h1. Compiling the application Now that we have a complete module including elements and a model, we need to compile these files into one coherent file to use in our html. To do so we have 2 options. Open a terminal window (command prompt) and navigate to the root of your Ninjs application. We can compile our application with one of 2 commands. The first choice is the compile command. From the root of your Ninjs application type:
$ ninjs compileThis will compile all the files in the modules folder, resolving all dependencies using the Sprockets engine, and finally outputting them into the application directory with the .module suffix removed. Our hello example module would compile into the application folder as hello.js. Now we can include the hello.js file (along with the myapplication.js) in our html document. Since running compile every time we make a change to any one of our module source files would quickly become a tedious chore, Ninjs also provides a watch command which will watch your root directory for changes and automatically compile when a file is changed. This speeds up development considerably and frankly makes Ninjs usable in a daily development context. To watch a Ninjs project simply navigate to the project root and issue the watch command:
$ ninjs watchThat's the basics of creating a Ninjs application!