lib/assets/javascripts/unpoly/form.js.coffee in unpoly-rails-0.27.3 vs lib/assets/javascripts/unpoly/form.js.coffee in unpoly-rails-0.28.0
- old
+ new
@@ -26,11 +26,11 @@
will be updated with the validation messages from the server.
By default this looks for a `<fieldset>`, `<label>` or `<form>`
around the validating input field, or any element with an
`up-fieldset` attribute.
- @param {String} [config.fields]
+ @param {String} [config.fields=[':input']]
An array of CSS selectors that represent form fields, such as `input` or `select`.
@stable
###
config = u.config
validateTargets: ['[up-fieldset]:has(&)', 'fieldset:has(&)', 'label:has(&)', 'form:has(&)']
@@ -155,11 +155,11 @@
promise = up.replace(target, url, options)
promise.always -> up.navigation.unmarkActive($form)
return promise
###*
- Observes a field or form and runs a callback when a value changes.
+ Observes form fields and runs a callback when a value changes.
This is useful for observing text fields while the user is typing.
The UJS variant of this is the [`up-observe`](/up-observe) attribute.
@@ -167,13 +167,17 @@
The following would submit the form whenever the
text field value changes:
up.observe('input[name=query]', function(value, $input) {
- up.submit($input)
+ console.log('Query is now ' + value);
});
+ Instead of a single form field, you can also
+ pass, a `<form>` or any container that contains form fields.
+ The callback will be run if any of the given fields change.
+
\#\#\#\# Preventing concurrency
Firing asynchronous code after a form field can cause
[concurrency issues](https://makandracards.com/makandra/961-concurrency-issues-with-find-as-you-type-boxes).
@@ -191,53 +195,56 @@
up.observe('input', { delay: 100 }, function(value, $input) {
up.submit($input)
});
@function up.observe
- @param {Element|jQuery|String} fieldOrSelector
+ @param {Element|jQuery|String} selectorOrElement
+ The form fields that wiill be observed.
+
+ You can pass one or more fields, a `<form>` or any container that contains form fields.
+ The callback will be run if any of the given fields change.
@param {Number} [options.delay=up.form.config.observeDelay]
The number of miliseconds to wait before executing the callback
after the input value changes. Use this to limit how often the callback
will be invoked for a fast typist.
@param {Function(value, $field)|String} onChange
- The callback to execute when the field's value changes.
+ The callback to run when the field's value changes.
If given as a function, it must take two arguments (`value`, `$field`).
If given as a string, it will be evaled as Javascript code in a context where
(`value`, `$field`) are set.
@return {Function}
A destructor function that removes the observe watch when called.
@stable
###
- observe = (selectorOrElement, args...) ->
-
+ observe = (selectorOrElement, extraArgs...) ->
options = {}
callbackArg = undefined
- if args.length == 1
- callbackArg = args[0]
- if args.length > 1
- options = u.options(args[0])
- callbackArg = args[1]
+ if extraArgs.length == 1
+ callbackArg = extraArgs[0]
+ else if extraArgs.length > 1
+ options = u.options(extraArgs[0])
+ callbackArg = extraArgs[1]
$element = $(selectorOrElement)
- options = u.options(options)
- delay = u.option($element.attr('up-delay'), options.delay, config.observeDelay)
- delay = parseInt(delay)
+ $fields = u.multiSelector(config.fields).findWithSelf($element)
callback = null
-
- if u.isGiven(options.change)
- u.error('up.observe now takes the change callback as the last argument')
-
- rawCallback = u.option(u.presentAttr($element, 'up-observe'), callbackArg)
+ rawCallback = u.option(callbackArg, u.presentAttr($fields, 'up-observe'))
if u.isString(rawCallback)
callback = (value, $field) -> eval(rawCallback)
else
- callback = rawCallback or u.error('up.observe: No change callback given')
+ callback = rawCallback or up.fail('up.observe: No change callback given')
- if $element.is('form')
- return observeForm($element, options, callback)
+ delay = u.option($fields.attr('up-delay'), options.delay, config.observeDelay)
+ delay = parseInt(delay)
+ destructors = u.map $fields, (field) ->
+ observeField($(field), options, callback)
+
+ u.sequence(destructors...)
+
+ observeField = ($field, delay, callback) ->
knownValue = null
callbackTimer = null
callbackPromise = u.resolvedPromise()
# This holds the next callback function, curried with `value` and `$field`.
@@ -251,18 +258,18 @@
returnValue = nextCallback()
nextCallback = null
returnValue
check = ->
- value = $element.val()
+ value = u.submittedValue($field)
# don't run the callback for the check during initialization
skipCallback = u.isNull(knownValue)
if knownValue != value
knownValue = value
unless skipCallback
clearTimer()
- nextCallback = -> callback.apply($element.get(0), [value, $element])
+ nextCallback = -> callback.apply($field.get(0), [value, $field])
runAndChain = ->
# Only run the callback once the previous callback's
# promise resolves.
callbackPromise.then ->
returnValue = runNextCallback()
@@ -275,40 +282,29 @@
u.setTimer(delay, runAndChain)
clearTimer = ->
clearTimeout(callbackTimer)
- changeEvents = if up.browser.canInputEvent()
- # Actually we only need `input`, but we want to notice
- # if another script manually triggers `change` on the element.
- 'input change'
- else
- # Actually we won't ever get `input` from the user in this browser,
- # but we want to notice if another script manually triggers `input`
- # on the element.
- 'input change keypress paste cut click propertychange'
- $element.on(changeEvents, check)
+ # Although (depending on the browser) we only need/receive either input or change,
+ # we always bind to both events in case another script manually triggers it.
+ changeEvents = 'input change'
+ unless up.browser.canInputEvent()
+ # In case this browser doesn't support input, we listen
+ # to all sorts of events that might change form controls.
+ changeEvents += ' keypress paste cut click propertychange'
+
+ $field.on(changeEvents, check)
+
check()
# return destructor
return ->
- $element.off(changeEvents, check)
+ $field.off(changeEvents, check)
clearTimer()
###*
- @function observeForm
- @internal
- ###
- observeForm = ($form, options, callback) ->
- $fields = u.multiSelector(config.fields).find($form)
- destructors = u.map $fields, ($field) ->
- observe($field, callback)
- ->
- destructor() for destructor in destructors
-
- ###*
[Observes](/up.observe) a field or form and submits the form when a value changes.
The changed form field will be assigned a CSS class [`up-active`](/up-active)
while the autosubmitted form is processing.
@@ -335,11 +331,11 @@
target ||= u.detect(config.validateTargets, (defaultTarget) ->
resolvedDefault = up.flow.resolveSelector(defaultTarget, options.origin)
$field.closest(resolvedDefault).length
)
if u.isBlank(target)
- u.error('Could not find default validation target for %o (tried ancestors %o)', $field.get(0), config.validateTargets)
+ up.fail('Could not find default validation target for %o (tried ancestors %o)', $field.get(0), config.validateTargets)
unless u.isString(target)
target = u.selectorForElement(target)
target
###*
@@ -447,11 +443,11 @@
###
switchTargets = (fieldOrSelector, options) ->
$field = $(fieldOrSelector)
options = u.options(options)
targets = u.option(options.target, $field.attr('up-switch'))
- u.isPresent(targets) or u.error("No switch target given for %o", $field.get(0))
+ u.isPresent(targets) or up.fail("No switch target given for %o", $field.get(0))
fieldValues = currentValuesForSwitch($field)
$(targets).each ->
$target = $(this)
if hideValues = $target.attr('up-hide-for')
hideValues = hideValues.split(' ')
@@ -869,9 +865,10 @@
config: config
submit: submit
observe: observe
validate: validate
switchTargets: switchTargets
+ autosubmit: autosubmit
)(jQuery)
up.submit = up.form.submit
up.observe = up.form.observe