PK ({)Bc\ \ obviel-1.0b3/genindex.html
Here is some information on how we develop Obviel and how you could participate.
Please talk to us our on our mailing list about your plans!
Obviel’s source code is maintained on bitbucket: http://bitbucket.org/obviel/obviel
You can check it out using Mercurial (hg); see the bitbucket documentation for more information as well.
Feel free to fork Obviel on bitbucket if you want to hack on it, and send us a pull request when you want us to merge your improvements.
The Obviel tests use Buster.JS, a powerful test runner that can run a complete browser test suite from the command line.
To run the tests you need to install Buster.JS as a Node.js module. See the Buster.JS getting started tutorial for more information. You can ignore the part about creating a config file to the project and below, as Obviel already has a config file.
In addition to installing Buster.JS, you also need to install buster-lint, an extension, into the Obviel project directory, like this:
$ npm_install buster-lint
Now to run the tests you first need to set a buster server. You do this in Obviel’s project directory:
$ buster server
It will mention starting a test server on http://localhost:1111
Go there and capture your browser as a test slave to the buster server. You can also connect more than one browser if you want, to run the tests in different browser environments.
Now on a different terminal you can run the tests, again in Obviel’s project directory:
$ buster test
Note that the Obviel test configuration also has integration with JSHint, so that any JSHint issues in your code will be automatically reported too.
Obviel’s documentation is written using restructured text and Sphinx.
To build the documentation of Obviel you need Python 2.6 or Python 2.7. First you need to install buildout, a tool that installs some useful scripts for us. In the Obviel project directory, type the following:
$ python bootstrap.py -d
$ bin/buildout
After you’ve done this once, you can build the documentation using Sphinx:
$ bin/sphinxbuilder
The docs source is in doc, the built documentation will be available in doc/_build/html.
Note that the src directory in its entirety is also copied into the documentation tree under _static. This is done to make it easy to publish demo code as part of the website.
Discussions on the use and further development of Obviel happen on the Obviel mailing list: obviel@googlegroups.com
You can subscribe here: https://groups.google.com/group/obviel
Obviel Template is a template language for Obviel. Obviel Template is deliberately a very minimal template language. The idea is that more complicated logic should be kept in JavaScript code, not templates, because it is much clearer to express it in JavaScript. You can do this by preprocessing the object that goes into the template, or by using data-func in the template.
Obviel Template is different from many template languages you might have encountered in that it is element-based and HTML aware, not text-based. Templates must be valid HTML fragments. If you try to construct a template with elements in places where they are not valid HTML, you will find that the browser will not produce that HTML. The advantage of being element-based is that the template language can directly operate on the DOM. You can for instance directly bind event handlers in the template language.
Obviel Template integrates with Obviel in an important way: sub-views can be rendered directly from the template. This allows these views to work in the standard Obviel way. They can for instance respond to events on the objects that they are representing, and rerender themselves. This is another benefit of Obviel Template being element-based.
Another powerful feature of Obviel Template is that it supports i18n markers directly in the template for the purpose of internationalization (i18n) of template texts. This is based on standard gettext, and supports pluralization. This way automatic extraction tools can be used to maintain translations of strings in the template. This feature exists in some server-side template languages (such as Zope Page Templates), but this is not commonly supported by client-side template languages.
Obviel-Template supports variable interpolation using {} markers in any element or attribute text. It looks like this:
<p>{content}</p>
If you combine that template with a JavaScript object with the following structure:
{
content: "Hello world!"
}
it will result in a rendered template like this:
<p>Hello world!</p>
What if the content property is not available in the JavaScript object, and is thus undefined? Obviel Template considers this an error and the template will fail with a RenderError exception.
Objects can be nested:
{
a: {
b: "The value"
}
}
You can indicate such nested values in a variable using a dotted name:
<p>{a.b}</p>
Rendering the template with the object will result in this:
<p>The value</p>
If you refer to a dotted name that does not exist in the data, this is an error and the template won’t render; you get a RenderError exception.
Dotted names work whenever a variable can be used, so also works in other directives such as data-with, data-if and data-each, which we will discuss later.
In Obviel Template, special variables start with @, and @. means “the current object”. This can be useful for debugging templates: you can see what the current object is somewhere. The current object will be serialized to JSON into the rendered template.
Consider this data:
{
title: "Foo"
}
and this template:
<div>
{@.}
</div>
Rendering the template with the data will result in this:
<div>
{
title: "Foo"
}
</div>
You can change the object in which variables are found with the data-with directive. Given the same nested structure as before:
{
a: {
b: "The value"
}
}
you can express the template like:
<div>
<p data-with="a">{b}</p>
</div>
Rendering the template with the object will result in this:
<div>
<p>The value</p>
</div>
data-with="a" indicates that within the p element, lookups will be performed in the b object primarily (that is the scope of that element).
data-with must point to an object; it cannot point to strings or arrays or other values, and cannot point to a missing value either: these are RenderError errors.
Sometimes you want to include an element only if a given value is true. For the purposes of data-if, a value is false if:
Otherwise, the value is considered true.
With !, the behavior of data-if can be reversed: values that evaluate to false will be included.
Consider this template:
<p data-if="person">Hello {person.name}!</p>
<p data-if="!person">{person.name} is not present</p>
Here’s a data structure with person present:
{
person: {
name: "Evan"
}
}
rendering the template will result in this:
<p>Hello Evan!</p>
but if you have a data structure without person present, such as this:
{
}
the template will render like this:
<p>Evan is not present</p>
If you point to an array with data-each, it will render each entry in the array into the DOM. So with a data structure like this:
{
persons: [
{
name: "Bob"
},
{
name: "Steven"
}
]
}
and a template like this:
<ul>
<li data-each="persons">{name}</li>
</ul>
the result will be this:
<ul>
<li>Bob</li>
<li>Steven</li>
</ul>
Note that data-each changes the scope to each entry in the array. So, data-each changes the object in which the variables are looked up to each entry in the array, one by one.
If the array is empty, nothing will be rendered at all. So, this data:
{
persons: [
]
}
will result in this:
<ul>
</ul>
You can use the special variable @. to render simple arrays where each item is not an object in itself. For instance:
{
persons: [
"Bob", "Steven", "Jay"
]
}
and this template:
<ul>
<li data-each="persons">{@.}</li>
</ul>
will render like this:
<ul>
<li>Bob</li>
<li>Steven</li>
<li>Jay</li>
</ul>
data-each supports the special variable @each. This variable contains some useful properties during the construction of loops:
For example:
<ul>
<li data-each="persons">{@each.index}</li>
</ul>
will render (with three items in the list) as follows:
<ul>
<li>0</li>
<li>1</li>
<li>2/li>
</ul>
To support nested use of data-each, @each also supports a variable named after the variable that is currently being looped through, in this example persons:
<ul>
<li data-each="persons">{@each.persons.index}</li>
</ul>
which will do the same as the previous template.
Let’s look at how this is used in a nested data-each. Here’s the data:
{outer: [
{inner: ['a', 'b', 'c']},
{inner: ['d', 'e']}
]}
And here’s a template:
<ul>
<li data-each="outer">
<ul>
<li data-each="inner">
value: {@.} outer: {@each.outer.number} inner: {@each.inner.number}
</li>
</ul>
</li>
</ul>
If you used this template with the data, you would get this output:
<ul>
<li>
<ul>
<li>
value: a outer: 1 inner: 1
</li>
<li>
value: b outer: 1 inner: 2
</li>
<li>
value: c outer: 1 inner: 3
</li>
</ul>
</li>
<li>
<ul>
<li>
value: d outer: 2 inner: 1
</li>
<li>
value: e outer: 2 inner: 2
</li>
</ul>
</li>
</ul>
What if the variable data-each goes through is a dotted name - how is it used with data-each? You can addres it by replacing the periods by underscores. For instance, given this data:
{
something: {
persons: ["Bob", "Steven", "Jay"]
}
}
and this template:
<ul>
<li data-each="something.persons">{@each.something_persons.number}</li>
</ul>
you will get this as a result:
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
So to address something.persons in a loop, you use something_persons, with the . replaced by _.
To support various user interface you want to add a class into the DOM depending on a some @each value, such as even or odd. While you can do this with data-attr (which we’ll see later), we recommend you use data-func for this (which we’ll also see later), as your code will generally be clearer.
Values in outer scopes are available for variables in inner scopes. Consider this structure of objects:
{
a: {
b: "The value"
},
c: "Outer value"
d: {
e: "Value in outer object"
}
}
In the scope a, we can still access c and d (and even a again):
<p>b in outer scope: {a.b}</p>
<p>c in outer scope: {c}</p>
<p>e in outer scope: {e}</p>
<p data-with="a">b in scope a: {b}</p>
<p data-with="a">c in scope a: {c}</p>
<p data-with="a">e in scope a: {d.e}</p>
<p data-with="a">b in a in scope a: {a.a.b}</p>
But if you redefine a name in an inner scope, you cannot access the value in the outer scope:
{
a: {
b: 'redefined'
}
b: 'original'
}
<p data-with="a">b in scope a: {b}</p>
will therefore get you:
<p>b in scope a: redefined</p>
Consider the following data:
{
greeting: "Hello",
who: {
iface: 'person',
name: "Pat Pending",
url: "http://www.example.com/pat_pending"
}
}
and this template:
<p>{greeting} <span><a data-with="who" href="{url}">{name}</a></span></p>
it will render like this:
<p>Hello <span><a href="http://www.example.com/pat_pending">Pat Pending</a></span></p>
This works. But we could also arrange our code so that we have a view for the person iface and use that instead. The advantage of that is that we could reuse this view in other places, and in addition we can use Obviel view features, such as binding its methods to DOM events (which we’ll see later). Let’s consider a view:
obviel.view({
iface: 'person',
obvt: '<a href="{url}">{name}</a>'
});
This is a view for the iface person. We use the Obviel Template (indicated by obvt) to render the view.
Now we can change our template to render who as a view:
<p>{greeting} <span data-view="who"></span></p>
The result is still the same:
<p>Hello <span><a href="http://www.example.com/pat_pending">Pat Pending</a></span></p>
Another advantage of using data-view in this case is that you have made the outer template more generic. If the object indicated by the name who has another iface, the template will still work unchanged. For example, here is a data structure where who is a robot:
{
greeting: "Greetings",
who: {
iface: 'robot',
designation: "OVL-R 4711",
}
}
and this is a view for the iface robot:
obviel.view({
iface: 'robot',
obvt: '{designation}'
});
and here is our outer template again, unchanged:
<p>{greeting} <span data-view="who"></span></p>
when we render the data now, we get this:
<p>Greetings <span>OVL-R 4711</span></p>
in other words, if you add data-view to an element el to render object who, Obviel Template will do the equivalent of this:
$(el).render(who);
Sometimes you have multiple views for an iface, distinguished by name. You can indicate the name used by data-view with a | character. Here’s another view for person with the name extended:
obviel.view({
iface: 'person',
name: 'extended',
obvt: 'The person has the name <a href="{url}">{name}</a>'
});
and here is how you refer to this extended view:
<p>{greeting} <span data-view="who|extended"></span></p>
By default, data-view looks up a view named default – the default view name for Obviel. If you want to change the way templates look up views to use another view globally you can do so in JavaScript:
obviel.template.setDefaultViewName('summary');
From now on if you don’t specify the view name explicitly, Obviel will look up views by the name of summary by default. If you decide to do this, you would want to do this once per web page before template rendering takes place, for instance when the page has just been loaded. Note that this does not alter the default view name of Obviel itself when you use el.render(obj); this will remain default.
data-view can also point to a variable that is a string, not an object. In this case the variable is treated as a URL, and this is loaded as a JSON object. The view is then rendered for that object.
data-view must point to an object or a string. It cannot point to an array or other value.
If you need to insert HTML snippets, use data-view. If you must manipulate text in some consistent way, you can use formatters. Formatters can be registered globally with Obviel Template like this:
obviel.template.registerFormatter('upper', function(value) {
return value.toUpperCase();
});
This registers a formatter upper which when used will turn any string variable to upper case. Here’s how we would use it in a template:
<p>{foo|upper}</p>
if we render this with the data structure:
{foo: 'hello'}
we’d get the following output:
<p>HELLO</p>
We’ve seen a way to register formatters globally. Formatters can also be defined as methods on the view that uses the template:
obviel.view({
iface: 'foo',
obvt: '<p>{foo|upper}</p>',
upper: function(value) {
return value.toUpperCase();
}
});
Again, formatters cannot be used to include HTML, only text; for including HTML you can use views. To do some special DOM manipulation, you can also consider using data-func.
If you are registering global formatters, you should register formatters only once per web page, before template rendering starts.
Often in a view you want to handle particular DOM events for elements in the view. You can bind DOM events to methods of the view using the data-handler directive.
For instance, if you want to bind a handler for the click event on a div, you can do it like this:
<div data-handler="click|someFunction">Click here!</div>
When the user now clicks on the div element, the function indicated by someFunction will be executed. someFunction is a method on the view that uses this template. Let’s look the example in a view:
obviel.view({
iface: 'foo',
obvt: '<div data-handler="click|someFunction">Click here!</div>',
someFunction: function(ev) {
alert("You clicked!");
}
});
To bind more than one event handler on an element at the same time, separate them by a space character:
<div data-handler="click|onClick dblclick|onDblclick">Click here!</div>
It is a RenderError if the handler function is not available on the view during render time.
Obviel Template has element-based directives (data-if, data-each, etc). But sometimes you just want to conditionally or repeatedly include a piece of text, not an element. You can do this by using a special data-unwrap element. During template rendering all elements marked by data-unwrap disappear.
So:
Hello? <span data-unwrap="" data-if="again">HELLO?</span>
with this data:
{ again: true }
will render like this:
Hello? HELLO?
and with again set to false:
{again: false}
will render like this:
Hello?
Normally you would generate an element by simply including it in the template, for instance a p element like this:
<p>Hello world</p>
In some cases you want to be able to dynamically decide the name of the element based on the data. data-el lets you do this. This is the equivalent of the above template using data-el:
<div data-el="p">Hello world</div>
When this is rendered, you’ll see this just like with the first example:
<p>Hello world</p>
What this lets you do is dynamically generate element names:
<div data-el="{myElementName}"></div>
You could also use data-repeat to generate a whole series of elements:
<div data-el="{@.}" data-repeat="tags"></div>
Normally you’d generate a dynamic attribute like this:
<a href="{url}">{title}</a>
but sometimes this isn’t good enough: in particular, we sometimes want to be able to generate attributes that are only there if some condition is true. Obviel Template has a special data-attr directive that lets you do this. The equivalent of the above example using data-attr looks like this:
<a><span data-attr="href" data-value="{url}"/>{title}</a>
What will happen when this template is rendered is the same as in the first example:
<a href="/some/url">Some Title</a>
data-attr will always apply to the element it is directly contained in. Note that the element that has the data-attr on it itself is never inserted in the result.
This construct is helpful because you can use data-if for conditional attributes now:
<a><span data-if="flag" data-attr="href" data-value="{url}" />{title}</a>
Now, the href attribute will appear only if flag is true.
In combination with data-each this can be used to dynamically generate multiple attributes from data:
<a><span data-each="attributes" data-attr="{name}" data-value="{value}"/>{title}</a>
That template, combined with this data:
{
title: "Hello world",
attributes: [ {name: 'href', value: 'foo'},
{name: 'class', value: 'bar'} ]
}
would result in the following HTML:
<a href="foo" class="bar">Hello world</a>
If you generate the same attribute multiple times the content is added to the existing attribute, with a space character for a separator. This is for instance useful when you want to add multiple classes dynamically to an element and have some show up only if a condition is true. This:
<div>
<span data-attr="class" data-value="{first}" />
<span data-attr="class" data-if="flag" data-value="{second}" />
</div>
combined with this data:
{
first: 'first',
second: 'second',
flag: true
}
will result in this output:
<div class="first second"></div>
but with this data:
{
first: 'first',
second: 'second',
flag: false
}
you’d get this output:
<div class="first"></div>
But as we say in the sidebar, consider using data-func instead in these cases.
In many cases it is more convenient to use JavaScript and jQuery to manipulate the DOM directly than it would be to use Obviel Template constructions such as data-attr. Obviel Template offers a special directive called data-func that lets you plug custom functions into the template rendering process directly.
To see what data-func does, let’s consider a simple case first. Let’s imagine these two individuals:
{
iface: 'person',
name: "Jane"
wild: false
}
{
iface: 'person',
name: "Tarzan",
wild: true
}
We now want to render a person but want to add a wild class to an element in the view only if that person is wild. We could do this in a relatively cumbersome way, with data-attr and data-el:
obviel.view({
iface: 'person',
obvt: '<div><span data-if="wild" data-attr="class" data-value="wild" />{name}</div>'
});
Let’s rewrite this to use data-func and a method on the view instead:
obviel.view({
iface: 'person',
obvt: '<div data-func="attributes">{name}</div>',
attributes: function(el) {
if (this.obj.wild) {
el.addClass('wild');
}
}
});
Especially for more complicated logic, this is often a lot more easy to understand.
The method called by data-func gets an optional second argument, variable. This is a function you can use to get to variables in the same scope as the template does where data-func was defined. Let’s rewrite the view to use variable:
obviel.view({
iface: 'person',
obvt: '<div data-func="attributes">{name}</div>',
attributes: function(el, variable) {
if (variable('wild')) {
el.addClass('wild');
}
}
});
It’s also possible to register a data-func function globally. It will look for these if it cannot find it on the view itself. Here is a silly example that adds a magic attribute:
obviel.template.registerFunc('magic', function(el) {
el.attr('magic', 'Magic!');
});
Once this registration has been made, we can use it within any template, for instance this one:
<p data-func="magic">Hello world!</p>
And once the template is rendered, we’ll see this:
<p magic="Magic!">Hello world!</p>
data-func is especially useful in combination with data-each. Let’s consider the case where we want to add an even and odd class to each iteration. We could write a template like this:
<ul>
<li data-each="list">
<p data-if="@each.even" class="even">{@.}</p>
<p data-if="@each.odd" class="odd">{@.}</p>
</li>
</ul>
But this template has various problems:
We could also use dynamically generated attributes to solve this case:
<ul>
<li data-each="list">
<p>
<span data-if="@each.even" data-attr="class" data-value="even"/>
<span data-if="@each.odd" data-attr="class" data-value="odd" />
{@.}
</p>
</li>
</ul>
This would also allow us to generate the class attribute on the li:
<ul>
<li data-each="list">
<span data-if="@each.even" data-attr="class" data-value="even"/>
<span data-if="@each.odd" data-attr="class" data-value="odd" />
<p>{@.}</p>
</li>
</ul>
This a reasonable solution. But it’s still pretty verbose, and it’s just so easy to express a rule like this in JavaScript with jQuery:
var evenodd = function(el, variable) {
if (variable('@each.even')) {
el.addClass('even');
} else {
el.addClass('odd');
}
};
You could have this evenodd function on the view that needs it. But in case of a generally useful functionality like this you can plug it in globally for the application:
obviel.template.registerFunc('evenodd', evenodd);
And then we can use it on p:
<ul>
<li data-each="list">
<p data-func="evenodd">{@.}</p>
</li>
</ul>
Or to use it to add the class to li directly:
<ul>
<li data-each="list" data-func="evenodd">
<p>{@.}</p>
</li>
</ul>
We can now also support more complex situations, where we don’t want to add a class attribute on even or odd, but every third item:
var everythird = function(el, variable) {
if (variable('@each.index') % 3 === 0) {
el.addClass('third');
}
};
obviel.template.registerFunc('everythird', everythird);
and then use it:
<ul>
<li data-each="list" data-func="everythird">
<p>{@.}</p>
</li>
</ul>
everythird cannot be expressed using Obviel Template alone, but is easy to express using a custom fuction.
So using data-func you can supplement Obviel Template with functionality particular to your views or application. This is useful for particular functionality your view needs, or for general functionality your whole application needs.
There are some restrictions on data-func. You should only use data-func to change the element, for instance by manipulating its attributes, not to add or remove child elements (or text-nodes). Most DOM manipulations by adding, moving or removing elements or text modes are unsafe: it can break Obviel Template in unexpected ways, as it relies on the order of child nodes to stay the same. But if you want to insert a whole bunch of elements you should consider using data-view instead.
Since Obviel templates use HTML and the HTML compiler internally, Obviel templates should be valid HTML fragments.
In valid HTML, an id attribute should be unique. If the id attribute is hardcoded there is no problem; you can just use them:
<div id="foo"></div>
But variables in ids are not allowed, because it would be too easy to have a HTML fragment with the same id (such as {myId}) in multiple id attributes. So this is illegal:
<div id="{myId}"></div>
If you try this, you’ll get an compilation error.
To generate an id dynamically, you can instead use the data-id directive:
<div data-id="{myId}"></div>
This works just like any other attribute, so you can put things before and after the variable:
<div data-id="prefix{something}postfix"></div>
This will generate HTML with proper id attributes.
A similar story applies to the src attribute. Let’s consider the img element. When the template is being parsed, the image referred to by src is loaded by the browser immediately. So this in your template would be okay:
<img src="myimage.png" />
But this is illegal:
<img src="{myImage}" />
because we wouldn’t want the browser to look for a URL {myImage} literally. Obviel Template prevents this by giving you a compilation error when you try this.
Instead, you can use data-src to set the src attribute:
<img data-src="{myImage}" />
Obviel Template offers the ability to write a template that can be shown in other languages as well: you can internationalize a template. Here we will discuss the basics of marking up a template so it can be translated, but much more detail is in a separate document about template i18n.
If we have a template like this:
<p data-trans="">Hello world!</p>
we can also show this template in another language, such as Dutch, if the appropriate translation is available:
<p>Hallo wereld!</p>
data-trans is used to mark up those parts of the template that should be translatable, such as attributes and texts.
A translatable text can contain variables as well:
<p data-trans="">Hello {who}!</p>
The data-trans attribute without content is used to mark the textual content of a tag for translation. If you want to mark an attribute as translatable you can do it like this:
<div data-trans="title" title="Hello" />
You can also mark multiple attributes:
<div data-trans="title other" title="Hello" other="Something" />
You can indicate attributes and textual element content with the special name .:
<div data-trans=”. title” title=”Hello”>Contents</div>
Read Obviel Template i18n for much more detail.
The order of execution of operations on an element is as follows :
For example, you could combine directives like this:
<p data-if="a" data-each="b" data-with="c" data-view="d"></p>
This means that if the value a is present and true, the p element will be included in the rendered template (at all). After this, for each of the entries in b (so b scope, iteratively), we render the object d in the c scope with a view.
This order holds no matter in what order you define the attributes.
data-trans is at the same level as data-view. It’s not allowed on an element with data-view.
data-attr and data-func are executed last for an element, and finally elements are transformed using data-el or data-unwrap.
Injecting the characters { and } can be done using a special markup:
{@open}
{@close}
You can also inject the pluralization marker || literally like this:
{@doublepipe}
The HTML CDATA section support in browsers is so inconsistent we didn’t think it was worth spending time supporting this in templates, given its limited utility. Obviel Template therefore has undefined behavior in the presence of CDATA sections in your template: we recommend you don’t use them. If you want to insert extended sequences of text in an element you can already do so with a simple variable.
Processing instructions are also not supported.
We are working on building up demo code for Obviel.
Here is an extended todo list application. This is an Obviel implementation of the TodoMVC application.
Here’s a form demo.
Here is a demo of some Obviel patterns, along with jGrowl integration.
Here’s a demo of i18n of JavaScript with jsgettext.
Obviel is a client-side web framework for jQuery. For a JavaScript object you render a view on an element: Ob-vi-el. Obviel supports the construction of dynamic, rich client-side web applications.
Obviel’s core is a model/view abstraction that helps you improve the structure of your jQuery-based JavaScript applications. On top of that Obviel adds a lot of features, such as templating, i18n support, form generation and validation, and routing. Obviel stays close to HTML but lets you build sophisticated components when you need to.
Obviel has a client-side templating language built-in. This template language has easy-to-use but extensive i18n support so you can offer your web application in multiple languages. It also integrates well with Obviel core, supporting sub-view rendering and easy event handlers.
Obviel lets you create a web UI that supports more than one language. It uses a standard gettext-based approach for i18n. Obviel comes with a server-side toolchain that can be used to extract translatable text from both JavaScript code as well as templates, and to prepare translations for publication on the web.
Obviel comes with a client-side form library. Describe your form using JSON structures, either on the client or on the server. Get and post your form data as JSON. Use composite and repeating fields with ease!
Obviel contains an easy to use routing library called Traject, which can be used both to resolve paths to objects as well as construct paths for objects. With Traject you can construct dynamic single-page user interfaces that work with hyperlinks and the browser back button.
Obviel is powerful. Obviel is also unobtrusive. You only need to learn a small, powerful core API to unlock the power of Obviel. Obviel puts minimal demands on your JavaScript objects. They can be plain JavaScript objects on the client. The objects could also be coming from a web server.
Obviel doesn’t just say REST is cool and then go through the motions. An Obviel-based app can start with a single URL and find out about the rest of your application’s URLs by following hyperlinks in objects. You can also swap between hyperlink and sub-object transparently. And that’s RESTful.
Obviel lets you build dynamic, loosely coupled web applications. Your JavaScript code will be partitioned into small, loosely coupled cooperating components. Obviel also supports loose coupling between the web server and the client application. You can change your server code and your client UI will adjust itself without changing a line of JavaScript. You can also modify JavaScript code without worrying too much about the server.
Look at demo code.
Read the Obviel documentation.
Check out the Obviel API.
Questions? Comments? Join the Obviel community!
Want to contribute to Obviel? Read our notes on development.
You have a project that uses Obviel for its user interface, and now you want it to work in multiple languages. For this you need to adjust your application to support multiple languages, a process known as “internationalization”, or “i18n” for short (as “internationalization” has 18 letters between the ‘i’ and the ‘n’).
How do you go about this?
The idea is that you mark up all translatable texts in your project’s code (.js and .obvt files and .html files with embedded templates) in a special way. When your application runs, marked text is looked up in a translation source. If a translation is found for a marked up piece of text, it is used instead of the original text. By changing the locale you can change which translation sources are in use.
A special extraction tool can be used to extract the marked up pieces of text from .js, .html and .obvt files, so that they can be given to human translators for translation.
Now let’s go into some more detail. We’ll discuss the JavaScript i18n strategy in detail here. We recommend however that you use Obviel Template for your i18n purposes, as it’s often more maintainable to mark up templates instead of code. Read Obviel Template i18n for much more detail on how to mark up your .obvt files for i18n. Then come back here and look at how to work with Obviel Template in the context of extraction tools and obviel.i18n.
Imagine we have this piece of JavaScript code we want to i18n:
alert("Hello world!");
This application has been written with one language in mind, in this case English. Now we want to modify the application so that if the application runs with another language (for instance French) we will see the popup in that language (for instance “Bonjour monde!”) instead of in English.
You can make this happen using obviel.i18n. This uses the gettext approach, which is very often used for i18n for a variety of projects and programming languages. obviel.i18n is built on top of jsgettext, so include jsgettext like this:
<script type="text/javascript" src="/path/to/Gettext.js"></script>
and you need to include obviel-i18n itself:
<script type="text/javascript" src="/path/to/obviel-i18n.js"></script>
We now need to make a translation source available for our Hello world! message. This is what that looks like:
var fr_FR = obviel.i18n.translationSource(
{'Hello world!': 'Bonjour monde!'});
obviel.i18n.registerTranslation('fr_FR', fr_FR);
Here we create a translation source from a simple JavaScript object using translationSource, and then register this source using registerTranslation. Later we’ll see how to load translations from a URL source.
Next we will create a function _ that can be used to mark up text to make it translatable:
var _ = obviel.i18n.translate();
The idea is that we use obviel.i18n.translate() once on top of each of the modules we want to i18n.
Now let’s return to our JavaScript code to i18n:
alert("Hello world!");
We adjust it to use our _ function:
alert(_("Hello world!"));
When you run your application now, you’ll still see Hello world!. Without a specific locale set the texts embedded in the code itself will be used. But earlier we registered the locale fr_FR, and we can alter the locale to that:
obviel.i18n.setLocale('fr_FR');
Now when we run the program we’ll see Bonjour monde!.
Here is a demo that has put all this together in a slightly more involved way.
Obviel, both in JavaScript as well as templates, supports a way to interpolate variables into translations. This could for instance be the English source of the translation:
Hello {who}!
The translator now needs to take care to include this variable in the translation as well:
Bonjour {who}!
A translator could decide it makes more sense to move the variable to another place in the text:
{who}, bonjour!
What is retrieved as the translation will still contain the variable {who}. We need to make sure it gets filled in.
To do so you can use a special variables() formatting function that Obviel i18n makes available:
obviel.i18n.variables(_("Hello {who}!"), { who: "Bob" });
Note that for variables to work you do need to include obviel-template.js on the page as well.
Obviel i18n, thanks to jsgettext, also supports plural forms of a piece of text.
First let’s introduce a program that contains some plural forms:
alert(count + " cows");
Given a value for count of 2, this will show 2 cows, and if you supply 10 you’ll get 10 cows. So far so good. But what now when we only have 1 cow? We’d see 1 cows, which is incorrect. We want to see 1 cow. The plural form for 1 in English is different than that for any other number.
Let’s consider another language, Dutch, which has the same pluralization rule, but different pluralization. We’ll set up a translation:
var nl_NL = obviel.i18n.translationSource(
{'1 cow': ['{count} cows', '1 koe', '{count} koeien']});
obviel.i18n.registerTranslation('nl_NL', nl_NL);
Here we see that 1 cow should be translated as 1 koe, and {count} cows, where count is any other number than 1, should be translated as {count} koeien. The exact syntax of how we specify this directly in the translation source is not important, as later on we’ll discuss tools to generate translation source JSON automatically.
To enable pluralization for JavaScript code you write the following somewhere on top of the module:
var ngettext = obviel.i18n.pluralize();
Naming this function ngettext is important, as it enables the extraction tools to extract plural information automatically.
We will now modify our original program as follows:
alert(variables(ngettext('1 cow', '{count} cows', count), {count: count}));
If we keep the locale to the original, we’ll see, for count 1, 1 cow, and for count 3 for instance we’d see 3 cows. This is because without a specific locale, the texts embedded in the source will be used. ngettext will return its first argument if the count variable (the third argument) is 1, and otherwise will return the second argument. We then use variables to interpolate count variable in the result.
Now we’ll change the locale to Dutch:
obviel.i18n.setLocale('nl_NL');
You’ll now see 1 koe and 3 koeien instead (for count 1 and 3), as we expected.
Some languages have simpler or more complicated pluralization rules. Some languages for instance have no separate singular and plural forms. Other languages instead have 3 or more plural forms and different rules about when they are used.
Let’s examine Polish for instance. In Polish there are 3 plural forms, the singular form, a second form used for numbers ending with the digits 2, 3 or 4, and a third form for numbers (besides 1) that end with 5 to 9 or 1.
Let’s consider the following program:
alert(variables(ngettext('1 file', '{count} files', count)), {count: count});
Let’s consider count [1, 2, 4, 5, 21, 22, 25] and look at the output for English:
1 file
2 files
4 files
5 files
21 files
22 files
25 files
With Polish, we want the output to be like this:
1 plik
2 pliki
4 pliki
5 pliko'w
21 pliko'w
22 pliki
25 pliko'w
We can specify the Polish rule when registering the source in the Plural-Forms metadata:
var pl_PL = i18n.translationSource({
'': {
'Plural-Forms': 'nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)'
},
'1 file.':
['{count} file.',
'1 plik.',
'{count} pliki.',
"{count} pliko'w."]});
If we register this as the translation source for pl_PL and set the locale to that, we’ll get the output as expected.
That’s a scary Plural-Forms entry! Luckily there is tool support for these. When you maintain the translations in .po files like we detail below, the msginit tool can automatically generate these for you based on the locale. There is of course the possibility that it doesn’t know the plural forms for the locale you specify to msginit. It will generate:
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
In that case you will have to put in an appropriate rule yourself. For more information about plural form rules, see this page with rules for plural forms for a whole range of languages, and this launchpad page which has textual descriptions of plural forms for many languages.
What if you have an application that has translatable content, and you want to use a library that has translatable content of its own? Obviel Forms is an example of such a library, as error messages displayed in the form need to be translatable.
In such a case we want to maintain the application translations and the library translations separately from each other. The translations for the library after all would typically be maintained by the library maintainers, not by the application maintainers.
You can take care of this by using a separate translation domain for each library and application. By default, obviel.i18n uses the default domain, but let’s change this now. Let’s alter our basic “Hello world” application so that it uses a separate domain:
var fr_FR = obviel.i18n.translationSource(
{'Hello world!': 'Bonjour monde!'});
obviel.i18n.registerTranslation('fr_FR', fr_FR, 'hello_world_app');
var _ = obviel.i18n.translate('hello_world_app');
alert(_("Hello world!"));
As you can see, we now pass an extra argument 'hello_world_app' when registering the translation as well as when we create the translation function _. This string indicates the domain. You can specify a domain for registered translations. You can also specify that domain for your module, indicating that it will look for translations in that domain. When you use _, the domain will be implicitly used for lookups.
If you define views they will pick up the domain implicitly as well. Any Obviel Template associated with that view will use that domain to look up translation. You can also explicitly pass a domain property to the view when you define it.
If you use obviel.i18n.pluralize in a module you’ll have to specify the domain there as well:
var ngettext = obviel.i18n.pluralize('hello_world_app');
Obviel Template pluralization will pick up the domain from the obviel.i18n.translate declaration already, so if you only use Obviel Template pluralization in a module, and don’t need to use ngettext there is no need to make the obviel.i18n.pluralize call.
We now know how to mark up our JavaScript code so it can be translated. We need a tool that can extract all pieces of marked up text automatically so that we can give it all to a translator.
We recommend the use of the Babel i18n tool with code that uses Obviel. While Babel was written to support i18n of Python applications, it also supports JavaScript applications.
If you’re on Linux, Babel may be available in your Linux distribution. You can also install it manually using one of the Python tools used for this (pip, easy_install, or buildout). After installation, a pybabel commandline tool will be available.
To make Babel work with .js files we first need to configure it. Create a .cfg file with the following content:
[javascript: **.js]
extract_messages = _
Now you can use it on your project directory:
$ pybabel extract -F myconfig.cfg project_directory > myproject.pot
You will now have a myproject.pot (PO template) file that will be the template for all the actual translation files for your application, the .po files. For example, if you had applied this to the simple demo described above, you would get a .pot file like this (skipping some metadata boilerplate):
msgid "Hello world!"
msgstr ""
In the comment line it points out where in the code the text to translate was found; this is sometimes useful context for translators.
For the plural forms example with files you’d see this in your .pot file:
msgid "1 file"
msgid_plural "{count} files"
msgstr[0] ""
msgstr[1] ""
We’ve demonstrated the extraction procedure for a .js file, but what about Obviel Template .obvt files?
We provide a plugin to Babel called babel-obviel that knows how to extract translatable text from .obvt files. You install it using the standard Python installation tools. Make sure it is installed in the same Python as Babel is, so that Babel can pick up on this plugin.
Imagine you have this template, marked up for translation:
<p data-trans="">Hello world!</p>
We need to teach pybabel extract about .obvt files in the .cfg file we created before:
[javascript: **.js]
extract_messages = _
[obvt: **.obvt]
When you now run pybabel extract on your project, it will extract translatable texts not only from .js files but from .obvt files as well.
Read Obviel Template i18n for much more detail on how to mark up your .obvt files for i18n.
It is possible to embed an Obviel template in a script tag in a HTML file. You can tell pybabel to extract from these too, by modifying the .cfg file to read like this:
[javascript: **.js]
extract_messages = _
[obvt: **.obvt]
[obvt_html: **.html]
For each language that you want to support in your project, you now need to create a .po file from the project’s .pot file. You can do this with the GNU gettext tool suite, using the msginit tool (details about msginit):
$ msginit -l fr_FR -i myproject.pot -o myproject-fr_FR.po
This command says we want to create a specific .po file for the French language locale (fr_FR). This is what the generated .po file looks like:
#: src/demo/i18n-js-demo.js:15
msgid "Hello world!"
msgstr ""
This is in fact very similar to our .pot file!
We’ve ignored metadata here, but in the metadata you’ll see the Plural-Forms section for that locale if msginit knows about it. See the Different pluralization rules section for more information on this.
We can now edit the file to add the French translations:
#: src/demo/i18n-js-demo.js:15
msgid "Hello world!"
msgstr "Bonjour monde!"
In practice, you would likely give this file to someone else: the person translating the application to French. They can then edit it directly to add or update the translations, or use some GUI tool that can work with .po files. When they are done, they would give the file back to you.
When your project evolves, you will likely add new texts to translate, or change or remove existing ones. You can use the babel extract tool as usual to extract a new .pot file containing all the current translation texts. You can then use the GNU gettext msgmerge tool (details) to update your existing .po files so that the new translatable texts become available to your translators:
$ msgmerge -U myproject-fr_FR.po myproject.pot
We now have a .po file with the right translations. To make it work with Obviel i18n, we need to convert it to a JSON file that can be used as a translation source. You can do this using the pojson tool (installable using one of the Python methods such as pip, easy_install or buildout).
Using pojson you can convert the .po file to a simple .json file:
$ bin/pojson convert myproject-fr_FR.po > myproject-fr_FR.json
Put that .json file somewhere where it can be found. Normally that would be in the same directory as the .js files.
Now we need to specify what translations are available. We do this in a JSON file which we give the .i18n extension by extension. This .i18n file is normally put in the same directory as the .js files as well, and therefore also in the same directory as the translation JSON files.
Let’s imagine we have an English translation that simply uses the source language, and a French translation that does have a JSON file available. The .i18n file would look like this:
{
"default": [
{
"locale": "en_US",
"url": null
},
{
"locale": "fr_FR",
"url": "myproject-fr_Fr.json"
]
}
We’ve registered the information for the default domain in this example, but normally you’d register it for your application’s domain or library’s domain.
Now we need to load this .i18n information. We do this using a <link> tag in the web page that needs the translations:
<link href="path/to/some.i18n" ref="i18n" type="application/json" />
You can include as many such link tags as you like, typically one per library you have installed that uses obviel.i18n.
Now you need to load the link tags. You do this when the document is ready, so like this:
$(document).ready(function() {
obviel.i18n.load().done(function() {
obviel.i18n.setLocale('fr_FR').done(function() {
... start application ...
});
});
});
Let’s examine this structure. When the document is ready, we load the i18n information. When the i18n information is known, we set the locale. This in turn will then look up the i18n information for the supplied locale. Once the locale is set, we can start up the rest of the application, for instance by rendering the view for the application object.
Want to know more? Go on to read the Obviel core manual.
Table of Contents
So Obviel promises a better structure for your JavaScript applications.
What does Obviel really do? Obviel lets you associate views with JavaScript objects and DOM elements. You decompose your application into loosely coupled views. The view’s task is to render the object on an element into the browser DOM. To do this, a view will contain a template or some JavaScript code, or both. This interplay of object, view and element is central to Obviel. It also inspires its name, Ob-vi-el.
To write an application that uses Obviel you have to do the following:
All this is pretty abstract, so let’s go into some more detail to try to make it more clear.
First you need to know how to include Obviel on a web page. You need to make sure that src/obviel.js is published somewhere on your web server. You also need jQuery, and src/obviel-template.js.
To include Obviel, you first need jQuery as a dependency:
<script type="text/javascript" src="/path/to/jquery-1.7.2.js"></script>
If you want to use Obviel Template (optional but strongly recommended), you need to include it:
<script type="text/javascript" src="/path/to/obviel-template.js"></script>
Finally, you need to include Obviel core itself:
<script type="text/javascript" src="/path/to/obviel.js"></script>
Obviel is now available as obviel in your JavaScript code.
Now that you have Obviel, you can write a JavaScript module that uses it:
(function($, obviel) {
// .. views are defined here ..
$(document).ready(function() {
$(<some_selector>).render(<some_object_or_url>);
});
})(jQuery, obviel);
How do you define views? What does that render call mean? We’ll describe that next.
Now that we have Obviel available, how do we actually render a view for an object on an element?
A view is a component that can render an object into an element in the browser DOM tree. This is done using by calling the function render on the result of a JQuery selector:
$('#foo').render(model);
If you have Obviel installed, this render function will be available. Since the DOM needs to be available when you start rendering your views, you need to bootstrap rendering in the $(document).ready callback. Most of the view rendering will be done by other views: your application view will render some views which render other views, etc.
So what does this render call do? Two things:
Typically you would use selectors that only match a single element, but if you use a selector that matches more than one element, view lookup is performed multiple times, once for each matching element.
Now let’s look at the pieces in more detail.
A model is just a JavaScript object with one special property: iface (or ifaces):
var model = {
iface: 'example',
name: 'World'
};
What creates these model objects? You could create them in JavaScript on the client, or you could create them in the server and send them to the client as JSON. The only thing that matters is that it has an iface property so it can declare its type.
As you can see, the iface property just a string. The string identifies the iface of this object.
A view is a special JavaScript object that says how to render a model on an element:
obviel.view({
iface: 'example',
render: function() {
this.el.text("Hello " + this.obj.name + "!");
}
});
You see how iface comes in again: this view knows how to render objects of iface example.
So if we have the following HTML:
<div id="foo"></div>
We can invoke the following:
$('#foo').render(model);
And the DOM will be changed to this:
<div id="foo">Hello World!</div>
The view has rendered "Hello World!" on element #foo, where World comes from the name property of the model object being rendered.
The steps taken by Obviel are:
What the render object does is up to you, but typically you’d manipulate the DOM in some way using jQuery like in the example above.
Why is view lookup useful? Loose coupling.
Dynamic view lookup based on iface is what allows loose coupling in Obviel applications.
You can see an iface as an informal promise that a model object will have certain properties (and methods).
A model does not need to know about what code is used to render it, it only needs to have an iface property to describe what type of the object has.
Because a view is bound to an iface, it can render any object that declares that iface.
Most importantly, the code that renders the model on an element does not need to know about the specifics of the model or the view. It can render any model on an element, as long as a view has been declared earlier.
Let’s look at an example of the power of this.
Here are two models that both declare they are of iface animal:
var elephant = {
iface: 'animal',
color: 'grey'
};
var lion = {
iface: 'animal',
color: 'golden'
};
We also define a view that knows how to render an animal on an element:
obviel.view({
iface: 'animal',
render: function() {
this.el.text('The animal is ' + this.obj.color);
};
});
So now this:
$('#animal').render(elephant);
will render in the element indicated by #animal the text:
The animal is grey
and this:
$('#animal').render(lion);
will render like this:
The animal is golden
We can make a zoo object that contains a bunch of animals:
var zoo = {
iface: 'zoo',
animals: [elephant, lion]
};
Let’s create a view for zoo next that renders the animals in a series of <p> elements:
obviel.view({
iface: 'zoo',
render: function() {
for (animal in this.obj.animals) {
var sub = $('<p></p>');
this.el.append(sub);
sub.render(animal);
}
}
});
As you can see, here we have a view that uses render to render more views.
Now the fun starts. What if we want to treat all animals the same, except elephants? To do this we must give the elephant model a special iface elephant first:
var elephant = {
iface: 'elephant',
color: 'grey'
};
We register a special view for the elephant iface:
obviel.view({
iface: 'elephant',
render: function() {
this.el.text('This elephant is ' + this.obj.color);
};
});
If we render zoo now each animal will be rendered as before (This animal is golden), but the elephant will be rendered as This elephant is grey. And because of the loose coupling Obviel provides, we haven’t had to change the view for zoo at all to accomplish this!
Now you can write abstract views that can show information without knowing in detail what these models are about or how to render them. And if your client application pulls in an object from the server using AJAX, it can render it without knowing what it is. This lets you change the behavior of the client-side application by modifying the server code.
In JavaScript applications you tend to write JavaScript code that works of a specific piece of HTML; you render content into it or bind event handlers, etc. You can insert this HTML in a view’s render method using jQuery with .html(), but Obviel offers a nicer way.
You can configure a view so that it renders a piece of HTML into the element (using the jQuery .html function) before the render function is called. You do this by adding a html property to the view.
Here’s an example:
obviel.view({
iface: 'foo',
html: '<div class="aClass">Some HTML</div>',
render: function() {
var el = $('.aClass', this.el);
el.text("Changed the text!");
}
});
This will add the structure <div class="aClass">Some HTML</div> into the element the view is rendered on, and then calls the render function, which can now make some assumptions about what is in the DOM under the element.
For small snippets inline HTML like above is fine, but for longer fragments of HTML it is nicer to maintain this information in a separate file instead of in a string embedded in the JavaScript code. Using the htmlUrl property you can make your view can refer to a static HTML resource on the server:
obviel.view({
iface: 'foo',
htmlUrl: 'some.html',
render: function() {
// ...
}
});
Before rendering the view, Obviel will look up the url some.html (which should be a static resource) and insert the contents into the DOM under the element. The HTML referred to by htmlUrl will be cached by the system, so when you render the view a second time no more request will be made to the server to retrieve the HTML fragment.
An alternative way to write longer templates is to include them on the web page that the view is being rendered on in a script tag identified by an id:
<script type="text/template" id="myId">Hello <em>world</em>!</script>
Script tags of type text/template will be inert on the web page itself, but the HTML snippets defined there are available to Obviel, by using htmlScript:
obviel.view(
iface: 'foo',
htmlScript: 'myId'
);
In some cases you may want to let the server supply the HTML in the model instead of using it from the view. If the object to be rendered has a html, htmlUrl or htmlScript property those will be interpreted as if they were on the view.
The properties of the model have precedence over any defined on the view.
A combination of static HTML and jQuery scripting is certainly pretty dynamic already, but you can write a lot more compact and readable code by using a template language. Obviel includes a template language custom-designed for Obviel called Obviel Template.
How do you use Obviel Template? The properties obvt, obvtUrl and obvtScript work like html, htmlUrl and htmlScript. Let’s look at an example:
obviel.view({
iface: 'person',
obvt: '<div>{name}</div>'
});
$('#somediv').render({
iface: 'person',
name: 'John'});
This will result in:
<div>John</div>
When rendering an Obviel Template, the object being rendered ((view.obj) is combined with the template and the result is added to the element on which render was invoked (view.el).
Obviel Template has the usual features like variable interpolation, conditionals and looping, but also special Obviel-related features such as i18n, hooking up event handlers, and the direct rendering of subviews. Read the template documentation for much more information.
Sometimes you want to have more than one view for the same object. For instance, we may want to have a view for an animal as it appears in an overview, and a detailed view for animal when it appears by itself.
Here’s an example of a view for animal with the name alternate:
obviel.view({
iface: 'animal',
name: 'alternate',
render: function() {
this.el.text("Color of animal is: " + this.obj.color);
};
});
You need to explictly pass the view name as a second argument to to render to look it up:
$('#animal').render(lion, 'alternate');
This will result in:
Color of animal is: golden
The default name used if you don’t specify it is appropriately called default.
When you render a view, a view instance is created that has several properties which you can access through this in the render function of a view (and in other methods of the view). We’ve seen some of them before, but we’ll go through them systematically now.
The element that this view is being rendered on. This is a jQuery-wrapped element, so all the usual jQuery functionality will work. The element is where the view expresses itself during rendering: it adds sub-elements to this element, or changes its text() value, hooks up event handlers, etc.
When building a complicated web page, it is often useful to partition the web page into separate components: views. You split up a complex view that tries to do everything into multiple smaller, simpler views, and along with this you separate a complex model into several smaller ones too.
For example, here we have a model that abstracts various web page components such as pieces of text and lists.
For instance:
{
iface: 'page',
parts: [
{
iface: 'text',
text: "Hello world"
},
{
iface: 'list',
entries: ['foo', 'bar', 'baz']
}
]
}
This model is almost too abstract for many applications; you’d typically would create more application specific ifaces than this, but it will make a good example.
So, here we have an outer object with the iface page, and in there there are two parts, one with iface text and one with iface list.
How would you set out to render such a thing with Obviel views? We could write a single view that tries to do everything for a page:
obviel.view({
iface: 'page',
render: function() {
var part, entry, divEl, pEl, ulEl, liEl;
for (part in this.obj.parts) {
divEl = $('<div></div>');
this.el.append(divEl);
if (part.iface === 'text') {
pEl = $('<p></p>');
divEl.append(pEl);
pEl.text(part.text);
} else if (part.iface === 'list') {
ulEl = $('<ul></ul>');
divEl.append('<ul>');
for (entry in part.entries) {
liEl = $('<li></li>');
ulEl.append(liEl);
liEl.text(entry);
}
}
}
}
});
Besides this being a rather big blob of code, it is also not very flexible. What if we suddenly include another kind part in the parts list with a different iface? We’d need to modify the code above by adding another else if statement.
Let’s decompose this code into a number of subviews. First we create a view for the text iface:
obviel.view({
iface: 'text'
render: function() {
var pEl = $('<p></p>');
this.el.append(pEl);
pEl.text(this.obj.text);
}
});
This view adds a p element to the DOM under which it is rendered, and renders the text property of the underlying object into it.
We’ll also create a view for list:
obviel.view({
iface: 'list'
render: function() {
var ulEl = $('<ul></ul>'),
entry, liEl;
this.el.append(ulEl);
for (entry in this.obj.entries) {
liEl = $('<li></li>');
ulEl.append(liEl);
liEl.text(entry);
};
}
});
This creates a ul element in the DOM and renders each entry in the entries list as a li element with text in it.
Now let’s look at what happens to our view for page:
obviel.view({
iface: 'page',
render: function() {
var part, divEl;
for (part in this.obj.parts) {
divEl = $('<div></div>');
this.el.append($(divEl);
divEl.render(part);
}
}
});
You can see how delegation to subviews comes in: we render each part individually. You can also see something else: the page view has no knowledge of what these sub views are, and could render any list of them – it’s entirely dependent on the object it is asked to render. So now if we insert a different kind of model into parts the page view can render it as long as it can find a view for it.
Partitioning code into views is useful: it’s the Obviel way. You’ll find it makes your code a lot easier to manage.
Obviel Templates offer a nicer way to render views. But let’s first review subview rendering the manual way:
obviel.view({
html: '<div class="foo"></div>',
render: function() {
$('.foo', this.el).render(this.obj.attr);
}
});
This will render a subview on the element matched by class foo for the model indicated by this.obj.attr. this.obj.attr may be a sub-object or a URL referring to another object.
Doing this by hand is not too bad, but Obviel also allows you to do this using a template using data-view:
obviel.view({
obvt: '<div data-view="attr"></div>'
});
Wow, that’s a lot shorter!
Now let’s examine the application from the section above again, using templates and data-view:
obviel.view({
iface: 'text',
obvt: '<p>{text}</p>'
});
obviel.view({
iface: 'list',
obvt: '<ul><li data-each="entries">{@.}</li></ul>'
});
obviel.view({
iface: 'page',
obvt: '<div data-each="parts" data-view="@."></div>'
});
As you can see, that has cleaned up the code a lot!
Obviel also offers a declarative way to define subviews using selectors. Now that Obviel Template can render subviews directly we recommend you use, as it’s easier to understand and less verbose. For completeness however we will discuss the selector-based approach here.
For example:
obviel.view({
html: '<div class="foo"></div>',
subviews: {
'.foo': 'attr'
}
});
The subviews property, if available, should define a mapping from jQuery selector to model property name. If the view has a render function, subviews are rendered after the render function of the view has been executed.
So, if you have this view:
obviel.view({
subviews: {
'#alpha': 'alpha',
'#beta': 'betaUrl'
});
And render it with the following context object:
{
alpha: {text: 'foo'},
betaUrl: '/beta.json'
}
the system will, in effect, call:
$('#alpha', this.el).render({text: 'foo'})
and:
$('#beta', this.el).render('/beta.json')
If you want to invoke a subview with a specific name, you can provide a name for subviews by passing an array instead of a string as the value of the subviews mapping:
obviel.view({
subviews: {
'#selector': ['foo', 'name']
}
});
Here, a subview is registered for the selector #selector, the data is looked up on the context object using property name foo, and the view is looked up using name.
Note that the promise returned from render() will be done after the main view and all its subviews are done rendering.
We saw how Obviel can be asked to render JavaScript objects. We also saw how you can compose views together by composing JavaScript objects together and then having views defer to each other. But the server code doesn’t have to send a whole composed object to the client at once - sometimes that’s too cumbersome (the server state changes), and sometimes accessing all the data on the server all at once to send to the client is just too costly. Obviel has a way out: you can hyperlink objects together using URLs, much like how web pages are hyperlinked together.
Obviel can render URLs just like it can render objects. When you pass a string to the render method instead of an object, Obviel will interpret it as a URL. It will send a GET request to the server to fetch a JSON object from that URL, and then render that object. For example:
$('#foo').render('http://example.com/somejson');
will fetch the JSON from http://example.com/somejson, and then call render on that object. It’s the equivalent of this jQuery code:
$.ajax({
url: 'http://example.com/somejson',
dataType: 'json'
}).done(function (obj) {
$('#foo').render(obj)
});
This behavior of Obviel allows you to create a hyperlinked structure of JavaScript objects. We can demonstrate that using the page view we created before. Here it is, using Obviel Template:
obviel.view({
iface: 'page',
obvt: '<div data-each="parts" data-view="@."></div>'
});
We also leave the other views to render text and list ifaces unchanged.
Instead of putting full-fledged objects in the parts array, you can also put hyperlinks in there. Consider the following object:
{
iface: 'page',
parts: [
'http://example.com/hello',
'http://example.com/some_list'
]
}
and then under the http://example.com/hello URL we return this:
{
iface: 'text',
text: "Hello world"
}
and under the http://example.com/some_list URL we return this:
{
iface: 'list',
entries: ['foo', 'bar', 'baz']
}
Rendering this page object will result in the same web page as before - Obviel will automatically request the underlying URLs and render the objects.
So Obviel’s understanding of hyperlinks gives us more cool things:
A view is just a JavaScript object, and you may therefore supply extra methods that it calls from the render method to assist in the rendering of the view and setting up of event handlers:
obviel.view({
render: function() {
this.foo();
},
foo: function() {
// ...extra work...
}
});
You can also add extra properties:
obviel.view({
render: function() {
this.foo();
},
extra: "An extra property"
});
In many views you will need to bind event handlers to elements rendered by the view. You can do this by hand using jQuery:
obviel.view({
iface: 'foo',
obvt: '<div id="press">Click me!</div><div id="clickResult"></div>',
render: function() {
var self = this;
$('#press', this.el).click(function(ev) {
$('#clickResult', self.el).text('clicked!');
});
}
});
Before we show how to do this with Obviel Template, let’s refactor this first to use a method on the view for the event handler:
obviel.view({
iface: 'foo',
obvt: '<div id="press">Click me!</div><div id="clickResult"></div>',
render: function() {
$('#press', this.el).click(this.clickHandler);
},
clickHandler: function(ev) {
$('#clickResult', this.el).text('clicked!');
}
});
We can now hook up the event handler using Obviel Template instead, using the data-handler directive:
obviel.view({
iface: 'foo',
obvt: '<div data-handler="click|clickHandler">Click me!</div><div id="clickResult"></div>',
clickHandler: function(ev) {
$('#clickResult', this.el).text('clicked!');
}
});
Now it’s immediately clear by reading the template which events are being handled.
Note that the event handling function gets a jQuery event object as its first argument (just like any normal jQuery event handler).
You can hook in multiple event handlers on an object by separating them by a space:
click|clickHandler blur|blurHandler
Consider the following view with a template that uses a formatter:
obviel.view({
iface: 'foo',
obvt: '{myVariable|someFormatter}'
});
When you render this view, someFormatter will be looked up in the global formatter registry. You can use Obviel Templates’s obviel.template.registerFormatter() to fill it when the application starts.
You can however also define this formatter on the view itself:
obviel.view({
iface: 'foo',
obvt: '{myVariable|someFormatter}',
someFormatter: function(value) {
return value + " formatted";
}
});
Now someFormatter will be called on the view to render myVariable.
The same approach works for data-func:
obviel.view({
iface: 'foo',
obvt: '<div data-func="someFunc"></div>',
someFunc: function(el, variable, context) {
if (variable('flag')) {
el.addClass('foo');
}
}
});
when you now render the view with this object:
{
flag: true,
}
the class foo will be added to the div in the template.
You can supply an optional before function to the view that will be called just before rendering starts. This can be useful to do additional client-side set up in the object being rendered just before the template renders it, so that the template can access extra information. Here is a simple example:
obviel.view({
iface: 'text',
before: function() {
this.obj.length = this.obj.data.length;
},
jsont: 'The text "{data}" has {length} characters.'
});
el.render({
iface: 'text',
data: 'Hello world'
});
Before we’ve seen how to bind event handlers to elements. You can also declaratively bind event handlers (for custom application-specific events) to the object that is being rendered by the view. When you trigger an event on the object, the event handler will be called.
Here is an example:
obviel.view({
iface: 'foo',
render: function() {},
objectEvents: {
'update': 'rerender'
},
rerender: function() {
self.el.render(self.obj);
}
});
This registers a handler that gets triggered when the custom update event is sent to the object. Let’s first render an object:
var myObj = {iface: 'ifoo'};
some_el.render(myObj);
and now let’s trigger the update event on myObj. This is done by wrapping the object in $ first:
$(myObj).trigger('custom');
This will cause rerender to be called, which will redraw the view. If myObj has changed in the mean time, the view will be redrawn to reflect this.
Because rerender happens so often we’ve predefined rerender on the standard Obviel view, so you don’t have to define it like we did above. This is sufficient:
obviel.view({
iface: 'foo',
render: function() {},
objectEvents: {
'update': 'rerender'
}
});
In some cases you need to know when a view has finished rendering; this is particularly useful when you are writing automated tests that involve Obviel. The Obviel test suite itself is a good example of this. Obviel’s render returns a jQuery promise. What this means is that you can hook a done function to be called when rendering is completed:
el.render(obj).done(function() { alert("Promise completed!") });
The done function gets a single argument, the view that was used to render the object:
el.render(obj).done(function(view) { ... });
Sometimes the server does not return JSON objects with an iface already included, or in some other way returns objects that are not very suitable for rendering them with Obviel.
You can plug in a transformer function to transform JSON content retrieved from the server to a format more useful to your client-side views.
How this transformation is done in detail is up to you; you can plug in any transformation logic you like, as long as you return an object that can be rendered using Obviel.
What does this look like? Let’s imagine we have a web server that returns JSON objects that are just perfect for Obviel, except for the fact that the objects don’t have an iface or ifaces property. Instead, these objects have a type property. We would like use this type property as the iface.
We can hook in a transformer that does this for any object retrieved from the server using URI access:
obviel.transformer(function(obj, url, name) {
obj.iface = obj.type;
return obj;
});
In this case, we just modify the incoming JSON object adding an iface property in there. You can also return wholly different objects instead.
As you can see, the transformer function takes three arguments:
obj
The JSON object retrieved and to be transformed.
url
The URL from which the object was retrieved. This argument is optional.
name
The name of the view that is being looked up. This argument is optional.
The URL argument could be used to set the iface of an object based on the URL it was accessed with. This is less flexible than letting the server send an object with an iface marker, however, so if you have control over the server this is recommended.
Of course setting the iface is just one of the transformations you can apply; you can add any arbitrary property to objects or even return completely different objects altogether.
While in many cases the additional methods strategy as described previously is sufficient, in more complex cases it can be useful to be able to create a new view by inheriting from another view. The Obviel form system uses this approach for its widgts.
To understand how view inheritance works, you first need to understand that the following registration:
obviel.view({
render: function() { ... }
});
is in fact a shorthand for this registration:
obviel.view(new obviel.View({render: function() { ... }}));
Obviel automatically creates a basic Obviel View if a bare object is passed to the view registration function.
You can however also create new view objects by subclassing View yourself:
var DivView = function(settings) {
var d = {
html: '<div></div>'
};
$.extend(d, settings);
obviel.View.call(this, d);
};
DivView.prototype = new obviel.View;
DivView.render = function() {
// ...
};
Now the new view can be registered like this:
obviel.view(new DivView());
You can also create your own base classes that derive from View that provide extra functionality, and inherit from them.
Obviel can start working with just a single URL; two if you need templates or HTML snippets. All the other URLs in the application it can access by following hyperlinks in JSON.
This is an example of Obviel bootstrapping code in your application:
$(document).ready(function() {
$('#main').render(appUrl);
});
This renders the object at appUrl when the DOM is ready, and will render it into the HTML element identified with the main id.
We call the object referred to by appUrl the root object. The root object should include hyperlinks to other objects in your application, which it will then in turn render as sub-objects.
The question remains how to actually set appUrl in your application. It is a URL that will be dependent on how your application is installed.
One way to do it is to exploit your web framework’s server-side templating system, and set it in a <script> element somewhere in your web page:
<script type="text/javascript">
var appUrl = "[the app url goes here using a template directive]";
</script>
Another way is to include a separate JavaScript file that you dynamically generate, that only sets appUrl:
var appUrl = "[the app url goes here, using server-side programming]";
There is a second URL that is handy to include using one of these methods as well: templateUrl. This is the URL that identifies your template (or HTML snippet) directory. It could for instance look like this:
http://example.com/templates/
Note how it ends with a forward slash (/).
Once templateUrl is available, your views can refer to individual templates like this:
v.view({
htmlUrl: templateUrl + 'some_snippet.html'
});
You can set up templateUrl in the same way you set up appUrl, though there is one extra requirement: templateUrl must be known before any Obviel views are registered, whereas appUrl only needs to be known when the DOM is ready. If you are going to set templateUrl it is therefore important to do this early, in a <script> tag that comes before the <script> tag that includes your code that registers views. For example:
<script type="text/javascript">
var templateUrl = "http://example.com/templates/";
var appUrl = "http://example.com/app_root";
</script>
<script type="text/javascript" src="http://example.com/obviel_app.js"></script>
When a view is rendered on an element, it remains associated with that element, unless the ephemeral property of the view is set to true. If a view is associated with an element, rendering an object of the view’s iface (and name) for any sub-element will render on the outer element instead. The sidebar has more background on this feature.
To retrieve the associated view of an element, you can use the $(el).view() function to get it back again.
To access a view in this element or its nearest parent, use $(el).parentView().
To remove the element association, you can call $(el).unview().
To re-render a view on the element again, use $(el).rerender().
When a view is rendered on an element that already had a view associated with it, or when a view is unrendered using unview, Obviel calls the cleanup method on the view. You can put in a special cleanup method on the view that gets called to perform the cleanup.
Obviel triggers two kinds of events:
These will both be triggered on the element that the view is rendered on. Both event objects will also have a special view property with the view that triggered the event.
The render-done.obviel event can be used to take action when the view is done rendering entirely (including the rendering of any subviews).
The render.obviel event is an internal event of Obviel; Obviel sets up an event handler for this by default on the document, and also sets up an event handler for this for elements that have a view associated with it. The latter event handler will only take action if the view being rendered has the same iface and name properties as the view that was associated with the element – it is used to implement Element association behavior.
It is sometimes useful to be able to register an iface more generically, for a whole selection of related objects. We may have more particular person objects such as employee, contestWinner, etc, but if we register a view for person objects we want it to automatically apply to those other types of objects as well, unless we registered more specific views for the latter.
Let’s consider the following object describing a person:
>>> bob = {name: 'Bob', location: 'US', occupation: 'skeptic',
... iface: 'person'}
>>> obviel.ifaces(bob)
['person', 'base', 'object']
So far nothing new. But ifaces themselves can have an extension relationship with each other: iface b can be declared to extend iface a. We’ve already seen an example of this, because person automatically extends the base iface base.
If a view is declared for a certain iface, it is also automatically declared for all ifaces that extend that iface.
So let’s imagine we have an iface employee that extends the person iface. We can tell the system about it like this:
>>> obviel.extendsIface('employee', 'person')
An iface may extend an arbitrary amount of other ifaces, but circular relationships are not allowed. The obviel.ifaces function knows about extensions. So, let’s say that we have an employee object:
>>> employee = {name: 'Bob', location: 'US', occupation: 'skeptic',
... wage: 0, iface: 'employee'}
Since we said before that any employee is also a person, the following is true:
>>> views.ifaces(employee)
['employee', 'person', 'base', 'object']
Note that interfaces are sorted by topological sort, with the most specific interfaces at the start. When looking up a view for an object, this is useful to ensure that the views registered for the most specific interfaces are found first.
Want to learn Obviel or just look something up? Obviel has extensive documentation:
Here is a full table of contents:
Please activate JavaScript to enable the search functionality.
From here you can search these documents. Enter your search words into the box below and click "search". Note that the search function will automatically search for all of the words. Pages containing fewer words won't appear in the result list.
Declare an iface.
Arguments: |
|
---|
Register an iface with the name name. Note that declaring an iface is not required in order to use it; you can just refer to any iface, declared or not, by name.
A registered iface always automatically extends a special iface base.
Check whether an object provides an iface.
Arguments: |
|
---|---|
Returns: | true if obj provides iface base (or an iface that extends base. |
Register a new base iface for an iface.
Arguments: |
|
---|
Obtain all ifaces that this object provides, going recursively through base interfaces, breadth first.
If no ifaces attribute is available, the JS type of the object is returned instead.
Arguments: |
|
---|---|
Returns: | a list of ifaces that the obj provides. |
Clear the template cache from any cached templates. Useful to do as cleanup between unit test.
Arguments: |
|
---|
You can create a View by instantiating one and then registering it with obviel.view(), but that function also allows creation of a new view directly from a settings object.
If you want to create your own view types that provide more features particular to specific use cases, you should derive your view from the View class. One example of this is Widget() from the Obviel forms library.
Register a view with Obviel. This allows Obviel to look up the view for the iface with which the view was registered.
Arguments: |
|
---|
Arguments: |
|
---|
Register a transformer function with Obviel. Obviel will pass any object retrieved from the server using URL access (i.e. a hyperlink reference in an object) through this transformer function.
The transformer function has three arguments:
obj
The object retrieved from the server that can be transformed.
url
The URL used to access the object on the server. Could be used to determine what transformation to apply. Optional.
name
The name used to render the view. Optional.
The transformer function must return the transformed object.
By default Obviel does no transformation, and objects are passed from the server to Obviel unchanged.
You can disable the transformer function by passing in null.
Render a view for an object on a jQuery element. This instantiates a view by cloning it from the registered view and then calls its obviel.View.render() method.
Arguments: |
|
---|---|
Returns: | A promise. You can hook in a function to be called when rendering has completed, such as $(el).render(obj).done(function(view) { }); |
All methods of the View() object will have access to the following properties on this during rendering:
el: the jQuery element that the view is being rendered on.
obj: the object being rendered.
looked up.
Re-render (non-ephemeral) view on element.
Returns: | A promise. You can hook in a function to be called when rendering has completed, such as $(el).render(obj).done(function(view) { });. view will be null in case there was no view on this element to rerender. |
---|
If there is a previous view on the element, and the view was rendered from an object, the object is re-rendered on the element.
If there is a previous view on the element and the view was rendered from a URL, the object for the URL is re-fetched and then rendered.
If there is no previous view on the element, nothing will happen.
Get the (non-epheremal) view previously rendered on element.
Returns: | The non-ephemeral view previously rendered on the element. If no non-ephemeral view was rendered on the element, undefined is returned. |
---|
Get nearest view registered on element or parent.
Returns: | The non-ephemeral view rendered on element, or if missing, walk up the ancestor chain until a view is found. If no view can be found return null. |
---|
Remove non-ephemeral view from element, executing any view cleanup. If no view exists on this element, nothing happens.
Views have a number of options you can pass into them in the settings parameter when registering a view using obviel.view(). Views also define some convenience methods.
The iface (a string) for which the view is registered. The view will only be looked up on objects that provide the given iface.
While it is optional and defaults to registering the view for all JavaScript objects, you would almost always want to supply an iface property to a view.
The name of the view. It is optional, and defaults to default. The name is used when a view is looked up, along with the iface, but for lookup also the default name is default. The difference between name and iface during lookup that iface is provided from the object being rendered, while the name is provided by the calling code as an argument to element.render().
A function which will be added as a method to the view. In it, the developer can refer to this.obj to access the object the view is rendering, and this.el to access the jQuery DOM element on which the view is rendering. The el property is typically manipulated in the render method to make the view actually do something.
Supplying a render method is optional.
An optional function that will be called before any rendering (template or otherwise) takes place. This is most useful to to manipulate this.obj to set extra information in it that the template needs.
An optional cleanup function that will be called when the (non-ephemeral) view is being cleaned up from the element it was rendered on, either explicitly because element.unview() was called on it, or implicitly because something else is being rendered on the element.
A boolean value. By default it is false. If set to true, the view will not be associated with the element. This is useful for views that are not really associated with the DOM, such as popup messages or views that redirect to other views.
A string with a snippet of HTML that should be inserted into the DOM upon rendering. This will be done before any obviel.view.render() function is called.
This property is optional, and by default there will be no HTML snippet insertion.
A string with a URL to a resource with a HTML snippet in it. This HTML snippet will be loaded if the URL has not been previously seen, otherwise it is retrieved from a cache. The HTML snippet is inserted into the DOM as with html.
This property is optional.
A string with an Obviel template that should be rendered on the view element. This will be done before any obviel.view.render() function is called.
This property is optional.
A URL referencing a resource that is a Obviel template. This template will be loaded if the URL has not been previously seen, otherwise it is retrieved from a cache. The template is then rendered on the view element, combined with the object being viewed.
This property is optional.
A string with a JSON template. The template is rendered with the object being rendered as the context, and the resulting HTML snippet is inserted into the DOM as with html.
This property is optional.
A URL referencing a resource that is a JSON template. This template will be loaded if the URL has not been previously seen, otherwise it is retrieved from a cache. The HTML snippets that results from rendering the template will be inserted into the DOM as with html.
This property is optional.
A JavaScript object literal describing subviews of this view. Each property name define a jQuery selector that will be issued on the element being rendered by the outer view that is being rendered. The resulting elements will be have the sub view rendered on them. The property value identifies a property on the model object that is being rendered. This property is accessed: if it is a sub object, it will be rendered directly. If it is a string, this string is interpreted as a URL identifying the object to be rendered, as with element.render().
To render a named subview, use a property value that is an array instead: the first item is interpreted as the property name identifying the subobject or URL to be rendered, and the second item is the view name to use to look up the subview.
A JavaScript object literal describing event handlers to hook up for this view. Each property name is the name of the event, such as click. The property value is another object literal, where each key is a jQuery selector and the value either a function or a string that indicates the event handler. The jQuery selector determines to which elements in the view the event handlers are bound.
If the event handler is a function, this function is the event handler. It will receive a single parameter that is the event. The event object has a view property that you can use to access the view that issued the event.
If the event handler is a string, this is used to identify a method of the view that is the event handler. This method also receives a parameter ev that has a view property, but the view can in this case also be accessed as this.
A JavaScript object literal describing event handlers to hook up to the object rendered by this view. Each property name is the name of the event. These are generally custom event names you can come up with yourself. The property value is either a function or a string indicating the event handler.
If the event handler is a function, this function is the event handler. It will receive a single parameter that is the event. The event object has a view property that you can use to access the view that issued the event.
If the event handler is a string, this is used to identify a method of the view that is the event handler. This method also receives a parameter ev that has a view property, but the view can in this case also be accessed as this.
The translation domain to use for any associated Obviel Template. If no domain is specified, the current translation domain specified by obviel.i18n.translate() is used.
A convenience method that is useful to refer to using declarative events or object events. This method when called will re-render the object on the element. This is useful for instance when the underlying object has changed.
Everything is in the obviel.template namespace.
Exception. Something went wrong during compilation of a template.
The JavaScript DOM node, typically an element node, on which compilation failed.
Message describing what went wrong during compilation.
Exception. Something went wrong during compilation of a template.
The JavaScript DOM node, typically an element node, on which rendering failed.
Message describing what went wrong during rendering.
Register a global formatter. Is done when the UI starts up (per page), before any templates are compiled. Note that with Obviel you can also use view methods as formatters.
Arguments: |
|
---|
Return a global formatter. If no formatter can be found, undefined is returned.
Arguments: |
|
---|---|
Returns: | the formatter function or undefined. |
Clear all previously registered formatters. Typically used during unit test teardown.
Register a global element adjustment function. Is done when the UI starts up (per page), before any templates are compiled. Note that with Obviel you can use view methods as funcs
Arguments: |
|
---|
Return a global element adjustment function. If no func can be found, undefined is returned.
Arguments: |
|
---|---|
Returns: | the func or undefined. |
Clear all previously registered funcs. Typically used during unit test teardown.
A template. To instantiate and compile, create a new Template object with a text argument. The text argument should be a HTML fragment that will be compiled, or alternatively a jQuery selector indicating an element to be compiled (everything in it will be compiled).
Can throw CompilationError() if the text could not be compiled.
Render template on jQuery element el, using obj to resolve variables, and using context as the rendering context.
Arguments: |
|
---|---|
Returns: | A promise, so you can hook in .done(function()) { } that will be called when the template is done rendering. |
A simple way to interpolate variables into a text, in particular for the purposes of i18n.
Given a text with variables (marked with { and }) and an object (hash) of variables, interpolate variables into text. This can be used from JavaScript to interpolate Obviel Template-like translation variables, instead of jsgettext’s strargs, which only works with positional values.
Arguments: |
|
---|---|
Returns: | The text with variables interpolated. |
Tokenize text using Obviel Template rules. Name tokens are marked by { and }, the rest are text tokens.
Arguments: |
|
---|---|
Returns: | an array of token objects. Each token object has a type property that indicates the token type (NAME_TOKEN or TEXT_TOKEN, and a value property. In case of NAME_TOKEN this is the name between { and }, in case of TEXT_TOKEN this is the text. |
A form structure is could be returned from the server-side but an also be defined on the client. It should have the iface viewform.
An array of form widget objects.
An array of form control objects. Defaults to an empty array.
The object representing the form contents. Defaults to the empty object and is maintained by the form.
A boolean. If true, the whole form is rendered in a disabled state and cannot be submitted.
A URL to do global validation on. Submitted to this URL will be the form’s data object (as JSON). The server needs to decode this JSON object and do validation on it. The server returns a data structure (as JSON) with the same structure as what was submitted, with the values replaced by error messages (or left out). The minimum object a global validator needs to return is the empty ({}) object. This object will be placed in the globalErrors attribute of the form.
The object representing the error messages currently visible in the form. If this object is not empty, form submission will not proceed.
This defaults to the empty object and is maintained by the form.
The object representing server-generated error messages currently visible in the form. If this object is not empty, form submission will not proceed.
This defaults to the empty object and is maintained by the form.
The internal name of the widget. This will be the name of the property under which the inputted value will be stored in the data object.
This must therefore be a valid avaScript property name.
The label that will be shown for the widget in the user interface.
A longer description of the widget that will be shown in the user interface. May be omitted.
A sub-object describing how this widget is to be validated. Validation properties differ per widget.
The default value that should be shown in the widget if no specific value is available in the data object.
iface: inputField
Base class for all widgets based on the <input type="text"> field.
Derives from Widget().
Width of the field in em. By default there is no explicit width and the system uses the browser-defined default width of the input field.
The maximum length that may be entered into this widget by the user. This is a physical limitation in the browser, not a validation limitation. By default there is no max length.
Render the widget as disabled and allow no input. By default, disabled is false.
This widget is required: a value must be entered by the user before the form can be submitted. By default, widgets are not required.
Example:
validate: {
required: true
}
iface: textlineField
Lets the user enter a single line of text, in an <input type="text"> widget. Becomes a string in the data object.
Derives from InputWidget().
The minimum input length in characters allowed.
The maximum input length in characters allowed.
An array of zero or more objects. Each object must have a reg property which is a JavaScript style regular expression, and a message property which is a string. If the input does not match reg, message will be displayed as the error message.
This allows you to write regular expression based custom validators for user input.
iface: textField
Lets the user enter a multiline text in a <textarea> widget. Becomes a string in the data object.
Derives from TextLineWidget().
The width of the textarea in em.
The height of the textarea in em.
iface: integerField
Lets the user enter an integer number. Floating point numbers are not accepted, only integers. Becomes a JavaScript number in the data object.
Derives from InputWidget().
If true, negative numbers are allowed as input. Defaults to false, not allowing negative numbers.
Only allow input to a fixed number of digits given by length. If not set, any amount of digits will be allowed.
iface: floatField
Lets the user enter a floating point number. Becomes a JavaScript number in the data object.
Derives from InputWidget().
Define the floating point separator. By default this is the period (.), but could be set to , or something else to suit other locales.
If true, negative numbers are allowed as input. Defaults to false, not allowing negative numbers.
iface: decimalField
Lets the user enter a decimal number. Becomes a string in the data object for parsing into a server decimal object.
Derives from InputWidget().
Define the decimal separator. By default this is the period (.), but could be set to , or something else to suit other locales.
If true, negative numbers are allowed as input. Defaults to false, not allowing negative numbers.
Minimum amount of digits that need to be present before the separator. If omitted, there is no minimum.
Maximum amount of digits that are allowed to be present before the separator. If omitted, there is no maximum.
Minimum amount of digits that need to be present after the separator. If omitted, there is no minimum.
Maximum amount of digits that are allowed to be present after the separator. If omitted, there is no maximum.
iface: booleanField
Lets the user enter a boolean, true or false in a checkbox, using <input type="checkbox">.
Becomes a JavaScript boolean in the data object.
Derives from Widget().
iface: choiceField
Lets the user select a choice from a drop-down box. This uses <input type="select">.
Becomes a string in the data object.
Derives from Widget().
The value that will be shown and submitted if the field is not filled in. If not given, the first option in the drop down list will be the default if the field is required.
The width of the drop-down box in em.
An array of choices that the user can select. Each entry in the array is an object with a value and a label property. The value will be the value submitted, and the label will be how this value is represented in the user interface.
iface: displayField
This is a display-only field that the user is not allowed to edit. The value will be shown in a <span> element.
The server code is responsible for really forbidding changes to fields represented by display widget.
Derives from Widget().
iface: hiddenField
This is special form of the display field that will be hidden from view, represented using <input type="hidden">.
Derives from DisplayWidget()
iface:: datepickerField
This is data input widget which offers picking a date from a calendar. Is represented in data in the yy-mm-dd format, where yy is a four digit year, for example 2011-10-01.
Since this widget depends on jquery-ui for its implementation, it is maintained in a separate field, obviel-forms-datepicker.js, which should be included to be able to use this widget.
Derives from TextLineWidget()
The format that the date should be displayed as. Note that the date will always be saved in yy-mm-dd format, where yy is a four-digit year, so for example 2011-11-01.
This follows the rules from jquery-ui’s datepicker:
http://jqueryui.com/demos/datepicker/#option-dateFormat
http://docs.jquery.com/UI/Datepicker/formatDate
By default the date format is mm/dd/yy.
When to show the date picker.
This follows the rules from jquery-ui’s datepicker:
http://jqueryui.com/demos/datepicker/#option-showOn
By default the datepicker is shown when the button is clicked.
This follows the rules from jquery-ui’s datepicker:
http://jqueryui.com/demos/datepicker/#option-constrainInput
By default it is set to false.
iface:: autocompleteField
This is a text line input with autocompletion, and can therefore be used like a ChoiceWidget(). Autocomplete widgets can either include all options directly in the form structure, or supply a server URL on which to look up values to autocomplete. The latter is more scalable if the amount of possible choices is very large.
Since this widget depends on jquery-ui for its implementation, it is maintained in a separate field, obviel-forms-autocomplete.js, which should be included to be able to use this widget.
Derives from TextLineWidget()
The autocomplete data. This may either be an array of objects with properties label and value, much like the possible values for a ChoiceWidget(), or it can a string, in which case it is interpreted as a URL.
If interpreted as a URL, the URL will get either a single identifier parameter or a combination of a term and limit parameters. If a single identifier parameter, the server should interpret this as a value and should return the label that belongs to it. If a term and a limit parameter, term is a search term that the server should use to look up matching label/value pairs, and return them as an array with objects each with a label and value property. The limit parameter is used to indicate to the server the maximum amount of matches the server should return.
Custom widgets will need to subclass InputWidget() and provide some methods. This is the typical pattern:
obviel.iface('customField', 'inputField')
// constructor
module.CustomWidget = function(settings) {
settings = settings || {};
var d = {
iface: 'customField' // set up iface
};
$.extend(d, settings);
module.InputWidget.call(this, d); // call superclass
};
// set up inheritance
module.CustomWidget.prototype = new module.InputWidget();
module.CustomWidget.prototype.validate = function(value) {
// use inherited validate function first
var error = module.InputWidget.prototype.validate.call(this, value);
// if we have an error in inherited validation, we are done, return
// that error
if (error !== undefined) {
return error;
}
// custom validation goes here, either do: return undefined (or
// plain bare return) or return error message
return undefined;
}
// convert function is necessary if we convert to non-String JavaScript
// value. Can also be used for strings even if we don't convert
// away from it, for instance to check whether a date string is really
// parseable at all, and to format it to a common format if so.
// in addition convert and its pair convertBack can be used to update
// the browser DOM in more complex ways than the default implementation
// allows.
module.CustomWidget.prototype.convert = function(value) {
// convert the empty input to null, so that validation for
// required can do its work
if (value === '') {
// return an object with a ``value`` property with the converted value.
return {value: null};
}
// try to interpret the input as the value type we want
var asint = parseInt(value, 10);
// if the interpretation did not succeed, return an object
// with an ``error`` property with the error message.
if (isNan(asint)) {
return {error: 'not a number'};
}
// return an object with a ``value`` property with the converted value.
return {value: asint};
}
// convert a JavaScript representation back into the widget representation
// (typically a string)
module.CustomWidget.prototype.convertBack = function(value) {
// super call
value = module.InputWidget.prototype.convertBack.call(this, value);
return value.toString();
}
// finally register the widget as a view with Obviel
obviel.view(new module.CustomWidget());
Everything is in the obviel.traject namespace.
A parse error when parsing a path.
The error message.
Error resolving a path to an object: object cannot be found.
The error message.
Error constructing a location (or path) for an object.
The error message.
Error during registration.
The error message.
Register a variable converter.
Arguments: |
|
---|
Register a lookup function for a path pattern.
Arguments: |
|
---|
Can throw ParseError() if the path cannot be parsed.
Can throw RegistrationError`() if the path cannot be registered due to unknown converters or conflicts in registration.
Register an inverse function (from object to variables) for objects of an iface and a pattern.
Arguments: |
|
---|
Register both a path lookup as well as the inverse at the same time.
Arguments: |
|
---|
Can throw ParseError() if the path cannot be parsed.
Can throw RegistrationError() if the path cannot be registered due to unknown converters or conflicts in registration.
Register a function that looks up a default object for those paths that don’t have a more specific lookup registered for them.
Param : | function f: A function that returns a default object. |
---|
Resolve a path to an object according to registered patterns, starting at root.
Arguments: |
|
---|---|
Returns: | The object found. |
Can throw ResolutionError() in case the path cannot be resolved to an object.
Resolve a stack to an object according to registered patterns, starting at root. A stack is a stack of steps in a path, in reverse order.
Arguments: |
|
---|---|
Returns: | The object found. |
Can throw ResolutionError() in case the stack cannot be resolved to an object.
Resolve a path as far as it can be resolved according to registered patterns, then stop resolving the path. Does not throw a ResolutionError(), instead simply stops.
Arguments: |
|
---|---|
Returns: | An object with three properties, unconsumed, an array of steps that could not be resolved (in stack form, reverse order), consumed, those steps that have been successfully consumed when resolving the path, and obj, the object found at the end of path consumption (if no path could be consumed at all, this object will be root). |
Resolve a stack of steps as far as it can be resolved according to registered patterns, then stop resolving the stack. Does not throw a ResolutionError(), instead simply stops.
Arguments: |
|
---|---|
Returns: | An object with three properties, unconsumed, an array of steps that could not be resolved (in stack form, reverse order), consumed, those steps that have been successfully consumed when resolving the path, and obj, the object found at the end of path consumption (if no stack could be consumed at all, this object will be root). |
Locate obj, giving it trajectName and trajectParent properties and placing it in a virtual hierarchy, such that following trajectParent will eventually result in the root object.
Arguments: |
|
---|
Can throw LocationError() in case the object can not be located.
Locate obj, and return its path. This also gives the obj trajectName and trajectParent properties and places it in a virtual hierarchy, such that following trajectParent will eventually result in the root object.
Arguments: |
|
---|---|
Returns: | The path for obj according to the patterns. |
Can throw LocationError() in case the object can not be located.
Everything is in the obviel.i18n namespace.
Exception. Something went wrong during translation; in particular a locale or domain cannot be found.
Message describing what went wrong.
Create a source of translations from mapping.
Arguments: |
|
---|---|
Returns: | The translation source. |
Create a source of translations from a URL.
Arguments: |
|
---|---|
Returns: | The translation source. |
Create an empty source of translations. This means no translation will take place. This can be useful to register the translations for the locale that the UI is written in.
Returns: | The translation source. |
---|
Register a translation source for a locale and a domain.
Arguments: |
|
---|
Clear the translations. Useful during test teardown.
Changes the locale that the application is working in to a new one. This loads the locale information according to previously specified information, for instance using obviel.i18n.load().
Arguments: |
|
---|---|
Returns: | A promise that is done when the locale has been loaded. |
Clears the locale that the application is working in to an empty one. Useful during test teardown.
Returns the current locale. This is null if no locale has been set.
Gives back the translation for a msgid in a particular domain, given the current locale.
Arguments: |
|
---|---|
Returns: | The translation for msgid in the domain. |
Gives back a function that takes the msgid and returns the translation in the domain.
Arguments: |
|
---|---|
Returns: | A function that can be given a msgid to return the translation in the specified domain. |
Set up translations for a module. Intended to be called at the top of your module, like this:
var _ = obviel.i18n.translate('foo');
This changes the template domain to foo, and also returns a function that when called with a message id, will return the translation in the domain foo. The convention is to assign this to _ so that extraction tools can extract occurrences from .js source code.
Arguments: |
|
---|---|
Returns: | A function that can be given a msgid to return the translation in the specified domain and current locale. |
Set up plural translations for a module. Intended to be called at the top of your module, like this:
var ngettext = obviel.i18n.translate('foo');
The ngettext function can be called with three parameters:
Based on the value of count either the singular text or plural text will be used, and translated to the current locale and the specified domain (foo, in this case).
Arguments: |
|
---|---|
Returns: | A function that can be given a singular msgid, a plural msgid and a count and will return the right text for the locale, domain and count. |
Gives back the default domain in use by Obviel templates.
An alias for obviel.template.variables(). Only available if Obviel Template is available when Obviel i18n is being loaded.
Load i18n information as indicated by link elements in document. This should be called when the document is ready.
The link tags should look like this:
<link href="something.i18n" ref="i18n" type="application/json" />
The .i18n file indicated is a JSON file that describes what translations are available for which domains. You can link in multiple .i18n files in one page. The .i18n file should look like this:
{
"<domain>": [
{
"locale": "<locale>",
"url": "<localeUrl>"
}
]
}
A domain may have information about more than one locale. The .i18n file may have entries for more than one domain. The url entry may be a relative URL, relative to the URL of the .i18n file itself. url may also be null, in which case the locale will not have any translations available and thus will be in the source language.
Returns: | A promise that is done when the i18n information has been loaded. Recommended is you do the obviel.i18n.setLocale() in the done handler. |
---|
Obviel includes the obviel-traject.js library. Traject is a library for resolving paths to objects. It is not dependent on Obviel itself, just on jQuery, but is intended for use with Obviel.
In some JavaScript applications you want to display the user interface as a single page that never reloads, but you still want to support hyperlinks, the back button and bookmarking.
For this you can use the fragment identifier. The fragment identifier is the text behind a hash (#) mark in the URL:
http://example.com/something#the_fragment_identifier
The fragment identifier can change without the web browser forcing a reload of the page, for instance when the user clicks a hyperlink to another fragment identifier (<a href="#another_fragment_identifier">Click</a>`), or when the user presses the back button in the browser.
A JavaScript library like jQuery BBQ can be used to subscribe to changes in the fragment identifier (hashchange in jQuery BBQ) and take appropriate action.
What is this action? The fragment identifier is held to be identifying, somehow, some JavaScript object that the client has available. We want to get this object, and then render it in the content area of the UI. When the fragment identifier changes, we want to update the content area of the UI with this rendered object.
Let’s look at these steps in more detail:
With Traject you can define a patterns registry with path patterns. You can then use this patterns registry to resolve actual paths to objects.
Let’s translate this into some code:
$(document).ready(function() {
// we render an application object when this
// page first loads. we have defined the app view
// to include a div with the id "content"
$('.app').render(app);
// trigger the initial hash change manually using BBQ, it goes
// to the empty fragment identifier, "#"
$(window).trigger('hashchange');
});
// listen to the BBQ hashchange event
$(window).bind('hashchange', function(ev) {
// we interpret the fragment identifier as a path to an object
var path = ev.fragment;
// resolve the path to obj using a traject patterns registry we have
// defined before
var obj = patterns.resolve(app, path);
// render the found obj using Obviel
$('#content').render(obj);
});
With this code, hyperlinks to fragment identifiers and the back button will work: when you click a link or press back, the content area in the UI will change accordingly, just as if you were browsing to a new web page.
This code is not complete: you need to define an app object, a view to display the app object and views to display whatever objects are found using patterns.resolve. What these views are is completely up to the application. We’ll see examples of an app object later.
But most importantly, we haven’t described how to create a patterns registry with Traject in the first place, and what it can do for you exactly. Let’s look at this now.
The main object defined by traject is obviel.traject.Patterns(). If you include traject in the page you can create one like this:
var patterns = new obviel.traject.Patterns();
Now let’s look at a simple scenario where we use the simplest form of a path pattern: a single name. We define a bunch of objects:
var app = {
iface: 'app',
objects: {
a: {iface: 'A', 'title': 'the A object'};
b: {iface: 'B', 'title': 'the B object'};
c: {iface: 'C', 'title': 'the C object'};
}
};
Now we want a pattern registry that gives back object a when the path is a, b when the path is b, and, you guessed it, c when the path is c.
Let’s do it:
var getA = function(variables) {
return app.objects.a;
};
var getB = function(variables) {
return app.objects.b;
};
var getC = function(variables) {
return app.objects.c;
};
patterns.register('a', getA);
patterns.register('b', getB);
patterns.register('c', getC);
When we now resolve a path with the patterns registry, we will find the appropriate objects:
var root = {iface: 'root'};
patterns.resolve(root, 'a'); // gives back app.objects.a
patterns.resolve(root, 'b'); // gives back app.objects.b
patterns.resolve(root, 'c'); // gives back app.objects.c
So, what you register with the patterns registry is a path (the first argument) and a function to call when this path is matched. You then resolve a path. We’ll explain what the root object is for later, but you also may have noticed the variables argument to the get lookup functions, which we’ll go into next.
The code above was a bit repetitive given the regular nature of the app object. In addition, each time we add a new object to app with some new name (d, e, foo, whatever) we would have to register a new path in the patterns registry so it can be found. We can use this code shorter and more flexible by using a variable in the path instead. Instead of the above registrations, we’ll use this:
var getObject = function(variables) {
return app.objects[variables.name];
};
patterns.register('$name', getObject);
Now we can look up paths with arbitrary names, and each will be resolved to an object in app.objects.
How does this work? Any variables in the path are indicated using a dollar sign ($). The values of these variables will be passed into the lookup function as properties of the variables object. The function then uses these variables to identify and return the object.
Paths are not restricted to single names, you can also use the slash (/) character in them:
patterns.register('foo/bar/baz', getSomething);
patterns.register('stores/$storeId', getStore);
Paths may also use multiple variables at the same time:
patterns.register('stores/$storeId/products/$productId');
Both storeId and productId will be passed into the lookup function as properties of the variables object.
When a path is resolved to an object using traject, the object is placed in a virtual hierarchy that is deduced from the path. For instance, when we look up an object like this:
var root = {iface: 'root'};
var obj = patterns.resolve(root, 'a');
obj will be placed within the root object. This is done by adding two properties to obj, trajectName and trajectParent.
trajectName is the name the object was looked up under, in this case a. trajectParent is the object that is placed under, in this case root. So these are true:
obj.trajectName === 'a'
obj.trajectParent === root
You can also construct deeper paths. Consider a registration like this:
patterns.register('foo', getFoo);
patterns.register('foo/bar', getBar);
When we now retrieve foo and bar using the following code:
var root = {iface: 'root'};
var foo = patterns.resolve(root, 'foo');
var bar = patterns.resolve(root, 'foo/bar');
the following will be true:
obj.trajectName === 'bar'
obj.trajectParent === foo
foo.trajectName === 'foo'
foo.trajectParent === root
This works with variables too:
patterns.register('departments/$departmentId', getDepartment)
var department1 = patterns.resolve(root, 'departments/1');
obj.trajectName === '1'
obj.trajectParent.trajectName === 'departments'
obj.trajectParent.trajectParent === root
Here we’ve registered the path departments/$departmentId, but we have not registered department. So what kind of object is obj.departmentParent? It’s a special default object that traject creates if it doesn’t have any more specific lookup function registered for that path:
{ iface: 'default'}
You can specify a custom default object lookup with a patterns registry if you want:
patterns.setDefaultLookup(function() {
return { iface: 'someCustomDefaultObject'};
});
In some cases it is convenient to have a variable be converted to a particular type, for instance an integer automatically. You can do this by indicating the converter behind the variable:
patterns.register('customers/$customerId:int', getCustomer);
The variables object passed to the getCustomer function will now get the variable customerId as an integer. If a non-integer is in the path for $customerId, the path will fail to resolve.
You can also register new converters with the patterns registry:
patterns.registerConverter('float', function(value) {
var result = parseFloat(value);
if (isNaN(result) {
return null;
}
return result;
});
You can now use a variable definition with :float, such as $customerId:float.
So far we’ve considered looking up an object for a path, using resolve. Traject also supports the inverse scenario: given an object, reconstruct what path it has. More generally, we are able to locate an object using traject. To locate an object means it has a trajectParent and trajectName, and following the trajectParent chain will eventually lead to the root object.
To help traject locate an object, we need to register an inverse function of the lookup function. A lookup function looks up an object given a set of variables. The inverse of that creates the variables given an object.
Let’s consider a simple scenario:
var departments = {
alpha: { iface: 'department', title: 'Alpha'},
beta: {iface: 'department', title: 'Beta'},
gamma: {iface: 'department', title: 'Gamma'}
};
var getDepartment = function(variables) {
return departments[variables.departmentName];
};
patterns.register('departments/$departmentName', getDepartment);
We now need to create a function that given a department object, gives back its name ($departmentName), in a variables object like this: {departmentName: 'the name'};.
How do we find the name of a department in departments? We can loop through it until we find the department we want the name for, like this:
var getDepartmentVariables = function(department) {
for (var departmentName in departments) {
if (department === departments[departmentName]) {
return {departmentName: departmentName};
}
}
};
Now we can register the inverse function:
patterns.registerInverse('department', 'departments/$departmentName',
getDepartmentVariables);
Now we can locate a department object:
patterns.locate(departments.alpha);
Once we’ve located the department, it will have a trajectParent and a trajectName. Locating an object is especially good to reconstruct its path, which can be useful to help construct links in the the user interface. To construct the path for an object (and automatically locate it first if necessary), you can use patterns.path:
var path = patterns.path(root, departments.alpha);
to get the path departments/alpha back.
In many cases you’d like to register the lookup and inverse at the same time. You can do this using the pattern method:
patterns.pattern('department', 'departments/$departmentName',
getDepartment, getDepartmentVariables);
Obviel Template has extensive internationalization (i18n) support. It lets you mark up those bits of the template that are translatable to another language. This way you can easily construct user interfaces can switch between different languages.
This document describes how to mark up translatable content in a template. To read about how to actually integrate in a translation workflow, and how to use i18n in JavaScript, read the i18n manual.
We want to allow templates to be multi-lingual. To this end we need to be able to indicate which element content and which attributes are translatable.
We can do this with Obviel Template like this:
<p data-trans="">Hello world!</p>
Where trans is short for translate.
The translatable text is now:
Hello world!
and this is also used as its message id in the generated .po file.
So, an empty data-trans tells the system that the element content is translatable. You can also indicate that an attribute is translatable, by naming it in data-trans:
<a title="A title" data-trans="title"></a>
Multiple attributes can be indicated by separating them with a space:
<a title="A title" description="A description" data-trans="title description"></a>
You can also name both element content as well as attribute at the same time for translation:
<a title="A title" data-trans=". title"></a>
Here we use . to indicate the textual element content.
i18n works with {variable} notations. This:
<p data-trans="">I saw {thing}.</p>
will imply the following message id:
I saw {thing}.
And this could be translated to Dutch like this:
Ik heb {thing} gezien.
The translator simply moves the variable to that part in the text where it makes the most sense in that language.
Variables are also be re-orderable:
I saw {thing}. It was {color}.
could translate to the following Dutch:
{color} was {thing} dat ik gezien heb.
For HTML elements with simple textual content, and for any translatable content that’s in HTML attributes, we’re done with the explanations on how to mark up your template. But complexities occur when we mark up an element that contains not only text, but also HTML.
data-each and data-if may not be used on elements contained in an element with data-trans on it (when data-trans is used to indicate the content of the element as opposed to attributes).
So, the following template is illegal and will not compile:
<div data-trans=""><p data-if="foo">Blah</p></div>
data-with and data-if may however be on the same element as a data-trans attribute. In this case, they will be applied before the data-trans attribute is applied. So, this is allowed:
<p data-if="foo" data-trans="">Hello world</p>
If we’re just translating attribute content we’re fine as well:
<div title="A title" data-trans="title"><p data-if="foo">Blah</p></div>
For the sake of simplicity of implementation, data-with is also not allowed within a data-trans.
We introduce this rule because programmatic manipulation of translatable text results in very hard-to-reason about situations that are not really possible to resolve. If you need specific reasoning on how to generate translatable text, we recommend you do this inside JavaScript in the view definition.
Let’s consider the following (illegal) template:
<div data-trans="">The <em>pink</em> elephant.</div>
Why is this illegal? After all, at first glance, the translatable text could be this:
The pink elephant.
But there is a problem: we have no reliable way of finding out where in the translation the pink will move. The French translation for instance is:
L'éléphant rose.
and we’d expect a translated template to look like this:
<div>L'éléphant <em>rose</em>.</div>
Obviel Template has no way however of knowing that rose is the French for pink, and that it should it be in the em` element.
The general problem will occur with any HTML element contained within an element with data-trans on it.
In these cases, we have to help the system a bit by marking up the em element with a data-tvar:
<div data-trans="">The <em data-tvar="color">pink</em> elephant.</div>
Obviel Template will now try to translate two message ids:
The {color} elephant.
and:
pink
So, data-tvar indicates that an element is to be treated as a variable in the content indicated by data-trans.
data-tvar on an element implies that its content is translatable as well; it is therefore very similar in behavior to data-trans. This means a data-tvar may be nested within another data-tvar element. For example:
<div data-trans="">This is a <em data-tvar="something">complicated <a data-tvar="thing" href="">scenario</a></em>.</div>
This results in the following pieces of text marked up for translation:
This is a {something}.
complicated {thing}
scenario
data-trans may not normally be used inside a data-trans, except when it is used to mark up attributes:
<p data-trans="">Hello <em title="Hello world!" data-trans="title" data-tvar="who">{who}</em>!</p>
This combination of data-trans within data-trans is illegal however:
<p data-trans="">Hello <em title="Hello world!" data-trans="" data-tvar="who">{who}</em>!</p>
In the above examples, the system will infer the message ids in the translation files from the text in the template itself. Because in some cases this can lead to ambiguous message ids, you may sometimes want to be more explicit. You can name message ids by using a special syntax in data-trans:
<p data-trans=":hello_world">Hello world!</p>
In this case, the content of the p element will get the message id hello_world, not Hello world! as would have been the default.
The template above is the equivalent of this:
<p data-trans=".:hello_world">Hello world!</p>
You can also do this for attributes:
<a title="A title" data-trans="title:some_title"></a>
and for titles and content combined:
<a title="A title" data-trans=".:some_content title:some_title">content</a>
data-tvar implies that the content contained in an element is translatable too, so we can give it an explicit message id too:
<p data-trans="">The <em data-tvar="color:elephant_color">pink</em> elephant.</p>
Let’s look at this example:
<p data-trans="">{count} cows</p>
When we go about using this, we run into a problem. What if count is 1? We’d see this:
<p>1 cows</p>
But this is wrong. We want to get this:
<p>1 cow</p>
English has two plural forms, one for the singular (1 cow) and one for everything else, plural (5 cows).
We could change our template to use data-if to pick the right plural form, but that will lead to a problem when we want to i18n our template. Some languages have no plural forms, and others have more than two, and will use different rules for selecting which plural form to use based on count.
Instead we can mark up our template to indicate we want to use pluralization:
<p data-trans="" data-plural="count">{count} cow||{count} cows</p>
We have introduce a data-plural directive that indicates that we want to use the count variable to pick the correct plural form. By default in English we have two plural forms, and we define them using the || separator in a template. The singular form goes before || and the plural form goes after it.
When you run the template in the default locale, Obviel Template will pick {count} cow or {count} cows for you automatically based on count. When you run the template in another locale however what translation string is picked can be determined from count in a different way.
What if you want to translate an attribute? Let’s look at another example:
<p data-trans="title" data-plural="title:count" title="{count} cow||{count} cows"></p>
As you can see, you can use data-plural to indicate the pluralization variable for the title attribute. The rules for indicating an attribute or content work the same way as the rules for data-trans, but the value after : is held to be the name of the pluralization variable, not the message id.
The i18n manual goes into detail on how to use pluralizations.
In some cases you may omit data-tvar on sub-elements in a data-trans. This can be done when the name of the data-tvar can be deduced from the variable inside the data-tvar. This template for example:
<div data-trans="">The <em data-tvar="color">{color}</em> elephant.</div>
may also be expressed like this:
<div data-trans="">The <em>{color}</em> elephant.</div>
data-tvar here deduced the variable name from the content of the em element ({color}).
You can also always omit the data-tvar in case of a data-view element:
<p data-trans="">The great <span data-view="foo"></span>.</p>
This will result in a message id like this:
The great {foo}.
Omitting data-tvar not allowed when an element contains something else than just a single variable, such as text or sub-elements. This for instance is illegal as it contains text (*) within the em element:
<div data-trans="">The <em>*{color}*</em> elephant.</div>
In this case, you need to actually mark up the data-tvar:
<div data-trans="">The <em data-tvar="color">*{color}*</em> elephant.</div>
This will result in the following two message ids to translate:
The {color} elephant
and:
*{color}*
When you use || within a data-trans or data-tvar element or within an attribute, you automatically imply pluralization is in use. data-plural is only necessary to indicate the pluralization variable if it cannot be determined unambiguously automatically.
So this example:
<p data-trans="" data-plural="count">{count} cow||{count} cows</p>
Can instead be written like this:
<p data-trans="">{count} cow||{count} cows</p>
because count can be unambiguously determined to be the pluralization variable, as it is the only one in use.
If you had an example like this:
<p data-trans="" data-plural="count">{count} {color} cow||{count} {color} cows</p>
you do need to specify data-plural as now there are two candidates for a pluralization variable, color and count, and we want to use count.
The same rule applies to the attribute example. We can simplify this:
<p data-trans="title" data-plural="title:count" title="{count} cow||{count} cows"></p>
To this:
<p data-trans="title" title="{count} cow||{count} cows"></p>
because the pluralization variable can be unambiguously determined.
GREAT RENAMING: this one breaks backwards compatibility! Any names previously using under_scores now use camelCase, to better follow JavaScript conventions. This means your previous Obviel code will break. In particular:
In general you can straightforwardly translate under_score to camelCase everywhere.
There are a few exceptions. _ is of course still to mark translations. Constants such as NAME_TOKEN also keep the underscore. We also recommend that if you have a variable named after a translation domain (i.e. en_US) you keep the underscore as well. Finally in Obviel template it is still used to refer to special variables when you loop through a dotted name (i.e. {@each.something_persons.number} if you are looping through something.persons.
If you use Emacs you can translate the underscores to camelcase in a file automatically, using the following command:
M-x replace-regexp
Replace regexp: \(\w\)_\(\w\)
with: \1\,(upcase \2)
Backwards compatibility: $(el).render(obj) now returns a jQuery promise. This is a more modern alternative for the callback argument. To track whether a view is done rendering you can hook in with .done. The callback argument has been removed, breaking backwards compatibility. You can rewrite:
$(el).render(obj, function() { this.el });
to this:
$(el).render(obj).done(function(view) { view.el });
A view is only considered done rendering (its promise will be resolved) when all its data-view sub-views have been resolved too.
Backwards compatibility: previously when you implemented render in a view, it could optionally receive a number of arguments (el, obj, name, callback, errback). callback and errback have been removed. el, obj and name have been available already on this, and these are the preferred way to access them.
This change also affects the cleanup method; if you implement it, it will not receive the el, obj, name arguments anymore.
Unfortunately <element>, <attribute> and <block> did not work well in IE. Instead you can now use data-el, data-attr and data-unwrap on any DOM element.
Introduce data-handler directive to hook up events directly from template. With Obviel, this will hook up events to view directly.
The template constructor now accepts templates that consist of multiple elements on top (“<p>first</p><p>second</p>”). It also accepts templates that are a piece of text and an element following (“text<p>following</p>”) or are an element followed by a piece of text (“<p>first</p>followed”).
Templates can be inlined in a <compilerName>Script section in the HTML document.
Fix a bug in data-id generation where data-id="not_a_variable" wasn’t correctly handled.
Change Obviel to be compatible with jQuery 1.7.2. There were two issues:
While Obviel may continue to work with older jQuery versions for a while, we’re going to target jQuery 1.7.2 now for our testing.
the json2 dependency, which takes care of JSON generation compatibility for Internet Explorer, has been moved to the dependencies directory out of the Obviel directory; this is where it belonged all along.
Formatters and funcs can now be more flexibility plugged into Obviel Template, just like translations and event handlers. Obviel core makes use of this by looking up formatters and funcs on the view first, then if it cannot be found there, in the global registries.
Improved i18n infrastructure for translations. See the i18n documentation for more information.
Pluralization support both in JavaScript and in Obviel Templates. See i18n documentation for more information.
use Pygments to highlight HTML and JavaScript snippets properly in the docs.
Rewritten compiler loading and caching infrastructure. Caching has been generalized and improved. Also helps prepare for better template error reporting.
When an error happens during Obviel template rendering the exception will be logged to the console with location information to help with debugging.
You can optionally return a jQuery promise from a view’s render method. If you do, subview rendering and view rendering completion waits until that promise is resolved.
When there is a CompilationError or RenderError in an Obviel template, log error on console detailing iface, template and xpath in template where error occurred.
Added missing API docs for element.rerender(), element.view(), element.parentView() and element.unview().
Use mockjax for cleaner AJAX testing.
Remove pyobviel, obviel-datatables and obvielts. These were unfinished and can always be resurrected at some point in the future.
the src attribute is like the id attribute special in that it may not be directly used with variables in a template. This is because an image src for instance is directly loaded as soon as the template is parsed, and this does not work with variables and will result in a network error. Instead we force the developer to use data-src to set the src attribute, just like we force the developer to set data-id.
traject is now placed in the obviel namespace for consistency with the other modules. It can still be used stand-alone, however.
Fixed a bug where data-view could not be used with URLs.
Changed documentation theme.
Updated documentation for clarity in various areas.
Initial public release.
Obviel forms are a way to create web forms. Obviel forms are interactive and allow you to create complex forms with ease.
It is instructive to briefly examine HTML forms and patterns involving them, before we dive into the details of Obviel forms and how these patterns are implemented.
A HTML form consists of a HTML form element. Inside the form element are various input, textarea and select elements describing the fields of the form that the user can fill in. In addition there are one or more button or submit input elements that let the user submit the form, or cancel the submission. More complex JavaScript asssisted input widgets may also exist, for instance to allow the user to enter a date.
When the form is submitted, it is sent to a particular URL, identified with the action attribute on the form tag. This initiates a form submission. The entered data are encoded, either in the URL if the form is submitted with a GET request, or in a form data format when doing a POST submission.
The web server software or web framework on the server side that you use knows how to decode the form data, and then hands it to your application in some way, to take an action: for instance to add a record to a database.
When browser submits a form to the action URL, the action URL also sends back a HTML response, which the browser then displays. This can contain the results of the form processing.
Form handling in an application tends to follow certain patterns. We’ll discuss some of these patterns and their traditional implementation here.
An add form is used to add new information to the application: a blog entry, someone’s address, or whatever else is relevant to the application domain. Add forms are empty when they are displayed first: its fields are either empty or have a default value in them that the user can alter.
An edit form is used to change existing information in the application: change a blog entry, modify your user account data, and so on. Edit forms are shown filled-in when you open them: the fields show the information that you are about to change, and allows you to modify them.
When a form is submitted, a form needs to be validated: for instance checking whether a number is in range, or a text input is not too long, or a list is one of several allowed values. A very common validation is to make sure a required value was filled in at all.
When a form is submitted, form data also needs to be converted: for instance, numbers are inputed as text strings, and need to be converted to actual numbers that the underlying application can deal with. A checkbox may be turned into a true/false value, and a date string needs to be parsed into a date object. Conversion needs to take place before validation can take place.
Conversion and validation can be client-side or server-side.
Client-side validation, typically in JavaScript, lets the form give instant feedback to the user that is fillin in the form, before the user even submits the form. This increases the usability of the form, as the user does not need to wait for a relatively long submit/reload cycle in order to get this information. Client-side validation does not ensure that the user really inputs the allowed information however.
Server-side validation has the benefit of being secure: the server can validate that the data entered by the client is really allowed by the application. This is important especially if the web application faces the public.
A traditional pattern of doing HTML forms is doing no client-side validation at all except for what is offered by bare HTML, and doing all the validation on the server. To display validation errors a round-trip procedure is required, where the whole form is submitted to the server. If conversion or validation failed, the whole form is shown again showing validation errors, along with any values the user previously filled in.
Advanced form patterns involve composite and repeating fields. A composite field is a field composed out of other fields. An example of a composite field is a date field that consists of three fields allowing you to enter the year, month and day separately from each other. A repeating field is a field where a list of values (sub-forms) needs to be entered. A form where you enter the crew of a ship for instance should allow you to enter more than one crew member, adding as many as you need.
In a form system without client-side code, a repeating field requires a complicated round-trip with the server. The server needs to have a sophisticated system for decoding nested data structures from the flat HTML form-data that’s been submitted by the client, and also a system for generating nested forms.
Obviel does several things differently:
Let’s look at an example first.
Here is an Obviel form:
{
ifaces: ['viewform'],
form: {
widgets: [
ifaces: ['textlineField'],
name: 'test',
title: 'Test',
validate: {
required: true
}
],
controls: [
{
'label': 'Submit',
'action': 'http://example.com/submit'
}
]
},
data: {
'test': 'Some value'
}
}
If this JSON is assigned to an object form in JavaScript, this is how we would render this form in an element somewhere on the page:
el.render(form);
The form would have one field, test, with the value Some value filled in (from the data object). The form would also have a submit button labeled Submit.
When the user changes the form, the data object will be automatically updated.
The test field is required. This means that a validation error will appear inline if the user attempts to submit the form without filling in this field, and submission will not proceed. Validation is handled on the client side.
Conversion is also performed partially on the client-side: an integerField will convert to a JavaScript number for instance, and a booleanField to a JavaScript true or false value.
Further conversion and validation can be performed on the server-side. Conversion is done to cast a value into one more convenient for the server-side: a date string may be converted into a date object, for instance. Validation after form submission on the server side is entirely restricted to validation for security purposes and not used for feedback purposes: is the JSON structure received actually legitimate considering the constraints of the form? In that case there is either an attempted security breach or an error, and server side form handling should not proceed. Since these situations should not occur in normal usage, there is no mechanism for server-side validation error reporting. (A mechanism for server-assisted client-side inline validation is however provided: a form may have a global validation URL that will validate the data object and return validation errors for it in a JSON structure. This information is then used by the client to supplement the inline validation procedure)
When the user presses the submit button on the form, the data object will be submitted as JSON to the control’s action URL, in this case http://example.com/submit.
Nested and repeating structures come naturally to Obviel forms: a nested field’s data is simply a sub-object of data, and a repeating field’s data is an array of sub-objects.
When Obviel Forms sets an error message (either field-error or global-error) for a field, or clears such an error message, an event is sent to the element on which the message is set. The event names are:
These events bubble up the DOM, so you can bind to these events on a higher level, such as the form or the document. The event object passed to the handler has a target property that can be used to retrieve the element on which the message was set.
You can override the way the error area per fields looks by registering a view on the special obvielFormsErrorArea iface. You need to do this after the obviel-forms file has been included. In it you should make sure of creating two elements which can contain text content that have the id this.obj.fieldErrorId and this.obj.globalErrorId, so that the errors can be rendered.