Skip to content

Instantly share code, notes, and snippets.

@tysonmote
Created March 1, 2013 19:13
Show Gist options
  • Save tysonmote/5066995 to your computer and use it in GitHub Desktop.
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.
#
# 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