Obviel core: Object/View/Element for jQuery

Introduction

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:

  • you add simple type information to the JSON objects that you want to render with views on the client. This is done using the iface property. We also call such JSON objects model. You can add the iface to JSON objects being sent from the server, or you can add it on the client.
  • you define views that know how to render objects of each type of object. You do this by hooking the view to an iface as well.
  • you can then render a view for an object on a DOM element by using a special render function that Obviel adds to jQuery.

All this is pretty abstract, so let’s go into some more detail to try to make it more clear.

How to include Obviel on your web page

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.

Rendering a view

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:

  • It will look up a view for the object model
  • It will ask that view to render the model on the element indicated by the jQuery selector #foo.

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.

What is model?

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.

What is a view?

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.

What just happened?

The steps taken by Obviel are:

  • Obviel looks at the iface property of the model being rendered, in this case example`.
  • Obviel looks up the view registered for the iface example.
  • Obviel sets the el` property of the view to the element that the view is being rendered on, and obj property to the object being rendered.
  • Obviel call the render method on the view.

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.

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.

An example

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.

html, htmlUrl and htmlScript

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.

obvt, obvtUrl, obvtScript: Obviel Template

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.

View lookup by name

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.

Properties available on views

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.

el

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.

obj

This is the model that the view is supposed to render. You access properties of this object to determine what to render.

name

This is the name of the current view. By default it is default.

Rendering subobjects

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.

Better subviews with Obviel Template

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 with a template using data-render:

obviel.view({
  obvt: '<div data-render="attr"></div>'
});

Wow, that’s a lot shorter!

Now let’s examine the application from the section above again, using templates and data-render:

obviel.view({
   iface: 'text',
   obvt: '<p>{text}</p>'
});

obviel.view({
   iface: 'list',
   obvt: '<ul><li data-repeat="entries">{@.}</li></ul>'
});


obviel.view({
   iface: 'page',
   obvt: '<div data-repeat="parts" data-render="@."></div>'
});

As you can see, that has cleaned up the code a lot!

subviews property

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.

Additional methods

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"
});

Declarative event handlers

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-on directive:

obviel.view({
   iface: 'foo',
   obvt: '<div data-on="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

Per view formatters and funcs

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-call:

obviel.view({
   iface: 'foo',
   obvt: '<div data-call="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.

before: intervening before rendering starts

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'
});

Declarative object events

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('update');

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'
  }
});

Render done

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) { ... });

Transformers

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.

View inheritance

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.

Bootstrapping Obviel

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>

Element association

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().

Cleanup

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.

Events sent by Obviel

Obviel triggers two kinds of events:

  • render-done.obviel
  • render.obviel

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.

Iface extension

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.