Modular Javascript with Packages JS

Packages JS

One script, two functions, three steps to modular javascript.

Packages in Javascript

Packages JS provides modularity and scope management in javascript by allowing you to create packages that explicitly state their dependencies and the external objects they use through the function Package, and make created functions and objects/classes available for use by other packages through the function Export.

Modular javascript in three steps

  1. Download Packages JS and include it in your page
  2. Place your code in packages using Package
  3. Make functions and objects available to other packages using Export

Function package

SYNOPSIS
Package(name [, dependencies, callback])
name
package name. Required. [String]
EG: "mypackage" or "my.common.lib".
dependencies
Array with names of packages that your package depends on. Optional. [Array]
EG: ["lib1", "lib2"]
callback
Function to be called when the dependencies have been resolved. Optional, required when dependencies are specified. [Function]
EG: function(import1, import2) { /* inline function */ }
imports
You can import objects into the local scope by defining arguments for the callback function with names matching those of objects exported by packages in your dependencies list.
EXAMPLES

Example 1 (registration only)

// code that needs to be in the global namespace
Package("my.package");

Example 2 (no dependencies)

// recommended form for code without dependencies
Package("my.package", [], function() {
  // code here is in a local scope
  function greet(){ alert("hello"); }
  Export(greet); // make available to other packages
});

Example 3 (import dependencies)

Package("other.package", ["my.package"], function(greet) {
  // Use greet from my.package here.
  greet(); // alerts 'hello'
});

Function Export

SYNOPSIS
Export(obj, [name])
obj
Object to export for use in other packages. Required. [Any]
EG: Cat or myFunction
name
Name of the object to export. Required. [String]
EG: "Cat" or "example.animals.Cat"
A fully qualified name consists of the package name and the object name separated by a dot. This form can always be used and is convenient for overriding the default location of the exported object, or for using this function from outside the context of a package callback.
Short names or no name at all can only be used if this function is called from within the context of an executing package callback function (i.e. from within your package). The object will be exported to your package's scope. This is the recomended form.
When the name argument is not supplied, the one-class-per-file form is assumed to be used. In this case the object is exported AT the package instead of IN it.
EXAMPLES

An object, function or class exported in one package...

Package ("example.animals", ["lang.Class"], function(Class) {
  var Cat = Class.create(..);
  Export(Cat, "Cat");
});

...becomes available to other packages in the global scope, under it's fully qualified name:

Package("some.package", ["example.animals"], function() {
  var Cat = example.animals.Cat;
  var felix = new Cat("Felix");
});

...but can also be injected in the local scope by Packages JS:

Package("other.package", ["example.animals"], function(Cat) {
  var felix = new Cat("Felix");
});

In the code above, note how Cat is injected as an argument to the package callback function.

If you leave out the optional name argument, the object will be exported AT the package scope instead of IN it. This variation can only be used from within the context of a package callback, and only once per package (obviously). This syntax is convenient for creating a one-class-per-file package structure:

Package("example.animals.Dog",["lang.Class"], function(Class){
  var Dog = Class.Create({bark: function(){alert("woof");} });
  Export(Dog); // no name param
});

In the above example, Dog becomes available at example.animals.Dog, not at example.animals.Dog.Dog, but can still be imported in the same way as other exported objects:

Package("any.package", ["example.animals.Dog"], function(Dog){
  var lassie = new Dog("Lassie");
  lassie.bark(); // alerts 'woof'
});

Packages included in the download

These packages are not needed to use Packages JS. They serve as an example and contain some functions that come in handy a lot. A lot of code in it is written by other authors.

log.firebug

Before I do anything in Javascript, I like to have some way of logging what is happening in my code. Fortunately it looks like a logging API for javascript is finally emerging, based on an object named 'console'. Both Safari and Firefox now support a common log API through console.log(), console.info(), console.warn() and console.error(). They are not fully compatible yet, but it's being worked on. Let us hope other browser vendors jump on the bandwagon so we may finally get native cross-browser javascript logging. Until that time, Firebug Lite will allow us to see our logging messages in other browsers.

The 'void' log console from firebugx is integrated into packages.js. It's an empty console object that's included in browsers that have no native console so you can safely use the console logging methods cross-browser. If you actually want to see the log in other browsers, include log.firebug on your page, like I did with this one. It's a javascript implementation of the console that firefox and safari have. Press F12 to open the console, it appears at the bottom of the page.


lang.Class

Prototype's new class framework is available separately in the package lang.Class. In version 1.6, Protototype's class framework has become more intuitive to use than in earlier versions. Learn more about it in Mislav's tutorial.

lang.Class is just the original code from prototype with slight modifications to allow it to work stand-alone. But importantly, the API is still compatible with Prototype's. So if you write a package using lang.Class, that package will work without change on Prototype. In fact, Prototype is bundled with Packages JS as framework.prototype, which forms a drop-in replacement for lang.Class.

dom.event

The dom.event package contains a compact version of the two cross- browser event functions addEvent and removeEvent that have built-in support for DOMContentLoaded, a non-standard but very usefull event that fires before onload but after the document is ready. Inspired by code from Dean Edwards, Tanny O'Haley and the guys from Prototype JS.

dom.Behaviour

Use Ben Nolan's Behaviour module to add behaviours transparently to your page.

Interestingly enough, when packaging dom.Behaviour I could remove two thirds of the file by specifying dependencies on dom.event, and dom.select. It shows how easy scripts collected from the internet can be integrated into your codebase by using Packages JS.

Click here for an alert   |   Mouseover here to change this text.

dom.select

dom.select contains Simon Willison's document.getElementsBySelector, which allows you to query the document for all elements matching a CSS selector. I renamed the function "select" and added an Export statement for it, that's it.

framework.prototype

An unmodified version of prototype 1.6.0.3, except for a couple of lines added at the end that registers both lang.Class and framework.prototype, making this package a drop-in replacement for lang.Class.

net.QueryString

Class that helps inspecting and manipulating QueryStrings.

net.URL

Class that helps inspecting and manipulating URL's. Uses net.QueryString.

Check the log for the results of inspecting some test url's.

example.animals

Example of what can be done with lang.Class. Creates classes Animal, Cat and Mouse that extend Animal and HouseCat which extends Cat. It then exports these classes for use in the package example.animaltest.

example.animaltest

Puts the example animals to the test. Included on this page, the example creates some animals and feeds them different kinds of food. Check out the log console to see who likes what (or who!).

example.behaviourtest

Example of what can be done with dom.behaviour. Included on this page, the example creates an onclick and onmouover and onmouseout handlers. See dom.behaviour for a live demo.

example.consoletest

Example that demonstrates the cross-browser log console. Included on this page, the example writes the value in the textbox to the log console, using the selected log level.

example.eventtest

Example that demonstrates cross-browser event functions addEvent and removeEvent by attaching handlers to the "load" and "DOMContentLoaded" events. Check the log for the results.

example.urltest

Example that demonstrates packages net.URL and net.QueryString.