Skip to content

Instantly share code, notes, and snippets.

@laser
Last active September 24, 2016 23:45
Show Gist options
  • Save laser/9095743 to your computer and use it in GitHub Desktop.
Save laser/9095743 to your computer and use it in GitHub Desktop.
Marionette Best Practices
# unless there's a really good reason to do so, a view should not be passed a reference to a preexisting DOM element
view = new BadPhoneInputView(el: $('#phone'))
# instead, write your markup as a string-property of the view prototype to be evaluated at render-time
class GoodPhoneInputView extends Backbone.Marionette.ItemView
template: "<input type='text' name='phone'><a class='submit' href='#'>submit</a>"
# this allows us to unit-test the view in-memory, independently of the document
describe 'GoodPhoneInputView', ->
beforeEach ->
@view = new GoodPhoneInputView()
@view.render()
it 'contains the elements we expect', ->
expect(@view.$('submit').length > 0).toBe(true)
# templateHelpers allow us to interpolate values into our markup at render-time
class GoodPhoneInputView extends Backbone.Marionette.ItemView
template: "<input type='text' name='phone' value='<%= phone() %>'><a class='submit' href='#'>submit</a>"
templateHelpers:
phone: () ->
this.name # 'this' refers to the 'attributes' property of the view's 'model' property
# now we can test in-memory, without appending anything to the document
describe 'GoodPhoneInputView', ->
beforeEach ->
@model = new Backbone.Model()
@view = new GoodPhoneInputView(model: @model)
it 'renders with a phone number, when provided', ->
phoneNumber = '2069991212'
@model.set('phone', phoneNumber, silent: true)
@view.render()
expect(@view.$('input').val()).toEqual(phoneNumber)
# definitely don't do this, because the this.$el hasn't been built
class EditContactView extends Backbone.Marionette.ItemView
initialize: (options) ->
@phone_input = new PhoneInputView el: @$('.phone.primary'), model: @model
@phone_input.render()
template: "<div class='phone primary'></div>"
# this is better ($el will be ready) - but sub-view won't be closed when parent is closed
class EditContactView extends Backbone.Marionette.ItemView
onRender: () ->
@phone_input = new PhoneInputView el: @$('.phone.primary'), model: @model
@phone_input.render()
template: "<div class='phone primary'></div>"
# do this:
class EditContactView extends Backbone.Marionette.Layout
regions:
phoneInput: ".phone.primary"
onRender: () ->
@phoneInput.show(new PhoneInputView(model: @model))
template: "<div class='phone primary'></div>"
# to communicate between a parent a child view, pass a message bus aka a "vent"
class EditContactView extends Backbone.Marionette.Layout
initialize: () ->
@_vent = new Backbone.Marionette.EventAggregator()
@listenTo(@_vent, 'phone:updated', @_onPhoneUpdated)
onRender: () ->
@phoneInput.show(new EditPhoneNumberView(vent: @_vent))
regions:
phoneInput: ".phone.primary"
template: "<div class='phone primary'></div>"
_onPhoneUpdated: (phoneNumber) ->
alert("sub view populated phone number: #{phoneNumber}")
# our sub-view publishes on a message bus
class EditPhoneNumberView extends Backbone.Marionette.ItemView
events:
'change input': '_onInputChanged'
initialize: (options) ->
@_vent = options.vent
template: "<input type='text' name='phone'><a class='submit' href='#'>submit</a>"
_onInputChanged: (e) ->
@_vent.trigger('phone:changed', $(e.target).val())
# don't do this
class MyIncomeFormView extends Backbone.Marionette.ItemView
bindElements: () ->
@$incomeWageType = @$('#income_wage_type')
events:
'submit form': '_onFormSubmitted'
onRender: () ->
@bindElements
template: """
<form action='#' method='POST'>
<input type='text' id='income_wage_type'>
</form>
"""
_onFormSubmitted: (e) ->
e.preventDefault()
alert(@incomeWageType.val())
# use the "ui" object instead
class MyIncomeFormView extends Backbone.Marionette.ItemView
events:
'submit form': '_onFormSubmitted'
onFormSubmit: (e) ->
e.preventDefault()
console.log(@ui.incomeWageType.val())
ui:
incomeWageType: '#income_wage_type'
template: """
<form action='#' method='POST'>
<input type='text' id='income_wage_type'>
</form>
"""
# don't do this
class BalanceDisplayView extends Backbone.Marionette.ItemView
initialize: (options) ->
@listenTo(@model, "change:balance", this._onBalancedChanged);
_onBalanceChanged: () ->
console.log("The new balance is: #{@model.get('balance')}")
# use the modelEvents object instead
class BalanceDisplayView extends Backbone.Marionette.ItemView
modelEvents:
change: "_onBalanceChanged"
_onBalanceChanged: () ->
console.log("The new balance is: #{@model.get('balance')}")
class PhoneNumberModel extends Backbone.Model
validate: (attributes, options) ->
if attributes.phone == ''
return 'must enter a phone number'
class EditPhoneNumberView extends Backbone.Marionette.ItemView
events:
'change input': '_onInputChanged'
'click .submit': '_onSubmitClicked'
initialize: (options) ->
@model = new PhoneNumberModel()
modelEvents:
invalid: _displayErrors
template: "<input type='text' name='phone'><a class='submit' href='#'>submit</a>"
_displayErrors: (model, error) ->
alert(error)
_onInputChanged: (e) ->
@model.set('phone', $(e.target).val())
_onSubmitClicked: (e) ->
e.preventDefault()
alert('phone entry complete!') if @model.isValid()
# definitely don't do this, because this component relies on being passed an element that's already in the document
class FlashUploaderView extends Backbone.Marionette.ItemView
initialize: () ->
config =
uploadUrl: "/upload"
flash_url: "/js/vendor/swfupload/swfupload.swf"
button_placeholder: @$('.swfUploadButton')[0]
@flashUploader = new SWFUpload(settings)
template: "<button class='swfUploadButton'></button>"
# onDomRefresh callback will be fired once the view has been rendered, shown, and is in the document
class FlashUploaderView extends Backbone.Marionette.ItemView
onDomRefresh: () ->
config =
uploadUrl: "/upload"
flash_url: "/js/vendor/swfupload/swfupload.swf"
button_placeholder: @$('.swfUploadButton')[0]
@flashUploader = new SWFUpload(settings);
template: "<button class='swfUploadButton'></button>"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment