[フレーム]
Last Updated: February 25, 2016
·
2.131K
· rossbrown

Form Errors in Chaplin JS views

When creating form views for models in a Chaplin JS app, it's a good idea to leverage the power of Chaplin's Mediator Pub/Sub pattern for your form submission to render validation and server errors in your form views.

Let's say we have a Party model:

class Party extends Model
 url: '/parties.json'
 defaults:
 ladies: 0
 fellas: 4
 celebrities: 0

 validate: (attrs, options) ->
 if attrs.ladies / attrs.fellas < 1
 "Sausage fest"

and its corresponding "edit" view:

class PartyEditView extends View
 templateName: 'parties/form'

 autoRender: true
 events:
 "submit form": 'saveParty'

 saveParty: (e) ->
 e.preventDefault()
 # Some method to get model attributes from view values goes here
 @model.save(@model.attributes)

So let's say our Party doesn't pass validation. We probably want to know about that, right? Right. Let's first make a simple Error model and a view for it:

class Error extends Model
 defaults:
 text: "Something went wrong"

class ErrorView extends View
 templateName: 'error'
 autoRender: true
 autoAttach: true

So our PartyEditView should do something with this error junk when something goes wrong:

class PartyEditView extends View
 templateName: 'parties/form'

 autoRender: true
 events:
 "submit form": 'saveParty'
 regions:
 '.errors': 'errors'

 initialize: ->
 super
 @error = false

 # When the Party is invalid, do something:
 @model.on "invalid", (model, error) =>
 @renderError(error)

 saveParty: (e) ->
 e.preventDefault()
 @error.dispose() if @error
 # Some method to get model attributes from view values goes here
 @model.save(@model.attributes)

 @renderError: (error) ->
 model = new Error({text: error})
 @error = new ErrorView model: model, region: 'errors'

So now when we try to save the model, it will trigger the invalid event. We've bound a listener to invalid to fire the renderError method, which creates a new Error model and a view for it. The view automatically renders and appends itself to the errors region, which we've defined in the view's regions object. Great.

But what if it passes our front-end validation and the server thinks it's still too lame of a party? We need a way to render errors that the server returns.

First of all, let's look at our PartyEditView. The saveParty method looks fishy. Why is a View concerned about model persistence? We should move @model.save elsewhere. The controller sounds like a good place for this sort of behavior.

class PartiesController extends Controller
 save: (model) -> 
 @model.save model.attributes

But how does the controller know when it should save the model? Chaplin's Pub/Sub service is extremely handy in cases like this. Let's have the view publish a "save_party" message that the controller will listen for.

class PartyEditView extends View
 saveParty: (e) ->
 e.preventDefault()
 @error.dispose() if @error

 # publish the save_party message and pass the model along
 @publishEvent 'save_party', @model
class PartiesController extends Controller
 initialize: ->
 super

 # Listen for word about any cool parties and fire the save method
 @subscribeEvent 'save_party', @save

 save: (model) -> 
 @model.save model.attributes,
 success: (model,response) =>
 alert 'Party was saved!'
 @redirectTo '/'
 error: (model, response) =>
 # Fire the view's renderError method and pass it the server response
 @view.renderError(response)

Typically the server response is going to be an object, so we'll need to modify our view's renderError method a bit to extract some helpful text to display. I have my Rails controller set up to deliver full_messages in the response:

render :json => @party.errors.full_messages, status: 422

So in order to show relevant text to the user, let's do something like this in our PartyEditView's renderError method:

renderError: (error) ->
 @error.dispose() if @error

 if typeof error is 'object'
 # Extract text from each error in server response
 for error in $.parseJSON(error.responseText)
 errorObj =
 text: error
 model = new Error(errorObj)
 @error = new ErrorView model: model, region: 'errors'
 else
 # Display validation error, which is just a string
 errorObj =
 text: error
 model = new Error(errorObj)
 @error = new ErrorView model: model, region: 'errors'

There you have it. Auto-rendering validation and server errors in your view. Ideally you'd take this funcionality and include it in a base view model that you can extend:

class ModelView extends View
 initialize: ->
 super
 @error = false
 @registerRegion '.form-errors', 'errors'
 @subscribeEvent 'renderError', @renderError
 @model.on "invalid", (model, error) =>
 @renderError(error)

 renderError: (error) ->
 @error.dispose() if @error
 if typeof error is 'object'
 for error in $.parseJSON(error.responseText)
 errorObj =
 text: error
 model = new Error(errorObj)
 @error = new ErrorView model: model, region: 'errors'
 else
 errorObj =
 text: error
 model = new Error(errorObj)
 @error = new ErrorView model: model, region: 'errors'

Then you can have error rendering on any view you want, using the @registerRegion method to target a container to render the views in:

class PartyEditView extends ModelView
 templateName: 'party/form'
 autoRender: true
 events:
 "submit form": 'saveParty'
 initialize: ->
 super
 @registerRegion '.party-errors', 'errors'
 saveParty: (e) ->
 e.preventDefault()
 @error.dispose() if @error
 @publishEvent 'save_party', @model

class NoiseViolationEditView extends ModelView
 templateName: 'violation/form'
 events:
 "submit form": 'saveViolation'
 initialize: ->
 super
 @registerRegion '.legal-errors', 'errors'
 saveViolation: (e) ->
 e.preventDefault()
 @error.dispose() if @error
 @publishEvent 'save_violation', @model

AltStyle によって変換されたページ (->オリジナル) /