ribcage-view

A simple base Backbone view class that adds subviews, rendering, and initialization.

ribcage-view

A simple base Backbone view class that adds subviews, rendering, and initialization.

Installing via npm is easy:

npm install ribcage-view

Ribcage requires you to use browserify (or some other way to get require on the front-end).

var View = require('ribcage-view')
 
/*
 * A view that doesn't do anything but render a template
 *
 * - a template is just a function that takes an object
 *   and returns a string to be rendered.
 *
 */
var templateView = new View({
  templatefunction(id){
    return '<div>' + id '</div>'
  }
})
// Extending a ribcage-view 
var Base = require('ribcage-view')
  , _ = require('lodash')
  // it's not required, but using a state model is _highly_ recommended. 
  , State = require('ampersand-state').extend({
    extraProperties: 'reject'
  })
 
var MyView = Base.extend({
 
  template: require('./template.html.hbs')
 
, className: 'myView'
 
, State: State.extend({
    props: {
      text: 'string'
    }
  })
 
, events: {
    'input input': 'onInputInput'
  }
 
// DOM Events 
 , onInputInput: function onInputInput(e){
    this.state.text = e.target.value
   }
 
// Backbone Events 
, bindEvents: function bindEvents(){
    // always stopListening so we don't reattach multiple listeners 
    if (this.state) this.stopListening(this.state)
 
    // listen to state, model, etc… events 
    this.listenTo(this.state, 'change:text', this.onStateChangeText)
  }
 
, onStateChangeText: function onStateChangeText(statevalue){
    console.log(value)
  }
 
// Create Subviews 
, createSubviewX: function createSubviewX(){
    return new SubviewX({})
  }
 
// Lifecycle Methods 
, beforeInit: function beforeInit(options){
    this.state = new this.State(_.omit(options, ['model', 'collection']))
  }
 
// instantiate subviews 
, afterInit: function afterInit() {
    this.subviewX = this.createSubviewX()
  }
 
, afterRender: function afterRender() {
    this.appendSubview(this.subviewX)
  }
 
, context: function context() {
    return this.state.toJSON()
  }
 
})
 
module.exports = MyView

These are methods that are reserved by Ribcage View. Overriding will break expected behavior.

Sets up the view and calls the render method.

Attaches the template to the DOM with all DOM and backbone events attached. Returns the view.

Closes out a view completely by removing it and all subviews from the DOM, destroying all listeners, and the view's DOM node.

options currently only accepts one key: keepDom which is a boolean. When set to true, it will preserve the view's DOM node. This is really only useful if removing many subviews at once that all share a parent node. Instead of removing each node, we can just remove the parent when we're done closing all the subviews.

The close happens in a requestAnimationFrame and the callback will be called when the close is complete.

// kill the subview. This removes it from the DOM, kills all event listeners, closes all subviews, and is the memory-leak free way to kill a view. 
// `keepDom` is false by default. If you want to close, but leave this in the DOM (probably a bad idea), you can set to `true` 
// The `keepDom` option exists for closing many views that share a parent element. You can then remove just the parent element. 
this.myView.close({keepDom: false}, function myViewClosed(){
  console.log('myView has been closed')
})

Closes all subviews. Takes the same options as close().

Iterate over each subview, performing the iterator function. context defaults to the view you're calling from.

// go through all the subviews 
this.eachSubview(function eachSubview(subview) {
  // `this` is the parent view 
  this.doSomething(subview.property)
})

These methods provide defaults, but you should feel free to replace them with your own.

Return an object for the template to use for its data. Defaults to the options.

Called before anything else happens. Is passed the options from the constructor. This is a good place to instantiate a state model.

Called after all the initialization has completed. This is a good place to create subviews.

If defined, will block rendering until done is called. Useful if you need to async load data from the server before anything happens.

Called after loadData but before anything else in the render flow.

Called after the render has happened. This is a good place to put subviews into the DOM.

, afterRender: function afterRender(){
  this.appendSubview(this.mySubview)
}

Called on every render. This is the place to attach backbone events to subviews. Be sure you call stopListening before listenTo so that you don't create multiple listeners for the same event.

, bindEvents: function bindEvents(){
  // always stopListening so we don't reattach multiple listeners 
  if (this.model) this.stopListening(this.model)
 
  this.listenTo(this.model, 'change', this.render)
}

These are methods that assist with common tasks.

Listen to an event from another view (probably a subview), and trigger the same event. This is useful if you want to pass an event up the chain of subviews.

Appends the view to the view's DOM. Optionally, you can pass a specific DOM node to append to.

Just like appendSubview, only it prepends.

Append an array of views. You can optionally pass in the el to append into.

This is an async function that is has better performance than calling appendSubview many times because it creates a documentFragment before appending to the DOM, and uses requestAnimationFrame.

The third argument is an optional callback that is called when the views are in the DOM.

// append many subviews at once 
// e.g. if you want to append a sub view for all models in a collection 
var collectionSubviews = this.collection.map(function(model){
  return new MyView({model: model})
})
// only the first argument is required 
this.appendSubviews(collectionSubviews, this.$('.collection'), function(views){
  // views are rendered after a requestAnimationFrame 
  console.log(assert.deepEqual(views, collectionSubviews))
})

This is like appendSubviews, but it requires the third argument be a Number. It allows you to append a set amount of views per animationFrame which might be necessary for performance reasons.

batchAppendSubview takes two callbacks. The first is called on every batch append, the second is called when all views have been appended.

// if your subviews take time to render and slow down the DOM 
// will render 2 subviews at a time. 
this.batchAppendSubviews(collectionSubviews
  , this.$('#place')
  , 2
  , function onPortionAppended(){
    console.log('2 more subviews appended')
  }
  , function onBatchAppended(){
    console.log('all subviews appended')
  }
)

Remove a view from the DOM, but keep it's node around in memory. This is useful if you want to move it to a different node.

Finds all subviews that have the model set as their model and closes them.

this.listenTo(this.collection, 'remove', this.closeSubviewsByModel)

Like closeSubviewsByModel, but only detaches.

  1. Remember to call View.prototype.initialize.apply(this, arguments) if you are override initialize. This fixes the loadData is not defined error. Alternatively, override afterInit and beforeInit instead.
  2. Subviews are closed when their parent is rendered, and will lose their events unless you call .delegateEvents() on them in the parent's afterRender. See #5.

Run this command to run tests at http://localhost:9999.

grunt dev

Tests can be run on Saucelabs.

# Run once to set up login info 
export SAUCE_USERNAME=YOUR-SAUCE-USERNAME
export SAUCE_ACCESS_KEY=YOUR-SAUCE-API-KEY
 
# Run to test on sauce 
$ grunt test