unpoly-rails 0.57.0 → 0.60.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of unpoly-rails might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +393 -1
- data/Gemfile.lock +5 -2
- data/README.md +1 -1
- data/README_RAILS.md +1 -1
- data/Rakefile +10 -1
- data/design/es6.js +32 -0
- data/design/ie11.txt +9 -0
- data/design/measure_jquery/element_list.js +41 -0
- data/design/measure_jquery/up.on_vs_addEventListener.js +56 -0
- data/design/todo_jquery.txt +13 -0
- data/dist/unpoly-bootstrap3.js +8 -8
- data/dist/unpoly-bootstrap3.min.js +1 -1
- data/dist/unpoly.css +22 -20
- data/dist/unpoly.js +6990 -5336
- data/dist/unpoly.min.css +1 -1
- data/dist/unpoly.min.js +4 -4
- data/lib/assets/javascripts/unpoly-bootstrap3/viewport-ext.coffee +5 -0
- data/lib/assets/javascripts/unpoly.coffee +8 -6
- data/lib/assets/javascripts/unpoly/browser.coffee.erb +23 -118
- data/lib/assets/javascripts/unpoly/classes/body_shifter.coffee +36 -0
- data/lib/assets/javascripts/unpoly/classes/cache.coffee +4 -4
- data/lib/assets/javascripts/unpoly/classes/compile_pass.coffee +45 -39
- data/lib/assets/javascripts/unpoly/classes/config.coffee +9 -0
- data/lib/assets/javascripts/unpoly/classes/css_transition.coffee +18 -27
- data/lib/assets/javascripts/unpoly/classes/divertible_chain.coffee +39 -0
- data/lib/assets/javascripts/unpoly/classes/event_listener.coffee +116 -0
- data/lib/assets/javascripts/unpoly/classes/extract_cascade.coffee +8 -8
- data/lib/assets/javascripts/unpoly/classes/extract_plan.coffee +19 -19
- data/lib/assets/javascripts/unpoly/classes/field_observer.coffee +54 -31
- data/lib/assets/javascripts/unpoly/classes/{focus_tracker.coffee → focus_follower.coffee} +2 -2
- data/lib/assets/javascripts/unpoly/classes/follow_variant.coffee +25 -25
- data/lib/assets/javascripts/unpoly/classes/html_parser.coffee +4 -11
- data/lib/assets/javascripts/unpoly/classes/motion_controller.coffee +157 -0
- data/lib/assets/javascripts/unpoly/classes/params.coffee.erb +525 -0
- data/lib/assets/javascripts/unpoly/classes/record.coffee +8 -2
- data/lib/assets/javascripts/unpoly/classes/rect.js +21 -0
- data/lib/assets/javascripts/unpoly/classes/request.coffee +41 -35
- data/lib/assets/javascripts/unpoly/classes/response.coffee +7 -3
- data/lib/assets/javascripts/unpoly/classes/reveal_motion.coffee +102 -0
- data/lib/assets/javascripts/unpoly/classes/scroll_motion.coffee +67 -0
- data/lib/assets/javascripts/unpoly/classes/selector.coffee +60 -0
- data/lib/assets/javascripts/unpoly/classes/tether.coffee +105 -0
- data/lib/assets/javascripts/unpoly/classes/url_set.coffee +12 -7
- data/lib/assets/javascripts/unpoly/element.coffee.erb +1126 -0
- data/lib/assets/javascripts/unpoly/event.coffee.erb +437 -0
- data/lib/assets/javascripts/unpoly/feedback.coffee +73 -94
- data/lib/assets/javascripts/unpoly/form.coffee.erb +188 -181
- data/lib/assets/javascripts/unpoly/{dom.coffee.erb → fragment.coffee.erb} +250 -283
- data/lib/assets/javascripts/unpoly/framework.coffee +67 -0
- data/lib/assets/javascripts/unpoly/history.coffee +29 -28
- data/lib/assets/javascripts/unpoly/legacy.coffee +60 -0
- data/lib/assets/javascripts/unpoly/link.coffee.erb +127 -119
- data/lib/assets/javascripts/unpoly/log.coffee +99 -19
- data/lib/assets/javascripts/unpoly/modal.coffee.erb +95 -118
- data/lib/assets/javascripts/unpoly/motion.coffee.erb +158 -138
- data/lib/assets/javascripts/unpoly/namespace.coffee.erb +0 -5
- data/lib/assets/javascripts/unpoly/popup.coffee.erb +119 -102
- data/lib/assets/javascripts/unpoly/protocol.coffee +11 -15
- data/lib/assets/javascripts/unpoly/proxy.coffee +62 -65
- data/lib/assets/javascripts/unpoly/radio.coffee +3 -5
- data/lib/assets/javascripts/unpoly/rails.coffee +8 -9
- data/lib/assets/javascripts/unpoly/syntax.coffee.erb +173 -125
- data/lib/assets/javascripts/unpoly/toast.coffee +25 -24
- data/lib/assets/javascripts/unpoly/tooltip.coffee +89 -79
- data/lib/assets/javascripts/unpoly/util.coffee.erb +579 -1074
- data/lib/assets/javascripts/unpoly/{layout.coffee.erb → viewport.coffee.erb} +334 -264
- data/lib/assets/stylesheets/unpoly/dom.sass +1 -1
- data/lib/assets/stylesheets/unpoly/layout.sass +2 -0
- data/lib/assets/stylesheets/unpoly/popup.sass +0 -1
- data/lib/assets/stylesheets/unpoly/tooltip.sass +17 -12
- data/lib/unpoly/rails/version.rb +1 -1
- data/package.json +1 -2
- data/spec_app/Gemfile +2 -1
- data/spec_app/Gemfile.lock +38 -27
- data/spec_app/app/assets/javascripts/integration_test.coffee +1 -0
- data/spec_app/app/assets/javascripts/jasmine_specs.coffee +1 -2
- data/spec_app/app/assets/stylesheets/integration_test.sass +14 -1
- data/spec_app/app/controllers/scroll_test_controller.rb +5 -0
- data/spec_app/app/views/css_test/modal.erb +6 -6
- data/spec_app/app/views/css_test/popup.erb +44 -18
- data/spec_app/app/views/css_test/tooltip.erb +23 -4
- data/spec_app/app/views/error_test/trigger.erb +1 -1
- data/spec_app/app/views/form_test/basics/new.erb +1 -3
- data/spec_app/app/views/pages/start.erb +9 -2
- data/spec_app/app/views/reveal_test/long1.erb +1 -1
- data/spec_app/app/views/reveal_test/long2.erb +1 -1
- data/spec_app/app/views/reveal_test/within_document_viewport.erb +24 -0
- data/spec_app/app/views/reveal_test/within_overflowing_div_viewport.erb +28 -0
- data/spec_app/app/views/scroll_test/long1.erb +30 -0
- data/spec_app/config/routes.rb +1 -0
- data/spec_app/spec/javascripts/helpers/agent_detector.coffee +3 -0
- data/spec_app/spec/javascripts/helpers/async_sequence.js.coffee +1 -0
- data/spec_app/spec/javascripts/helpers/browser_switches.js.coffee +17 -5
- data/spec_app/spec/javascripts/helpers/enable_logging.js.coffee +1 -1
- data/spec_app/spec/javascripts/helpers/fixture.js.coffee +25 -0
- data/spec_app/spec/javascripts/helpers/jquery_no_conflict.js +1 -0
- data/spec_app/spec/javascripts/helpers/last_request.js.coffee +1 -0
- data/spec_app/spec/javascripts/helpers/mock_ajax.js.coffee +1 -1
- data/spec_app/spec/javascripts/helpers/parse_form_data.js.coffee +2 -2
- data/spec_app/spec/javascripts/helpers/protect_jasmine_runner.coffee +4 -1
- data/spec_app/spec/javascripts/helpers/remove_body_margin.js.coffee +3 -0
- data/spec_app/spec/javascripts/helpers/reset_history.js.coffee +2 -1
- data/spec_app/spec/javascripts/helpers/reset_knife.js.coffee +2 -2
- data/spec_app/spec/javascripts/helpers/reset_up.js.coffee +18 -11
- data/spec_app/spec/javascripts/helpers/restore_body_scroll.js.coffee +3 -0
- data/spec_app/spec/javascripts/helpers/show_lib_versions.coffee +3 -0
- data/spec_app/spec/javascripts/helpers/spec_util.coffee +47 -0
- data/spec_app/spec/javascripts/helpers/to_be_around.js.coffee +3 -0
- data/spec_app/spec/javascripts/helpers/to_be_array.coffee +5 -0
- data/spec_app/spec/javascripts/helpers/to_be_attached.coffee +6 -2
- data/spec_app/spec/javascripts/helpers/to_be_blank.js.coffee +3 -0
- data/spec_app/spec/javascripts/helpers/to_be_detached.coffee +6 -2
- data/spec_app/spec/javascripts/helpers/to_be_element.js.coffee +8 -0
- data/spec_app/spec/javascripts/helpers/to_be_error.coffee +3 -0
- data/spec_app/spec/javascripts/helpers/to_be_given.js.coffee +3 -0
- data/spec_app/spec/javascripts/helpers/to_be_hidden.js.coffee +8 -0
- data/spec_app/spec/javascripts/helpers/to_be_missing.js.coffee +3 -0
- data/spec_app/spec/javascripts/helpers/to_be_present.js.coffee +3 -0
- data/spec_app/spec/javascripts/helpers/to_be_scrolled_to.coffee +3 -0
- data/spec_app/spec/javascripts/helpers/to_be_visible.js.coffee +9 -0
- data/spec_app/spec/javascripts/helpers/to_contain.js.coffee +3 -0
- data/spec_app/spec/javascripts/helpers/to_end_with.js.coffee +3 -0
- data/spec_app/spec/javascripts/helpers/to_equal_jquery.js.coffee +1 -2
- data/spec_app/spec/javascripts/helpers/to_equal_node_list.coffee +7 -0
- data/spec_app/spec/javascripts/helpers/to_equal_via_is_equal.js.coffee +7 -0
- data/spec_app/spec/javascripts/helpers/to_have_class.js.coffee +10 -0
- data/spec_app/spec/javascripts/helpers/to_have_descendant.js.coffee +10 -0
- data/spec_app/spec/javascripts/helpers/to_have_length.js.coffee +8 -0
- data/spec_app/spec/javascripts/helpers/to_have_opacity.coffee +7 -3
- data/spec_app/spec/javascripts/helpers/to_have_own_property.js.coffee +3 -0
- data/spec_app/spec/javascripts/helpers/to_have_request_method.js.coffee +1 -0
- data/spec_app/spec/javascripts/helpers/to_have_text.js.coffee +9 -0
- data/spec_app/spec/javascripts/helpers/to_have_unhandled_rejections.coffee +0 -21
- data/spec_app/spec/javascripts/helpers/to_match_list.coffee +14 -0
- data/spec_app/spec/javascripts/helpers/to_match_selector.coffee +3 -0
- data/spec_app/spec/javascripts/helpers/to_match_text.js.coffee +4 -1
- data/spec_app/spec/javascripts/helpers/to_match_url.coffee +1 -0
- data/spec_app/spec/javascripts/helpers/trigger.js.coffee +91 -7
- data/spec_app/spec/javascripts/helpers/wait_until_dom_ready.js.coffee +3 -0
- data/spec_app/spec/javascripts/up/browser_spec.js.coffee +23 -90
- data/spec_app/spec/javascripts/up/classes/cache_spec.js.coffee +3 -0
- data/spec_app/spec/javascripts/up/classes/config_spec.coffee +24 -0
- data/spec_app/spec/javascripts/up/classes/divertible_chain_spec.coffee +45 -0
- data/spec_app/spec/javascripts/up/classes/focus_tracker_spec.coffee +5 -2
- data/spec_app/spec/javascripts/up/classes/params_spec.coffee +557 -0
- data/spec_app/spec/javascripts/up/classes/request_spec.coffee +7 -4
- data/spec_app/spec/javascripts/up/classes/scroll_motion_spec.js.coffee +51 -0
- data/spec_app/spec/javascripts/up/classes/store/memory_spec.js.coffee +3 -0
- data/spec_app/spec/javascripts/up/classes/store/session_spec.js.coffee +3 -2
- data/spec_app/spec/javascripts/up/element_spec.coffee +897 -0
- data/spec_app/spec/javascripts/up/event_spec.js.coffee +496 -0
- data/spec_app/spec/javascripts/up/feedback_spec.js.coffee +69 -48
- data/spec_app/spec/javascripts/up/form_spec.js.coffee +252 -194
- data/spec_app/spec/javascripts/up/{dom_spec.js.coffee → fragment_spec.js.coffee} +381 -388
- data/spec_app/spec/javascripts/up/history_spec.js.coffee +21 -19
- data/spec_app/spec/javascripts/up/jquery_spec.js.coffee +4 -0
- data/spec_app/spec/javascripts/up/legacy_spec.js.coffee +27 -0
- data/spec_app/spec/javascripts/up/link_spec.js.coffee +163 -160
- data/spec_app/spec/javascripts/up/log_spec.js.coffee +85 -12
- data/spec_app/spec/javascripts/up/modal_spec.js.coffee +141 -123
- data/spec_app/spec/javascripts/up/motion_spec.js.coffee +117 -113
- data/spec_app/spec/javascripts/up/popup_spec.js.coffee +60 -77
- data/spec_app/spec/javascripts/up/protocol_spec.js.coffee +1 -0
- data/spec_app/spec/javascripts/up/proxy_spec.js.coffee +85 -78
- data/spec_app/spec/javascripts/up/radio_spec.js.coffee +29 -22
- data/spec_app/spec/javascripts/up/rails_spec.js.coffee +14 -13
- data/spec_app/spec/javascripts/up/spec_spec.js.coffee +9 -0
- data/spec_app/spec/javascripts/up/syntax_spec.js.coffee +96 -66
- data/spec_app/spec/javascripts/up/toast_spec.js.coffee +37 -0
- data/spec_app/spec/javascripts/up/tooltip_spec.js.coffee +31 -47
- data/spec_app/spec/javascripts/up/util_spec.js.coffee +725 -562
- data/spec_app/spec/javascripts/up/{layout_spec.js.coffee → viewport_spec.js.coffee} +175 -149
- metadata +57 -19
- data/lib/assets/javascripts/unpoly-bootstrap3/layout-ext.coffee +0 -5
- data/lib/assets/javascripts/unpoly/bus.coffee.erb +0 -518
- data/lib/assets/javascripts/unpoly/classes/extract_step.coffee +0 -4
- data/lib/assets/javascripts/unpoly/classes/motion_tracker.coffee +0 -125
- data/lib/assets/javascripts/unpoly/params.coffee.erb +0 -522
- data/spec_app/spec/javascripts/helpers/append_fixture.js.coffee +0 -8
- data/spec_app/spec/javascripts/up/bus_spec.js.coffee +0 -210
- data/spec_app/spec/javascripts/up/namespace_spec.js.coffee +0 -9
- data/spec_app/spec/javascripts/up/params_spec.coffee +0 -768
- data/spec_app/vendor/asset-libs/jasmine-fixture-1.3.4/jasmine-fixture.js +0 -433
- data/spec_app/vendor/asset-libs/jasmine-jquery-2.1.1/.bower.json +0 -26
- data/spec_app/vendor/asset-libs/jasmine-jquery-2.1.1/jasmine-jquery.js +0 -838
@@ -6,11 +6,12 @@ Unpoly comes with functionality to [submit](/form-up-target) and [validate](/inp
|
|
6
6
|
forms without leaving the current page. This means you can replace page fragments,
|
7
7
|
open dialogs with sub-forms, etc. all without losing form state.
|
8
8
|
|
9
|
-
@
|
9
|
+
@module up.form
|
10
10
|
###
|
11
|
-
up.form =
|
11
|
+
up.form = do ->
|
12
12
|
|
13
13
|
u = up.util
|
14
|
+
e = up.element
|
14
15
|
|
15
16
|
###**
|
16
17
|
Sets default options for form submission and validation.
|
@@ -26,16 +27,15 @@ up.form = (($) ->
|
|
26
27
|
will be updated with the validation messages from the server.
|
27
28
|
|
28
29
|
By default this looks for a `<fieldset>`, `<label>` or `<form>`
|
29
|
-
around the validating input field
|
30
|
-
`up-fieldset` attribute.
|
30
|
+
around the validating input field.
|
31
31
|
@param {string} [config.fields]
|
32
32
|
An array of CSS selectors that represent form fields, such as `input` or `select`.
|
33
33
|
@param {string} [config.submitButtons]
|
34
34
|
An array of CSS selectors that represent submit buttons, such as `input[type=submit]`.
|
35
35
|
@stable
|
36
36
|
###
|
37
|
-
config =
|
38
|
-
validateTargets: ['
|
37
|
+
config = new up.Config
|
38
|
+
validateTargets: ['fieldset:has(&)', 'label:has(&)', 'form:has(&)']
|
39
39
|
fields: ['select', 'input:not([type=submit]):not([type=image])', 'button[type]:not([type=submit])', 'textarea'],
|
40
40
|
submitButtons: ['input[type=submit]', 'input[type=image]', 'button[type=submit]', 'button:not([type])']
|
41
41
|
observeDelay: 0
|
@@ -50,6 +50,41 @@ up.form = (($) ->
|
|
50
50
|
fieldSelector = ->
|
51
51
|
config.fields.join(',')
|
52
52
|
|
53
|
+
###**
|
54
|
+
@function up.form.fields
|
55
|
+
@internal
|
56
|
+
###
|
57
|
+
findFields = (root) ->
|
58
|
+
e.subtree(root, fieldSelector())
|
59
|
+
|
60
|
+
# findFields = (rootOrRoots) ->
|
61
|
+
# rootOrRoots = e.list(rootOrRoots)
|
62
|
+
# u.flatMap rootOrRoots, (root) -> e.subtree(root, fieldSelector())
|
63
|
+
|
64
|
+
###***
|
65
|
+
@function up.form.submissionFields
|
66
|
+
@internal
|
67
|
+
###
|
68
|
+
findSubmissionFields = (root) ->
|
69
|
+
fields = findFields(root)
|
70
|
+
if button = submittingButton(root)
|
71
|
+
fields = u.toArray(fields)
|
72
|
+
fields.push(button)
|
73
|
+
fields
|
74
|
+
|
75
|
+
###**
|
76
|
+
@function up.form.submittingButton
|
77
|
+
@internal
|
78
|
+
###
|
79
|
+
submittingButton = (form) ->
|
80
|
+
selector = submitButtonSelector()
|
81
|
+
focusedElement = document.activeElement
|
82
|
+
if focusedElement && e.matches(focusedElement, selector) && form.contains(focusedElement)
|
83
|
+
return focusedElement
|
84
|
+
else
|
85
|
+
# If no button is focused, we assume the first button in the form.
|
86
|
+
return e.first(form, selector)
|
87
|
+
|
53
88
|
###**
|
54
89
|
@function up.form.submitButtonSelector
|
55
90
|
@internal
|
@@ -149,27 +184,28 @@ up.form = (($) ->
|
|
149
184
|
@stable
|
150
185
|
###
|
151
186
|
submit = (formOrSelector, options) ->
|
152
|
-
$form = $(formOrSelector).closest('form')
|
153
|
-
|
154
187
|
options = u.options(options)
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
options.
|
162
|
-
options.
|
163
|
-
options.
|
164
|
-
options.
|
165
|
-
options.
|
166
|
-
options.
|
167
|
-
options.
|
168
|
-
options.
|
169
|
-
options.
|
170
|
-
options.
|
171
|
-
options.
|
172
|
-
options
|
188
|
+
|
189
|
+
form = e.get(formOrSelector)
|
190
|
+
form = e.closest(form, 'form')
|
191
|
+
|
192
|
+
target = options.target ? form.getAttribute('up-target') ? 'body'
|
193
|
+
url = options.url ? form.getAttribute('action') ? up.browser.url()
|
194
|
+
options.failTarget ?= form.getAttribute('up-fail-target') ? e.toSelector(form)
|
195
|
+
options.reveal ?= e.booleanOrStringAttr(form, 'up-reveal') ? true
|
196
|
+
options.failReveal ?= e.booleanOrStringAttr(form, 'up-fail-reveal') ? true
|
197
|
+
options.fallback ?= form.getAttribute('up-fallback')
|
198
|
+
options.history ?= e.booleanOrStringAttr(form, 'up-history') ? true
|
199
|
+
options.transition ?= e.booleanOrStringAttr(form, 'up-transition')
|
200
|
+
options.failTransition ?= e.booleanOrStringAttr(form, 'up-fail-transition')
|
201
|
+
options.method ?= form.getAttribute('up-method') ? form.getAttribute('data-method') ? form.getAttribute('method') ? 'post'
|
202
|
+
options.cache ?= e.booleanAttr(form, 'up-cache')
|
203
|
+
options.restoreScroll ?= e.booleanAttr(form, 'up-restore-scroll') # the option supports an object value, but not the attr
|
204
|
+
options.origin ?= form
|
205
|
+
options.layer ?= form.getAttribute('up-layer')
|
206
|
+
options.failLayer ?= form.getAttribute('up-fail-layer')
|
207
|
+
options.params = up.Params.fromForm(form)
|
208
|
+
options = u.merge(options, up.motion.animateOptions(options, form))
|
173
209
|
|
174
210
|
if options.validate
|
175
211
|
options.headers ||= {}
|
@@ -177,24 +213,26 @@ up.form = (($) ->
|
|
177
213
|
options.failTransition = false
|
178
214
|
options.headers[up.protocol.config.validateHeader] = options.validate
|
179
215
|
|
180
|
-
up.
|
181
|
-
up.feedback.start(
|
216
|
+
up.event.whenEmitted('up:form:submit', log: 'Submitting form', target: form).then ->
|
217
|
+
up.feedback.start(form)
|
182
218
|
|
183
219
|
# If we can't update the location URL, fall back to a vanilla form submission.
|
184
220
|
unless up.browser.canPushState() || options.history == false
|
185
221
|
# Don't use up.browser.navigate(); It cannot deal with file inputs.
|
186
|
-
|
222
|
+
form.submit()
|
187
223
|
return u.unresolvablePromise()
|
188
224
|
|
189
225
|
promise = up.replace(target, url, options)
|
190
|
-
u.always promise, -> up.feedback.stop(
|
226
|
+
u.always promise, -> up.feedback.stop(form)
|
191
227
|
promise
|
192
228
|
|
193
229
|
###**
|
194
230
|
This event is [emitted](/up.emit) when a form is [submitted](/up.submit) through Unpoly.
|
195
231
|
|
232
|
+
The event is emitted on the`<form>` element.
|
233
|
+
|
196
234
|
@event up:form:submit
|
197
|
-
@param {
|
235
|
+
@param {Element} event.target
|
198
236
|
The `<form>` element that will be submitted.
|
199
237
|
@param event.preventDefault()
|
200
238
|
Event listeners may call this method to prevent the form from being submitted.
|
@@ -206,105 +244,71 @@ up.form = (($) ->
|
|
206
244
|
|
207
245
|
This is useful for observing text fields while the user is typing.
|
208
246
|
|
209
|
-
The unobtrusive variant of this is the [`up-observe`](/up-observe) attribute.
|
247
|
+
The unobtrusive variant of this is the [`[up-observe]`](/up-observe) attribute.
|
210
248
|
|
211
249
|
\#\#\# Example
|
212
250
|
|
213
251
|
The following would print to the console whenever an input field changes:
|
214
252
|
|
215
|
-
up.observe('input.query', function(value
|
216
|
-
console.log('Query is now '
|
217
|
-
})
|
253
|
+
up.observe('input.query', function(value) {
|
254
|
+
console.log('Query is now %o', value)
|
255
|
+
})
|
218
256
|
|
219
257
|
Instead of a single form field, you can also pass multiple fields,
|
220
258
|
a `<form>` or any container that contains form fields.
|
221
|
-
The callback will be run if any of the given fields change
|
222
|
-
|
223
|
-
\#\#\# Preventing concurrency
|
224
|
-
|
225
|
-
Making network requests whenever a form field changes can cause
|
226
|
-
[concurrency issues](https://makandracards.com/makandra/961-concurrency-issues-with-find-as-you-type-boxes).
|
227
|
-
Since `up.observe()` can trigger many requests in a short period of time,
|
228
|
-
the responses might not arrive in the same order.
|
229
|
-
|
230
|
-
To mitigate this, `up.observe()` will try to never run a callback
|
231
|
-
before the previous callback has completed.
|
232
|
-
For this your callback code must return a promise that resolves
|
233
|
-
when your request completes.
|
234
|
-
|
235
|
-
The following would submit a form whenever an input field changes,
|
236
|
-
but never make more than one request at a time:
|
237
|
-
|
238
|
-
up.observe('input.query', function(value, $input) {
|
239
|
-
var submitDone = up.submit($input);
|
240
|
-
return submitDone;
|
241
|
-
});
|
242
|
-
|
243
|
-
Note that many Unpoly functions like [`up.submit()`](/up.submit) or
|
244
|
-
[`up.replace()`](/up.replace) return promises.
|
259
|
+
The callback will be run if any of the given fields change:
|
245
260
|
|
246
|
-
|
261
|
+
up.observe('form', function(value, name) {
|
262
|
+
console.log('The value of %o is now %o', name, value)
|
263
|
+
})
|
247
264
|
|
248
|
-
|
249
|
-
|
250
|
-
a few miliseconds before executing the callback:
|
265
|
+
You may also pass the `{ batch: true }` option to receive all
|
266
|
+
changes since the last callback in a single object:
|
251
267
|
|
252
|
-
up.observe('
|
253
|
-
|
254
|
-
})
|
268
|
+
up.observe('form', { batch: true }, function(diff) {
|
269
|
+
console.log('Observed one or more changes: %o', diff)
|
270
|
+
})
|
255
271
|
|
256
272
|
@function up.observe
|
257
|
-
@param {Element|jQuery
|
258
|
-
The form fields that
|
273
|
+
@param {string|Element|Array<Element>|jQuery} elements
|
274
|
+
The form fields that will be observed.
|
259
275
|
|
260
276
|
You can pass one or more fields, a `<form>` or any container that contains form fields.
|
261
277
|
The callback will be run if any of the given fields change.
|
278
|
+
@param {boolean} [options.batch=false]
|
279
|
+
If set to `true`, the `onChange` callback will receive multiple
|
280
|
+
detected changes in a single diff object as its argument.
|
262
281
|
@param {number} [options.delay=up.form.config.observeDelay]
|
263
282
|
The number of miliseconds to wait before executing the callback
|
264
283
|
after the input value changes. Use this to limit how often the callback
|
265
284
|
will be invoked for a fast typist.
|
266
|
-
@param {Function(value,
|
285
|
+
@param {Function(value, name): string} onChange
|
267
286
|
The callback to run when the field's value changes.
|
268
|
-
|
287
|
+
|
288
|
+
If given as a function, it receives two arguments (`value`, `name`).
|
289
|
+
`value` is a string with the new attribute value and `string` is the name
|
290
|
+
of the form field that changed.
|
291
|
+
|
269
292
|
If given as a string, it will be evaled as JavaScript code in a context where
|
270
|
-
(`value`,
|
271
|
-
@return {Function}
|
293
|
+
(`value`, `name`) are set.
|
294
|
+
@return {Function()}
|
272
295
|
A destructor function that removes the observe watch when called.
|
273
296
|
@stable
|
274
297
|
###
|
275
|
-
observe = (
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
callbackArg = extraArgs[1]
|
283
|
-
|
284
|
-
$element = $(selectorOrElement)
|
285
|
-
|
286
|
-
callback = null
|
287
|
-
rawCallback = u.option(callbackArg, u.presentAttr($element, 'up-observe'))
|
288
|
-
if u.isString(rawCallback)
|
289
|
-
callback = new Function('value', '$field', rawCallback)
|
290
|
-
else
|
291
|
-
callback = rawCallback or up.fail('up.observe: No change callback given')
|
292
|
-
|
293
|
-
delay = u.option(u.presentAttr($element, 'up-delay'), options.delay, config.observeDelay)
|
294
|
-
delay = parseInt(delay)
|
295
|
-
|
296
|
-
$fields = u.selectInSubtree($element, fieldSelector())
|
297
|
-
|
298
|
-
destructors = u.map $fields, (field) ->
|
299
|
-
observeField($(field), delay, callback)
|
300
|
-
|
301
|
-
u.sequence(destructors...)
|
302
|
-
|
303
|
-
observeField = ($field, delay, callback) ->
|
304
|
-
observer = new up.FieldObserver($field, { delay, callback })
|
298
|
+
observe = (elements, args...) ->
|
299
|
+
elements = e.list(elements)
|
300
|
+
fields = u.flatMap(elements, findFields)
|
301
|
+
callback = u.extractCallback(args) ? observeCallbackFromElement(elements[0]) ? up.fail('up.observe: No change callback given')
|
302
|
+
options = u.extractOptions(args)
|
303
|
+
options.delay = options.delay ? e.numberAttr(elements[0], 'up-delay') ? config.observeDelay
|
304
|
+
observer = new up.FieldObserver(fields, options, callback)
|
305
305
|
observer.start()
|
306
306
|
return observer.stop
|
307
307
|
|
308
|
+
observeCallbackFromElement = (element) ->
|
309
|
+
if rawCallback = element.getAttribute('up-observe')
|
310
|
+
new Function('value', 'name', rawCallback)
|
311
|
+
|
308
312
|
###**
|
309
313
|
[Observes](/up.observe) a field or form and submits the form when a value changes.
|
310
314
|
|
@@ -318,28 +322,27 @@ up.form = (($) ->
|
|
318
322
|
The field or form to observe.
|
319
323
|
@param {Object} [options]
|
320
324
|
See options for [`up.observe()`](/up.observe)
|
321
|
-
@return {Function}
|
325
|
+
@return {Function()}
|
322
326
|
A destructor function that removes the observe watch when called.
|
323
327
|
@stable
|
324
328
|
###
|
325
329
|
autosubmit = (selectorOrElement, options) ->
|
326
|
-
observe(selectorOrElement, options, (
|
327
|
-
|
328
|
-
|
329
|
-
)
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
resolvedDefault
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
target
|
330
|
+
observe(selectorOrElement, options, -> submit(selectorOrElement))
|
331
|
+
|
332
|
+
findValidateTarget = (field, options) ->
|
333
|
+
option = options.target ? field.getAttribute('up-validate')
|
334
|
+
option ||= u.findResult config.validateTargets, (defaultTarget) ->
|
335
|
+
resolvedDefault = e.resolveSelector(defaultTarget, options.origin)
|
336
|
+
if e.first(resolvedDefault)
|
337
|
+
# We want to return the selector, *not* the element. If we returned the element
|
338
|
+
# and derive a selector from that, any :has() expression would be lost.
|
339
|
+
return resolvedDefault
|
340
|
+
|
341
|
+
unless option
|
342
|
+
up.fail('Could not find validation target for %o (tried defaults %o)', field, config.validateTargets)
|
343
|
+
|
344
|
+
# resolveSelector() also creates a selector string if given an element
|
345
|
+
return e.resolveSelector(option, options.origin)
|
343
346
|
|
344
347
|
###**
|
345
348
|
Performs a server-side validation of a form field.
|
@@ -366,38 +369,42 @@ up.form = (($) ->
|
|
366
369
|
@stable
|
367
370
|
###
|
368
371
|
validate = (fieldOrSelector, options) ->
|
369
|
-
|
372
|
+
field = e.get(fieldOrSelector)
|
370
373
|
options = u.options(options)
|
371
|
-
options.origin =
|
372
|
-
options.target =
|
374
|
+
options.origin = field
|
375
|
+
options.target = findValidateTarget(field, options)
|
373
376
|
options.failTarget = options.target
|
374
|
-
options.reveal
|
377
|
+
options.reveal ?= e.booleanOrStringAttr(field, 'up-reveal') ? false
|
375
378
|
options.history = false
|
376
|
-
options.headers = u.option(options.headers, {})
|
377
379
|
# Make sure the X-Up-Validate header is present, so the server-side
|
378
380
|
# knows that it should not persist the form submission
|
379
|
-
options.validate =
|
380
|
-
options = u.merge(options, up.motion.animateOptions(options,
|
381
|
-
|
382
|
-
promise = up.submit($form, options)
|
381
|
+
options.validate = field.getAttribute('name') || ':none'
|
382
|
+
options = u.merge(options, up.motion.animateOptions(options, field))
|
383
|
+
promise = up.submit(field, options)
|
383
384
|
promise
|
384
385
|
|
385
|
-
switcherValues = (
|
386
|
-
|
387
|
-
|
388
|
-
|
386
|
+
switcherValues = (field) ->
|
387
|
+
value = undefined
|
388
|
+
meta = undefined
|
389
|
+
|
390
|
+
if e.matches(field, 'input[type=checkbox]')
|
391
|
+
if field.checked
|
392
|
+
value = field.value
|
389
393
|
meta = ':checked'
|
390
394
|
else
|
391
395
|
meta = ':unchecked'
|
392
|
-
else if
|
393
|
-
|
394
|
-
|
396
|
+
else if e.matches(field, 'input[type=radio]')
|
397
|
+
form = closestContainer(field)
|
398
|
+
groupName = field.getAttribute('name')
|
399
|
+
checkedButton = form.querySelector("input[type=radio]#{e.attributeSelector('name', groupName)}:checked")
|
400
|
+
if checkedButton
|
395
401
|
meta = ':checked'
|
396
|
-
value =
|
402
|
+
value = checkedButton.value
|
397
403
|
else
|
398
404
|
meta = ':unchecked'
|
399
405
|
else
|
400
|
-
value =
|
406
|
+
value = field.value
|
407
|
+
|
401
408
|
values = []
|
402
409
|
if u.isPresent(value)
|
403
410
|
values.push(value)
|
@@ -418,32 +425,32 @@ up.form = (($) ->
|
|
418
425
|
still marked `@internal`.
|
419
426
|
|
420
427
|
@function up.form.switchTargets
|
421
|
-
@param {
|
428
|
+
@param {Element} switcher
|
422
429
|
@param {string} [options.target]
|
423
430
|
The target selectors to switch.
|
424
|
-
Defaults to an `up-switch` attribute on the given field.
|
431
|
+
Defaults to an `[up-switch]` attribute on the given field.
|
425
432
|
@internal
|
426
433
|
###
|
427
|
-
switchTargets = (
|
428
|
-
|
429
|
-
|
430
|
-
targetSelector
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
switchTarget(
|
434
|
+
switchTargets = (switcher, options = {}) ->
|
435
|
+
targetSelector = options.target ? switcher.getAttribute('up-switch')
|
436
|
+
form = closestContainer(switcher)
|
437
|
+
u.isPresent(targetSelector) or up.fail("No switch target given for %o", switcher)
|
438
|
+
fieldValues = switcherValues(switcher)
|
439
|
+
|
440
|
+
u.each e.all(form, targetSelector), (target) ->
|
441
|
+
switchTarget(target, fieldValues)
|
435
442
|
|
436
443
|
###**
|
437
444
|
@internal
|
438
445
|
###
|
439
446
|
switchTarget = (target, fieldValues) ->
|
440
|
-
|
441
|
-
|
442
|
-
if hideValues =
|
447
|
+
fieldValues ||= switcherValues(findSwitcherForTarget(target))
|
448
|
+
|
449
|
+
if hideValues = target.getAttribute('up-hide-for')
|
443
450
|
hideValues = u.splitValues(hideValues)
|
444
451
|
show = u.intersect(fieldValues, hideValues).length == 0
|
445
452
|
else
|
446
|
-
if showValues =
|
453
|
+
if showValues = target.getAttribute('up-show-for')
|
447
454
|
showValues = u.splitValues(showValues)
|
448
455
|
else
|
449
456
|
# If the target has neither up-show-for or up-hide-for attributes,
|
@@ -451,21 +458,23 @@ up.form = (($) ->
|
|
451
458
|
# is checked or entered.
|
452
459
|
showValues = [':present', ':checked']
|
453
460
|
show = u.intersect(fieldValues, showValues).length > 0
|
454
|
-
|
455
|
-
|
461
|
+
|
462
|
+
e.toggle(target, show)
|
463
|
+
target.classList.add('up-switched')
|
456
464
|
|
457
465
|
###**
|
458
466
|
@internal
|
459
467
|
###
|
460
|
-
findSwitcherForTarget = (
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
468
|
+
findSwitcherForTarget = (target) ->
|
469
|
+
form = closestContainer(target)
|
470
|
+
switchers = e.all(form, '[up-switch]')
|
471
|
+
switcher = u.find switchers, (switcher) ->
|
472
|
+
targetSelector = switcher.getAttribute('up-switch')
|
473
|
+
e.matches(target, targetSelector)
|
474
|
+
return switcher or u.fail('Could not find [up-switch] field for %o', target)
|
475
|
+
|
476
|
+
closestContainer = (element) ->
|
477
|
+
e.closest(element, 'form, body')
|
469
478
|
|
470
479
|
###**
|
471
480
|
Forms with an `up-target` attribute are [submitted via AJAX](/up.submit)
|
@@ -600,9 +609,9 @@ up.form = (($) ->
|
|
600
609
|
By default only responses to `GET` requests are cached for a few minutes.
|
601
610
|
@stable
|
602
611
|
###
|
603
|
-
up.on 'submit', 'form[up-target]', (event,
|
604
|
-
up.
|
605
|
-
u.muteRejection submit(
|
612
|
+
up.on 'submit', 'form[up-target]', (event, form) ->
|
613
|
+
up.event.consumeAction(event)
|
614
|
+
u.muteRejection submit(form)
|
606
615
|
|
607
616
|
###**
|
608
617
|
When a form field with this attribute is changed, the form is validated on the server
|
@@ -749,8 +758,8 @@ up.form = (($) ->
|
|
749
758
|
This defaults to a fieldset or form group around the validating field.
|
750
759
|
@stable
|
751
760
|
###
|
752
|
-
up.on 'change', '[up-validate]', (event,
|
753
|
-
u.muteRejection validate(
|
761
|
+
up.on 'change', '[up-validate]', (event, field) ->
|
762
|
+
u.muteRejection validate(field)
|
754
763
|
|
755
764
|
###**
|
756
765
|
Show or hide elements when a `<select>` or `<input>` has a given value.
|
@@ -848,14 +857,14 @@ up.form = (($) ->
|
|
848
857
|
A space-separated list of input values for which this element should be hidden.
|
849
858
|
@stable
|
850
859
|
###
|
851
|
-
up.compiler '[up-switch]', (
|
852
|
-
switchTargets(
|
860
|
+
up.compiler '[up-switch]', (switcher) ->
|
861
|
+
switchTargets(switcher)
|
853
862
|
|
854
|
-
up.on 'change', '[up-switch]', (event,
|
855
|
-
switchTargets(
|
863
|
+
up.on 'change', '[up-switch]', (event, switcher) ->
|
864
|
+
switchTargets(switcher)
|
856
865
|
|
857
|
-
up.compiler '[up-show-for]:not(.up-switched), [up-hide-for]:not(.up-switched)', (
|
858
|
-
switchTarget(
|
866
|
+
up.compiler '[up-show-for]:not(.up-switched), [up-hide-for]:not(.up-switched)', (element) ->
|
867
|
+
switchTarget(element)
|
859
868
|
|
860
869
|
###**
|
861
870
|
Observes this field and runs a callback when a value changes.
|
@@ -927,7 +936,7 @@ up.form = (($) ->
|
|
927
936
|
The number of miliseconds to wait after a change before the code is run.
|
928
937
|
@stable
|
929
938
|
###
|
930
|
-
up.compiler '[up-observe]', (
|
939
|
+
up.compiler '[up-observe]', (formOrField) -> observe(formOrField)
|
931
940
|
|
932
941
|
###**
|
933
942
|
[Observes](/up.observe) this form field and submits the form when its value changes.
|
@@ -974,9 +983,9 @@ up.form = (($) ->
|
|
974
983
|
The number of miliseconds to wait after a change before the form is submitted.
|
975
984
|
@stable
|
976
985
|
###
|
977
|
-
up.compiler '[up-autosubmit]', (
|
986
|
+
up.compiler '[up-autosubmit]', (formOrField) -> autosubmit(formOrField)
|
978
987
|
|
979
|
-
up.compiler '[autofocus]', { batch: true }, (
|
988
|
+
up.compiler '[autofocus]', { batch: true }, (inputs) -> u.last(inputs).focus()
|
980
989
|
|
981
990
|
up.on 'up:framework:reset', reset
|
982
991
|
|
@@ -985,12 +994,10 @@ up.form = (($) ->
|
|
985
994
|
submit: submit
|
986
995
|
observe: observe
|
987
996
|
validate: validate
|
988
|
-
switchTargets: switchTargets
|
989
997
|
autosubmit: autosubmit
|
990
998
|
fieldSelector: fieldSelector
|
991
|
-
|
992
|
-
|
993
|
-
)(jQuery)
|
999
|
+
fields: findFields
|
1000
|
+
submissionFields: findSubmissionFields
|
994
1001
|
|
995
1002
|
up.submit = up.form.submit
|
996
1003
|
up.observe = up.form.observe
|