Created
March 1, 2013 19:13
-
-
Save tysonmote/5066995 to your computer and use it in GitHub Desktop.
Some helper classes to make rendering Backbone views less of a pain in the ass. SimpleSpin and SmallError are custom classses -- you'll have to implement them yourself.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# Backbone.HandlebarsView | |
# ======================= | |
# | |
# HandlebarsView is a Backbone.View class that renders Handlebars templates. | |
# HandlebarsView handles nested subviews. Currently, HandlebarsView doesn't do | |
# piecemeal re-rendering. When render() is called, it re-renders all subviews, | |
# as well. | |
# | |
Backbone.HandlebarsView = Backbone.View.extend( | |
# Handlebars template function. | |
template: null | |
# Delegate these method names to the model methods. | |
modelDelegates: [] | |
# Delegate these method names to model attribute getters. | |
modelAttributeDelegates: [] | |
initialize: -> | |
_.each( @modelDelegates, (name) => | |
this[name] = => | |
attr = @model[name] | |
if typeof attr is "function" | |
attr.apply( @model, arguments ) | |
else | |
attr | |
) | |
_.each( @modelAttributeDelegates, (name) => | |
this[name] = => @model.get( name ) | |
) | |
# Render this view and then render all subviews. Subviews are declared in the | |
# template and have their element and model values added to the `subviews` | |
# array, which is then turned into a view and rendered. | |
render: -> | |
@subviews = [] | |
@$el.html( this.template( this ) ) | |
this.delegateEvents( @events ) if @events | |
# Render our subviews. | |
@subviews = _.map( @subviews, (attrs) -> | |
view = new attrs.klass( el: attrs.el, model: attrs.model ) | |
view.render() | |
view | |
) | |
@rendered = true | |
return this | |
findSubviews: (klass) -> | |
_.filter( @subviews, (subview) -> subview instanceof klass ) | |
remove: (removeWrapper = false) -> | |
_.each( @subviews, (subview) -> subview.remove?() ) | |
if removeWrapper then @$el.remove() else @$el.empty() | |
@rendered = false | |
this.trigger( "remove" ) | |
this | |
) | |
# | |
# Backbone.AjaxHandlebarsView | |
# =========================== | |
# | |
# HandlebarsView subclass that displays a spinner while the model is loaded | |
# via AJAX. Only the first AJAX request will display a spinner. All subsequent | |
# requests will simply update the view after being fetched. | |
# | |
Backbone.AjaxHandlebarsView = Backbone.HandlebarsView.extend( | |
# Return true when the model is ready to be rendered. | |
modelIsReady: -> !_.isEmpty( @model.attributes ) | |
# Return an appropriate error message for the given HTTP status code. | |
errorMessage: (status) -> "Error" | |
initialize: -> | |
Backbone.HandlebarsView::initialize.apply( this ) | |
@spinner = new SimpleSpin( @$el ) | |
@error = new SmallError( @$el ) | |
@model.on( "change", _.bind( this.render, this ) ) | |
@model.on( "error", _.bind( this.renderError, this ) ) | |
this.on( "remove", _.bind( this.stopRequest, this ) ) | |
render: (refreshData = false) -> | |
Backbone.HandlebarsView::render.apply( this, arguments ) | |
if this.modelIsReady() | |
@spinner.stop() | |
@model.fetch() if refreshData is true | |
else | |
@rendered = false # we're not "rendered" until the data is loaded / displayed | |
@spinner.start( this.renderStatusEl() ) | |
@request = @model.fetch() | |
renderError: (_, response) -> | |
@error.message( this.errorMessage( response.status ), this.renderStatusEl() ) | |
renderStatusEl: -> @$el.find( ".render-status" )[0] | |
stopRequest: -> | |
@request?.abort?() | |
delete @request | |
) | |
# | |
# Backbone.RefreshingHandlebarsView | |
# ================================= | |
# | |
# HandlebarsView subclass that refreshes the model's data on an interval, | |
# rerendering if needed. | |
# | |
Backbone.RefreshingHandlebarsView = Backbone.HandlebarsView.extend( | |
initialize: -> | |
Backbone.HandlebarsView::initialize.apply( this ) | |
@model.on( "change", _.bind( this.render, this ) ) | |
this.on( "remove", _.bind( this.stopRequest, this ) ) | |
@timeouts = [] | |
refreshInterval: -> | |
throw "Not implemented -- refreshInterval should return ms" | |
hardRefresh: -> | |
this.stopRequest() | |
this.trigger( "hardRefresh" ) | |
this.refresh() | |
refresh: -> | |
this.stopRequest() | |
@request = @model.fetch( success: => | |
@timeouts.push( | |
setTimeout( _.bind( this.refresh, this ), this.refreshInterval() ) | |
) | |
) | |
stopRequest: -> | |
clearTimeout( timeout ) while timeout = @timeouts.pop() | |
@request?.abort?() | |
delete @request | |
) | |
# | |
# Handlebars Helpers | |
# | |
# Initializes a subview inside of `view` with the given subviewClass and model. | |
# It returns a placeholder tag that will later be rendered to by the subview. | |
Backbone.HandlebarsView.subview = (view, subviewClass, model ) -> | |
id = _.uniqueId( "subview" ) | |
# Register this subview with the parent view | |
view.subviews.push( klass: subviewClass, el: "##{id}", model: model ) | |
# Build the render destination element | |
attrs = subviewClass.attributes || {} | |
attrs.id = id | |
attrs.class = subviewClass::className if subviewClass::className | |
tagName = subviewClass::tagName || "div" | |
attrsString = _.map( attrs, (v, k) -> "#{k}=\"#{v}\"" ).join( " " ) | |
return new Handlebars.SafeString( "<#{tagName} #{attrsString}></#{tagName}>" ) | |
# Render a model with the given view in a <div> as a subview. A placeholder | |
# element will be returned, which the subview will later render to. | |
Handlebars.registerHelper "view", (view, model) -> | |
model = model.apply(this) if typeof model is "function" | |
subviewClass = _.constantize( view ) | |
Backbone.HandlebarsView.subview( this, subviewClass, model ) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment