OJ is a JavaScript library with the goal to make objects that create and live edit the web. So this BulletList object creates itself and then lets you manipulate it:
IMPORTANT: Did you click the button yet? =). All the editors live compile OJ. Click stuff, and change the code to see what happens!
When making a website you have to keep in mind HTML, CSS, and JavaScript code. In OJ, all three parts are unified into objects. OJ can render to HTML and CSS as well as bind JavaScript events.
The benefit is that OJ objects have no dependencies. There is no need to read the docs to be sure the HTML is correct, or see if the CSS is included. Everything just works.
Allow me to demonstrate: In this example, the oj.Button generates its own <button>
tag, and then once created binds a JavaScript click event:
Included in oj.js
are the built-in objects that are the basic building blocks of the web. These include creation and manipulation of the common <html>
elements:
<input type='text'>
<textarea>
<select> and <option>
<input type='checkbox'>
<button>
<ol> and <li>
<ul>
and <li>
<table>
,<tr>
,<td>
, <tbody>
, and more.Once you are comfortable using those, take a look at the objects that come with OJ plugins. The whole point of OJ is to make creating these objects as easy as possible
(Check out createType if you want to learn more about how they are made).
Head to the download page, and get going by downloading oj.js
and any of the plugins currently available.
For inspiration, take a look at some example projects that use OJ client-side or on the command-line.
Yes, jQuery 2.0 is a required dependency of OJ. This is how OJ supports event binding and direct DOM manipulation as objects change. Including this dependency took some soul searching, but OJ lives and breaths DOM creation and manipulation, and that is what jQuery does best.
As for why 2.0 -- this is the version of jQuery made for "modern" browsers, specifically IE9 and above that support the more recent features of JavaScript, HTML5, and CSS3. This browser version requirement also holds for OJ.
Let's start from the beginning and see how OJ objects are made.
Every html <tag>
has an analogous tag function by the same name. For example, <div>
has oj.div
:
Attributes are created by passing objects as arguments. This example defines an href
attribute on an anchor tag:
Order doesn't matter for attributes; add as many as you need, and the attribute objects will be unioned. For example, this <div>
has both an id
and an inline style
attribute:
To make life easier, style attributes can be set with camelCase
in addition to dashed-case
:
Like tag functions, CSS is defined by its own function: oj.css
. It accepts an object of objects:
The css
function fully supports selectors with .classes
, #ids
, @media
queries, and nested definitions with &
. Here is an example with classes and a nested hover selector:
The class attribute is abbreviated as c
. OJ mostly eschews abbreviations, but 'class' is a pretty commonly accessed attribute and is a reserved keyword in JavaScript--so instead of cluttering the code with quotes around "class", OJ lets you use 'c' as well.
Nesting tags can be done a couple ways. The most powerful is to use a function:
At first, the function notation seems a little verbose--but it's worth it because it allows structure to be created through code. Here is the same structure generated with a for loop:
Just to show the power, here is FizzBuzz =):
OJ tag functions have built-in support for jQuery events. If any event is passed as an attribute it will be automatically bound. Here, the oj.button
tag function binds click
, mouseenter
, and mouseleave
events:
The supported list of jQuery events are:
Here is an example of keydown
/ keyup
:
In addition OJ supports an insert
event. It is specified the same way as the jQuery events above and triggers when a tag element is inserted in the page.
This example shows an element triggering a fade-in animation when insert
is detected:
Just to be clear insert
doesn't use the deprecated DOMNodeInserted or any of the clever hacks that have emerged using CSS animation. Instead, OJ has full control over DOM element insertion so it can call events at the right time without special magic.
OJ objects follow the standard JavaScript convention of capitalizing objects' class names. Hence oj.button
is the tag function but oj.Button
is the Type. The biggest difference is that objects remember their dom element so they can change themselves or even remove themselves from the page.
For example this BulletList instance can edit itself once inserted.
Notice that these objects don't use new. One of OJ's goals was to make Objects that can be used exactly like tag functions--so using div feels just the same as using a YouTubeVideo!
Using 'new' has a special effect in OJ: it creates an element without emitting it--that is, without immediately adding it to the DOM. Once you're ready to inset the element, just call 'emit' and it'll appear!
This lack of emitting is actually quite useful for when you want to customize an object and render it later. Take for example placing an AceEditor inside of a Table:
In OJ, partials are just functions. Let's create a function that makes twitter links:
Just like partials, templates are also functions in OJ. The difference is that partials create parts of websites and templates create basic structure to fill in the rest.
Lets create a template function that renders a header and takes the body and title as an argument:
In the MVC sense, OJ is entirely focused on creating Views and nothing else. It doesn't create Models. It doesn't create Controllers or Routing, or help with Page Navigation or Server Syncing. Instead it tries to do only one thing well: make awesome View objects.
That said, a good View should update from Models and vice versa. OJ supports two-way model binding to Backbone models by default. It depends on nothing other then on-off-trigger event syntax, so creating adapters to bind to other model systems should be straightforward.
There are three kinds of model binding supported by OJ: ModelKeyView, ModelView, and CollectionView. The first is used by form elements to bind to a part of a model, the second binds to an entire model, and the last binds to a collection of models.
Here we use ModelKeyView binding to link several controls together to the key of name
. Changing one will change the model, which will then trigger a change in the rest:
Another type of binding is CollectionView binding. This is used most often to bind a collection of models to a List or Table, which inherit from CollectionView.
Similarly to Model Binding by default OJ understands binding to Backbone Collections. In this example, let's render a List from a collection of users:
Let's use the same data to render a Table. The only difference here is that the each function can set the cells directly.
Included in OJ is a sophisticated type system inspired by CoffeeScript. This lets you create types with:
That said, OJ types have two big differences from CoffeeScript types:
OJ types work in JavaScript (CoffeeScript types only work in CoffeeScript)
This is the big reason why OJ doesn't just use CoffeeScript types (though they are pretty great). OJ works in pure JavaScript and keeps the syntax as clean as possible.
OJ types directly support first-class properties. So if you choose, you can define properties with get / set methods to better abstract your data access.
This brings front and center my favorite "new" feature of JavaScript, first-class properties (ejohn.org). Even though properties have been in JavaScript since 2009, some devs still aren't aware of them. OJ builds these kind of properties right in.
As an example of this powerful type system, let's create a Rectangle type that has properties width
and height
, with a readonly first-class property area
:
Plugins can be made in OJ by using createType and inheriting from View.
In this example a new view is created called OrangeButton
. This view contains default styling with CSS, and editing the label
property will change its text.
(Clearly this example is a bit contrived, but it does show how createType unifies HTML, CSS, and JS into a new View Object).