jQuery View

Markup as JavaScript Development | Production (6KB)


A class and inheritance system for templates which can be constructed with a mix of pure JavaScript, HTML strings, jQuery templates and jQuery objects:

ListView = $.view(function(){
  return this.ul( //pure JavaScript
    "<li>Item One</li>", //HTML strings
    "<li>${key}</li>", //jQuery Templates
    $(this.li('Item Three')).click(this.handleClick) //inline jQuery
  );
},{
  //methods are auto proxied, "this" is always the view
  handleClick: function(event){}
});
//use views as arguments to jQuery
var instance = new ListView({key:'Item Two'});
$(instance).appendTo('body');

Works Well With

Copyright 2011 Ryan Eastridge. Released under the MIT or GPL License.
Thanks to _why for Markaby.

Class Creation

Use $.view to create a new View class. $.view takes two arguments, a constructor function that must return a DOM element or HTML string, and an optional hash of instance methods.

MyView = $.view(function(){
  return this.div();
},{
  methodName: function(){}
});

The constructor may also be specified as “initialize”:

MyView = $.view({
  initialize: function(){
    return this.div();
  }
});

The element returned by the constructor is available via the element method. Passing a View instance to jQuery is the same as passing the View’s element to jQuery.

var instance = new MyView();
instance.element().tagName == 'DIV';
$(instance).appendTo(document.body);

All instance methods specified are automatically proxied, so you can pass an instance method as an event handler and “this” will still refer to the view instance.

$('<a href="#">My Link</a>').click(this.handleClick);

A View class may optionally attach to an Element that is already on the page, in which case the constructor must always be called with an Element:

MyView = $.view(function(element){});
new MyView($('#my_div'),{key:'value'});

Attributes

View classes take only one argument when creating a new instance: an optional hash of attributes. Attributes are accessed using get, set, and attributes which will return a plain hash of the View’s attributes.

var instance = new MyView({
  key: 'value'
});
instance.get('key');
instance.attributes();

Subclasses

Views can be subclassed by passing a View class as the first argument to $.view. The constructor will receive the parent’s element as the only argument. The constructor does not need to return an element since the parent’s constructor has already generated it. Any events bound to the parent class will be triggered on the child class.

MyViewSubclass = $.view(MyView,function(element){
  $(element).addClass('two');
  this.ready(function(){
    //do something special only in this subclass
  });
},{
  childMethod: function(){}
});

A subclass constructor may optionally return an element. In this case the subclass will return the parent’s p tag wrapped in a div.

ParagraphView = $.view(function(){
  return this.p(this.get('text'));
});

EnhancedParagraphView = $.view(ParagraphView,function(element){
  return this.div({className:'enhanced'},element);
});

Singletons

View classes implement a variant of the Singleton pattern with the instance method. This method will return the same instance of a view every time it is called, or will create it if instance is being called for the first time on that class.

var instance = MyView.instance();

Builder

All HTML tag names are available as methods inside of View classes. Each view method takes a variable number of arguments which can be passed in any order and returns a DOM element. Possible arguments are:

A hash of HTML attributes:

this.a({href:'#',className:'my_link'});

A string:

this.p('Paragraph text.');

DOM Elements:

this.ul({className:'my_list'},
  this.li('Item One')
  this.li(this.b('Bold List Item Two'))
);

HTML strings can be mixed and matched:

this.form(
  '<p class="label">Author</p>',
  this.input({name:'author',type:'text'}),
  this.p({className:'label'},'Body <b>Required</b>'),
  '<textarea name="body"></textarea>'
);

Templates will be rendered with the view’s attributes and methods:

MyView = $.view(function(){
  this.set('key','value');
  this.ul(
    '<li>${key}</li>'
  );
});

jQuery objects can be used. Any instance methods defined by the class will automatically be proxied, so “this” will always refer to the view instance if passed to an event handler.

MyView = $.view(function(){
  return this.ul(
    this.li(
      $(this.a({href:'#'},'My Link')).click(this.handleClick)
    )
  );
},{
  handleClick: function(event){
    //this == MyView instance
    var element = event.target;
    return false;
  }
});

Methods will be called:

this.ul(this.generateListItems);

View classes and view instances:

this.ul(
  this.li('Item Two'),
  ListItemView, //will be initialized with no attributes
  new ListItemView({name:'Item Three'})
);

An array (which will be flattened) of any of the above can be used as well:

this.ul(
  this.li('Item One'),
  [
    this.li('Item Two'),
    this.li('Item Three'),
    [
      this.li('Item Four')
    ]
  ]
);

The map method returns an Array and is designed to be used with builder methods. It accepts an array or object. “this” will always refer to the view instance inside of the iterator.

this.ul(
  this.map(['One','Two','Three'],function(item,i){
    return this.li('Item ' + item);
  })
);

this.ul(
  this.map({
    'jQuery': 'http://jquery.com/',
    'NodeJS': 'http://nodejs.org/'
  },function(key,value){
    return this.li(this.a({href:value},key));
  })
);

Builder methods are also available in the $.view object if builder methods are needed outside of view classes:

MyView.classMethod = function(){
  return $.view.div();
};

References to elements can be assigned as you build your elements. This saves writing a query to find a particular element you need later.

this.ul(
  this.listItemOne = this.li(),
  this.li()
);
$(this.listItemOne).click(this.clickHandler);

Events

Each View class has the same event method names as jQuery: bind, unbind, one, trigger. View events are not DOM events, there is no event object and an arbitrary number of arguments can be passed to event handlers. Events should be the primary way multiple View’s communicate with each other.

Events are created with the trigger method. All arguments passed to trigger are passed to any registered event handlers. If any event handler returns false, the call to trigger will return false. Otherwise it will return an array of responses from the handlers.

this.trigger('event_name',a,b);

Events can be observed on all instances of a class. Handlers will receive the instance that triggered the event followed by any arguments passed in the event.

MyView.bind('event_name',function(instance,a,b){

});

View’s bind method accepts an optional context parameter. Any arguments after that will be curried onto the handler.

this.bind('event_name',function(c,a,b){
  this == context;
},context,c);

View classes have two built in events. The ready event is triggered when the View’s element has been attached to the DOM. It can be accessed by the ready method or by calling bind(‘ready’,handler).

MyView = $.view(function(){
  this.ready(function(){
    this.nameInput.focus();
  });
  return this.form(
    this.p({className:'label','Name'}),
    this.nameInput = this.input({type:'text'})
  );
});

The change event is triggered whenever attributes in the view have been changed.

MyView.bind('change',function(instance,changed_attributes){
  for(var key in changed_attributes){
    
  }
});

Class

$.view(Object methods) -> Class
$.view(Class parent, Object methods) ->Class
$.view(Function constructor [,Object methods]) -> Class
$.view(Class parent, Function(Element) [,Object methods]) -> Class

Create a new View class:

MyView = $.view(function(){
  return this.div();
},{
  methodName: function(){}
});

The constructor can also be specified via the “initialize” method:

MyView = $.view({
  initialize: function(){
    return this.div();
  },
  methodName: function(){}
});

Or subclass an existing View class:

MyViewTwo = $.view(MyView,function(element){
  $(element).addClass('special');
},{
  childMethod: function(){}
});

A View class may optionally attach to an Element that is already on the page, in which case the constructor must always be called with an Element:

MyView = $.view(function(element){});
new MyView($('#my_div'),{key:'value'});

new Class([Object attributes]) -> instance
new Class(Element [,Object attributes]) -> instance

Creates a new instance of a View class.

var instance = new MyView({
  key: 'value'
});
instance.get('key') == 'value';
$(instance).appendTo(document.body);

Views may optionally attach to an element that is already in the DOM, in which case they require an Element or jQuery object as the first argument, which the constructor will receive:

MyView = $.view(function(element){});
new MyView($('#my_div'),{key:'value'});

Class.instance() -> instance

Get an instance of the View class. instance will create a new instance the first time it is invoked, and will return the same instance on subsequent calls.

var instance = MyView.instance();
instance == MyView.instance();

Core

instance.element() -> Element
instance.element(Element element) -> Element

Get the outermost element of the view, which is returned by the constructor.

var instance = new MyView();
instance.element().tagName == 'DIV'

You can explicitly set the element in the constructor using this method instead of reutrning an Element from the constructor.

MyView = $.view(function(){
  this.element(this.div());
  $(this.element()).addClass('my_div');
});

instance.attributes() -> Object
instance.attributes(Object attributes [,Boolean silent = false]) -> Object

Get a hash of attributes in the view.

var instance = new MyView({key:'value'});
instance.attributes() == {key:'value'};

Or set all attributes in the view. Attributes that are present in the view but not in the passed object will be removed. Set silent to true to prevent the change event from being triggered.

instance.bind('change',function(changed_attributes){
   for(var key in changed_attributes){
     
   }
});
instance.attributes({key:'value'});

instance.get(String key) -> mixed
instance.get(Array keys) -> Array
instance.get(String key [,String key…]) -> Array

Get an attribute from the view.

var instance = new MyView({key:'value'});
instance.get('key') == 'value';

Or get an array of attributes:

instance.get('a','b');
instance.get(['a','b']);

instance.set(Object attributes [,Boolean silent = false]) -> Object

Set attributes in the view. This will trigger the change event. Set silent to true to prevent the change event from firing.

var instance = new MyView();
instance.bind('change',function(changed_attributes){
  for(var key in changed_attributes){}
});
instance.set({key:'value'});

You can bind events to individual keys as well:

instance.bind('change:key',function(value){});

instance.tag([String text] [,Element] [,Object attributes]…) -> Element

tag refers to any HTML tag name and is used to create DOM elements. Tag takes an arbitrary number of arguments in any order which can be:

Sample usage:

this.ul({className:'my_list'},
  this.li('Item One'),
  this.li('Item Two'),
  $(this.li('Item Three')).click(),
  '<li>Item Four</li>'
)

Events

instance.bind(Object events) -> Object
instance.bind(String event_name, Function handler [,Object context]) -> Function

Register a handler for an event on a given instance. “this” will refer to the view instance unless a context argument was passed.

instance.bind('event_name',function(a,b,c){
  
});

You can register an event handler on all instances of a class by calling bind on the class. The handler will receive the instance that triggered the event as the first argument, followed by any other arguments passed by trigger.

MyView.bind('event_name',function(instance,a,b,c){
  
});

Multiple handlers can be registered at once by passing a hash:

instance.bind({
  event_name: function(a,b,c){}
});

instance.unbind([String event_name] [,Function handler]) -> null

unbind an event handler registered on a given instance.

instance.unbind('event_name',handler);

Any event handlers that were registered on the class can be unbound by calling Class.unbind:

MyView.unbind('event_name',handler);

instance.one(String event_name, Function handler [,Object context]) -> Function

This method is identical to bind, except that the handler is unbound after its first invocation.

instance.one('event_name',function(){
  //only called once
});

instance.trigger(String event_name [,mixed arg…]) -> Array or false

Triggers the given event, passing an arbitrary number of arguments to the handlers. Returns an array of responses, or false if a handler stopped the event by returning false.

instance.trigger('event_name',a,b,c);

instance.trigger will notify all handlers bound by instance.bind and Class.bind. Calling Class.trigger will only notify handlers bound by Class.bind.

instance.bind('event_name',handler); //not called by Class.trigger
MyView.bind('event_name',handler); //called by Class.trigger
MyView.trigger('event_name');

instance.ready(Function handler [,Object context]) -> Function

Identical to calling instance.bind(‘ready’,handler) The ready event is triggered when the view’s outermost element is attached to the DOM. “this” will always refer to the view instance unless a context argument was passed.

instance.ready(function(){
  $('input:first',this).focus();
});

Calling ready on the class will observe the ready event of all instances.

MyView.ready(function(instance){
  $('input:first',instance).focus();
});

instance.emit(event_name [,mixed arg…]) -> Function

Creates a callback that will trigger event_name with the supplied arguments.

this.bind('event_name',function(a,b,c){});
$(link).click(this.emit('event_name',a,b,c));

Helpers

instance.map(Array, Function(item,index)) -> Array
instance.map(Object, Function(key,value,index)) -> Array
instance.map(String key_of_array, Function(item,index)) -> Array
instance.map(String key_of_object, Function(key,value,index)) -> Array

Similar to Array#map or Ruby’s Array#collect. Works on objects or Arrays. If an object is passed the iterator will be called with (key,value), if an Array is passed the iterator will be called with (value,index). Inside the iterator “this” will always refer to the view instance.

var NavigationView = $.view(function(){
  return this.ul(this.map({
    'Page Title': 'http://page.com/'
  },function(title,url,i){
    return this.li(
      this.a({href:url},title)
    );
  }));
});

Passing a string key is the same as passing this.get(key) to map:

this.map('links',function(link){});
//equivalent to:
this.map(this.get('links'),function(link){});

A hash of keys and callbacks can also be passed:

this.map({
  an_array: function(item,index){},
  an_object: function(key,value,index){}
})

instance.callback(String method_name, [mixed arg…]) -> Function
instance.callback(Function method, [mixed arg…]) -> Function

Creates a callback function which will call the method with the supplied arguments. The generated callback always returns false, regardless of what the function it calls returns.

MyView = $.view(function(){
  return this.ul(
    this.map([1,2,3],function(i){
      return this.li(
        $(this.a({href:'#'})).click(this.callback('myMethod',i))
      );
    })
  );
},{
  myMethod: function(i){
    //do stuff, no need to return false
  }
});

instance.delegate(String selector, String event_name, Function callback [,Object context]) -> null

Equivelent to calling jQuery’s delegate method, but can be called before the view’s element has been created.

MyView = $.view(function(){
  this.delegate('a','click',this.handleClick);
  return this.ul(
    this.li(this.a({href:'#'},'Link One')),
    this.li(this.a({href:'#'},'Link Two'))
  );
},{
  handleClick: function(){}
});

instance.escape(String unescaped) -> String

Prevent HTML or template strings from being interpreted.

this.p(this.escape('Will not be processed ${key}'));
this.p(this.escape('<b>Will appear as text.</b>'));

instance.$ -> Object | Function

A hybrid jQuery object for the current view’s element that also acts as a selector function scoped to the current view’s element.

//adds "active" class to outermost view element
this.$.addClass('active');
//selects all spans inside the view
this.$('span');

Templates

instance.template -> String

Specify a string template in this property if you want it to be available via the element method before the constructor is called. Also convenient when using CoffeeScript’s multiline strings.

MyView = $.view({
  template: "<p></p>",
  initialize: function(element){
    $(element).addClass('best_paragraph_ever');
  }
});

instance.render(String template [,Object attributes]) -> String

Render a string with the current template engine.

this.set('key','value');
this.render('<li>${key}</li>');

Class.engine() -> String
Class.engine(String engine) -> null
$.view(‘engine’,Object engine) -> null
$.view(‘engine’,String engine) -> null

Get or set the current template engine per class.

MyView = $.view(function(){
  return "<p>{{key}}</p>";
});
MyView.engine('mustache');
MyView.engine() == 'mustache';

To register a new engine, call $.view with ‘engine’ and an object containing name, detect and render. The official jQuery Template plugin is the default engine, and ships with jQuery View as “jquery.tmpl”. The following code would register Mustache as a valid engine:

$.view('engine',{
  name: 'mustache',
  detect: function(string){
    return string.match(/\{\{[^\}]+\}\}/);
  },
  render: function(string,attributes){
    return Mustache.to_html(string,attributes);
  }
});

Registering a new engine will set that engine as the default on all classes unless the class specifies it’s engine with Class.engine(engine). To change the default:

$.view('engine','jquery.tmpl');

Properties

$.view.classMethods -> Object

Methods that are available to all view classes.

$.view.classMethods.myClassMethod = function(){};
MyClass = $.view(function(){});
MyClass.myClassMethod();

$.view.logging -> Boolean

Set this to true to have view classes output console.log messages.

$.view.fn -> Object

Methods that will be available to all instances of all view classes.

$.view.fn.myMethod = function(){
  return this.get('key');
};
MyClass = $.view(function(){});
var instance = new MyClass({
  key: 'value'
});
instance.myMethod(); //returns "value"

Examples

Sample Applications

Data Centric Views

Coming soon.

Effective Usage of jQuery Templates

Coming soon.

Subclassing

Coming soon.

Class

- $.view

- new Class

- instance

Core

- element

- attributes

- get

- set

- tag

Events

- bind

- unbind

- one

- trigger

- ready

- emit

Helpers

- map

- callback

- delegate

- escape

- $

Templates

- template

- render

- engine

Properties

- classMethods

- logging

- fn