blob: c8de2643705c64748fd85f6c88c77771006d29f3 [file] [log] [blame]
# Copyright 2012, 2013 The Apache Software Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http:#www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ## t5/core/forms
#
# Defines handlers for HTML forms and HTML field elements, specifically to control input validation.
define ["./events", "./dom", "underscore"],
(events, dom, _) ->
# Meta-data name that indicates the next submission should skip validation (typically, because
# the form was submitted by a "cancel" button).
SKIP_VALIDATION = "t5:skip-validation"
clearSubmittingHidden = (form) ->
hidden = form.findFirst "[name='t:submit']"
# Clear if found
hidden and hidden.value null
form.meta SKIP_VALIDATION, null
return
setSubmittingHidden = (form, submitter) ->
mode = submitter.attr "data-submit-mode"
isCancel = mode is "cancel"
if mode and mode isnt "normal"
form.meta SKIP_VALIDATION, true
hidden = form.findFirst "[name='t:submit']"
unless hidden
firstHidden = form.findFirst "input[type=hidden]"
hidden = dom.create "input", type: "hidden", name: "t:submit"
firstHidden.insertBefore hidden
# TODO: Research why we need id and name and get rid of one if possible.
name = if isCancel then "cancel" else submitter.element.name
# Not going to drag in all of json2 just for this one purpose, but even
# so, I'd like to get rid of this. Prototype includes Object.toJSON(), but jQuery
# is curiously absent an equivalent.
hidden.value "[\"#{submitter.element.id}\",\"#{name}\"]"
return
# Passed the element wrapper for a form element, returns a map of all the values
# for all non-disabled fields (including hidden fields, select, textarea). This is primarily
# used when assembling an Ajax request for a form submission.
gatherParameters = (form) ->
result = {}
fields = form.find "input, select, textarea"
_.each fields, (field) ->
return if field.attr "disabled"
type = field.element.type
# Ignore types file and submit; file doesn't make sense for Ajax, and submit
# is handled by keeping a hidden field active with the data Tapestry needs
# on the server.
return if type is "file" or type is "submit"
return if (type is "checkbox" or type is "radio") and field.checked() is false
value = field.value()
return if value is null
name = field.element.name
# Many modern UIs create name-less elements on the fly (e.g., Backbone); these may be mixed
# in with normal elements managed by Tapestry but should be ignored (not sent to the server in a POST
# or Ajax update).
return if name is ""
existing = result[name]
if _.isArray existing
existing.push value
return
if existing
result[name] = [existing, value]
return
result[name] = value
return result
defaultValidateAndSubmit = ->
where = -> "processing form submission"
try
if ((@attr "data-validate") is "submit") and
(not @meta SKIP_VALIDATION)
@meta SKIP_VALIDATION, null
hasError = false
focusField = null
for field in @find "[data-validation]"
memo = {}
where = -> "triggering #{events.field.inputValidation} event on #{field.toString()}"
field.trigger events.field.inputValidation, memo
if memo.error
hasError = true
focusField = field unless focusField
# Only do form validation if all individual field validation
# was successful.
unless hasError
memo = {}
where = -> "trigging cross-form validation event"
@trigger events.form.validate, memo
hasError = memo.error
if hasError
clearSubmittingHidden this
# If a specific field has been identified as the source of the validation error, then
# focus on it.
focusField.focus() if focusField
# Trigger an event to inform that form validation results in error
where = -> "triggering validation in error event"
@trigger events.form.validateInError
# Cancel the original submit event when there's an error
return false
# Allow certain types of elements to do last-moment set up. Basically, this is for
# FormFragment, or similar, to make their hidden field enabled or disabled to match
# their UI's visible/hidden status. This is assumed to work or throw an exception; there
# is no memo.
where = -> "triggering #{events.form.prepareForSubmit} event (after validation)"
@trigger events.form.prepareForSubmit
catch error
console.error "Form validiation/submit error `#{error.toString()}', in form #{this.toString()}, #{where()}"
console.error error
return false
# Otherwise, the event is good, there are no validation problems, let the normal processing commence.
# Possibly, the document event handler provided by the t5/core/zone module will intercept form submission if this
# is an Ajax submission.
return
dom.onDocument "submit", "form", defaultValidateAndSubmit
# On any click on a submit or image, update the containing form to indicate that the element
# was responsible for the eventual submit; this is very important to Ajax updates, otherwise the
# information about which control triggered the submit gets lost.
dom.onDocument "click", "input[type=submit], input[type=image]", ->
setSubmittingHidden (dom @element.form), this
return
# Support for link submits. `data-submit-mode` will be non-null, possibly "cancel".
# Update the hidden field, but also cancel the default behavior for the click.
dom.onDocument "click", "a[data-submit-mode]", ->
form = @findParent "form"
unless form
console.error "Submitting link element not contained inside a form element."
return false
setSubmittingHidden form, @closest "a[data-submit-mode]"
# Now the ugly part; if we just invoke submit() on the form, it does not trigger
# the form's "submit" event, which we need.
form.trigger "submit"
# And cancel the default behavior for the original click event
return false
exports =
gatherParameters: gatherParameters
setSubmittingElement: setSubmittingHidden
# Sets a flag on the form to indicate that client-side validation should be bypassed.
# This is typically associated with submit buttons that "cancel" the form.
skipValidation: (form) ->
form.meta SKIP_VALIDATION, true