a thought provoking Javascript experiment.

WARNING This is experimental framework exploring alternative ways of creating javascript applications.
It is intended as food for thought. It is not ready for real world use cases!


The framework's design goals include:

Forget about the DOM
Stop using selectors to target elements, they're evil!
Pure Javascript
We're not writing a Web App, its a Javascript app!
JSON templates
They're supported natively, efficient to type and read nicely.
Two-way data binding
Abstract your data into a model and let the framework provide consitency with the UI.
Run your code in the browser, on a server, or a bit of both!
Desktop Native
Serve your Javascript application as a native desktop application!
Dynamic Script Loader
The framework should load your application for you.
Persistent Routing
Changing route and posting data between routes should be simple and survive page reloads.
Easy to Test
Tests are important, there should be no reason to avoid them.


If you want to jump straight in and see a demo, take a look at an implementation of todo-mvc in PajamaJS:

Show Me

Component Overview

Pajama.js consists of 6 main components:

Encapsulate functions and properties that will bind to a View. Models also contain any relevant application level logic.
Contain JSON templates, they provide all DOM abstractions and deal with two-way data binding with Models.
Construct and combine Elements to build up complex UI.
Handle top level routing logic and manage application state. They construct Models and Views.
Facilitate communication between models and/or controllers, avoiding unnecessary coupling.
Connect URLs to Controllers enabling application navigation.


To define a model, it must be identified by name:

To create an instance of a model, reference it by name:

Technically speaking, models can be substituted for any generic object. The wrapper is provided to enable dynamic script loading and for consistency with Views.


The Basics

This is how to create a new element:

An elements tag defaults to div, it can be changed by passing a tag option into the constructor:

Most other options are simply converted into attributes:

Classes must be added to an element like this:


Elements can be appended to one another:

Throwing around the new pjs.element constructor gets tiring, the same behaviour can be had by simply dropping it:

There are two other overrides for making it easier to stack together elements:

In addition to append, there is also prepend:


To remove an element from it's container, there is a remove function:

To remove all children from a container, there is a clear function:


To list all child nodes of a container, there is a getNodes function:

The underlying DOM element can (but shouldn't!) be accessed like this:

To get or set an attribute on an existing element:

DOM Events and Inputs

There is a helper to force an event to fire:

Finally, there are helpers for form inputs to access values:

Two Way Data Binding (basic)

To set up a two-way data binding, pass in an object instead of a string as an option value. In the next example, value: { price: myModel } should read as "the `value` should bind to the `price` property of `myModel`".

If anything updates the model, the text in the input box will update immediately. If the text in the input box changes, the value of the model will update immediately. If anyone is wondering, one update will never trigger the other.

To bind a Model's function to an event on an element it is exactly the same as setting up a two-way data binding, only the named property on the model should resolve to a function. In the next example, click: { alert: myModel } should read as on `click`, invoke `alert` on `myModel`.

When the named event on the element is triggered, the function on the model will be invoked within the scope of the model.

Taking things further, this next example sets up a button and a div, whereby clicking the button fires an event, which triggers the bound function increment, which updates the counter property on model, which is bound to the innerHTML property of the div. Beautiful.

Two Way Data Binding (arrays)

We can also bind arrays to an element in order to dynamically alter its contents. In this next example there is a library model which has a books property containing a list of book titles. The element definition should read "the `div` contains the `books` in `library` forEach `book` return ...".

The book variable within the anonymous function is an item within library.books. The return value of the anonymous function is passed into a constructor to pjs.element when gets appended to the container.

As library.books gets mutated, the contents of elem automagically update to match. Pop()ing an item from the array will remove the corresponding DOM element, Push()ing a new item into the array will create a new DOM element, and so on.

Once we're using views, there is an alternative way of using contains by specifying a usingView property. Instead of running each object through the forEach function, it gets passed into a new view with the resulting element being appended to the container:

There is a big in-line demo of this example at the very bottom of the page.


To define a view, it must be identified by name:

To create an instance of a view, reference it by name:

The typical use case for a view is to initialise other views or elements and append them to the instance. If all bound elements are attached to the element as properties, it will ease testing:


To define a controller, it must be identified by name:

To create an instance of a controller, reference it by name:

Technically speaking, controllers can be substituted for any function or object. The wrapper is provided to enable dynamic script loading and for consistency with Views.


An event bus is provided to allow models and controllers to interact with each other without retaining object references.

This is how to listen for an event:

This is how to a listen for a one-off event:

Triggering an event:

Here's an example of using an Event to convert a button click into a redirect:


The intial entry point for every project is the invokation of pjs.defineRoutes. The first param should be the DOM node to contain the project. The second param should be a list of routes.

Each route should be a json object containing two properties, a path and a controller name:

When the routing layer invokes a controller, the first argument passed in is the DOM node wrapped up inside a pjs.element. Any params passed around, either as a part of the URL (GET style) or otherwise (POST style) will stack up as additional arguments.

Data passed around in the routing will persist through page reloads and browser navigation.

Moving backwards and forwards in the browser will replay each route, complete with all associated GET-style and POST-style objects.

Project Structure

The Todo-MVC implementation looks like this:


Script Loader

After routes have been initialised, Pajama.js attempts to pull down every named controller from expected locations. Each time a new file is loaded it is scanned for additional dependencies and those too are loaded. This cycle continues until all name dependencies have been loaded.

Throughout this process, the script loader prints debugging into to the console.

Here is the console output for the TodoMVC demo:

Serverside Rendering

Running PajamaJS serverside to serve up static content:

Instances of pjs.element can be converted into flat markup via the .toMarkup() function.

Clientside Inversion

This is extremeley experimental. You can find the full demo in the GitHub repository, execute it with node ./demo/inverted/server.js and navigate to http://localhost:8080

This example will start a HTTP server running on the named host and port. It responds to every request with some very basic javascript which will initiate a SocketIO connection back to the server and send the current URL up the websocket. The request will be routed through PajamaJS into one of the controllers and from that point onwards, the clients DOM will synchronise with the serverside DOM (represented by the 'body' object).

Native Applications

This is extremeley experimental. You can find the full demo in the GitHub repository, execute it with node ./demo/native/sampleApp.js


Does all this work in your browser? Scroll down to find out!

In-Line Demo