define ["./events", "./dom", "underscore"],
(events, dom, _) ->
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.
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"
Data attribute and value added to HTML forms generated by the Tapestry’s Form component from the core library.
DATA_ATTRIBUTE = "data-generator"
DATA_ATTRIBUTE_VALUE = "tapestry/core/form"
TAPESTRY_CORE_FORM_SELECTOR = "form[" + DATA_ATTRIBUTE + "='" + DATA_ATTRIBUTE_VALUE + "']"
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", TAPESTRY_CORE_FORM_SELECTOR, 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", TAPESTRY_CORE_FORM_SELECTOR + " input[type=submit], " + TAPESTRY_CORE_FORM_SELECTOR + " 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