Backbone.Poised ||= {}
class Backbone.Poised.Form extends Backbone.Poised.View
tagName: 'form'
className: 'poised'
events:
'submit': 'preventOriginalSubmit'
preventOriginalSubmit: (e) -> e.preventDefault()
extractGroupOptions: (options = {}) ->
@options.group = _.chain(options)
.pick('by', 'collapsible', 'collapsed')
.defaults
collapsible: false
collapsed: []
.value()
extractOptions: (options = {}) ->
# TODO: Maybe default titles should use the name of the model or
# the localisation for models...
@options = _.chain(options)
.pick('cancelable', 'resetable', 'title', 'creationTitle', 'editingTitle', 'liveForm', 'validate')
.defaults
cancelable: false
resetable: false
liveForm: false
validate: @model.__proto__.validation?
.value()
@extractGroupOptions(options.group)
extractFields: (options = {}) ->
if options.fields
@fields = options.fields
else if @model.formFields?
@fields = @model.formFields
else
@fields = []
initialize: (options = {}) ->
throw new Error('Missing `model` option') unless options.model?
@extractOptions(options)
@extractFields(options)
validateSubmit: =>
errors = @model.validate()
if _.isEmpty(errors)
@trigger('validSubmit', @model)
else
@trigger('invalidSubmit', @model)
# Refocus the first invalid input control, causing the browser to
# scroll to this element automatically.
@$('.invalid input').first().blur()
@$('.invalid input').first().focus()
submit: =>
@trigger('submit', @model)
@validateSubmit() if @options.validate
cancel: =>
@trigger('cancel', @model)
appendGroup: (group, fields) =>
$grp = $('
', class: 'poised control-group')
@appendItem(field, $grp) for field in fields
@$el.append($grp)
appendItem: (field, $el = @$el) =>
# Make sure we do not override defaults by cloning the field defaults
f = _.clone(field)
f.type = 'anchor' if f.anchor?
f.type = 'range' if f.range?
f.type = 'select' if f.options?
f.type ||= 'text'
f.locale = @locale
f.localePrefix = @localePrefix
@subviews[f.attribute] = @controlView(f)
$el.append(@subviews[f.attribute].render().el)
# Instantiates the correct form control view for the given field
# object. Eventually sets default values for needed options (e.g.
# models, the parent view, etc.) and sets the correct validation
# function.
#
# @param [Object] field The field
#
# @returns [Backbone.Poised.LinkedControl] view The view for the control
controlView: (field) ->
field = _.defaults field,
model: @model
parentView: this
field.validate = @model.__proto__.validation and _.has(@model.__proto__.validation, field.attribute)
switch field.type
when 'text' then new Backbone.Poised.StringControl(field)
when 'range' then new Backbone.Poised.RangeControl(field)
when 'number' then new Backbone.Poised.NumberControl(field)
when 'select' then new Backbone.Poised.SelectControl(field)
when 'value' then new Backbone.Poised.ValueControl(field)
when 'anchor' then new Backbone.Poised.Anchor(field)
# Binds Backbone.Validations to the respective view and model.
bindValidation: ->
Backbone.Validation.bind(this) if @options.validate
# Returns the string for the title in the form template.
# If a localisation exists, this is used.
title: ->
if @model.isNew()
@loadLocale 'formTitles.create', 'formTitle',
defaultValue: @options.creationTitle or @options.title
returnNull: true
else
@loadLocale 'formTitles.edit', 'formTitle',
defaultValue: @options.editingTitle or @options.title
returnNull: true
render: =>
@$el.html()
title = @title()
@$el.append($('
', text: title)) if title
if @options.group.by
groups = _.chain(@fields)
.pluck(@options.group.by)
.compact()
.unique()
.value()
for group in groups
@appendItem
type: 'anchor'
anchor: group.toCamel()
collapsible: $.ematch(group, @options.group.collapsible)
collapsed: $.ematch(group, @options.group.collapsed)
@appendGroup(group, _.filter(@fields, (f) => f[@options.group.by] is group))
else
@appendItem(field) for field in @fields
unless @options.liveForm
@subviews.buttons = new Backbone.Poised.SubmitControl
parentView: this
cancelable: @options.cancelable
resetable: @options.resetable
@listenTo @subviews.buttons, 'submit', @submit
@listenTo @subviews.buttons, 'cancel', @cancel
@$el.append(@subviews.buttons.render().el)
@bindValidation()
this