unpoly-rails 0.56.7 → 0.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of unpoly-rails might be problematic. Click here for more details.

Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +74 -1
  3. data/dist/unpoly.js +1569 -793
  4. data/dist/unpoly.min.js +4 -4
  5. data/lib/assets/javascripts/unpoly.coffee +2 -0
  6. data/lib/assets/javascripts/unpoly/browser.coffee.erb +25 -41
  7. data/lib/assets/javascripts/unpoly/bus.coffee.erb +20 -6
  8. data/lib/assets/javascripts/unpoly/classes/cache.coffee +23 -13
  9. data/lib/assets/javascripts/unpoly/classes/compile_pass.coffee +87 -0
  10. data/lib/assets/javascripts/unpoly/classes/focus_tracker.coffee +29 -0
  11. data/lib/assets/javascripts/unpoly/classes/follow_variant.coffee +7 -4
  12. data/lib/assets/javascripts/unpoly/classes/record.coffee +1 -1
  13. data/lib/assets/javascripts/unpoly/classes/request.coffee +38 -45
  14. data/lib/assets/javascripts/unpoly/classes/response.coffee +16 -1
  15. data/lib/assets/javascripts/unpoly/classes/store/memory.coffee +26 -0
  16. data/lib/assets/javascripts/unpoly/classes/store/session.coffee +59 -0
  17. data/lib/assets/javascripts/unpoly/cookie.coffee +56 -0
  18. data/lib/assets/javascripts/unpoly/dom.coffee.erb +67 -39
  19. data/lib/assets/javascripts/unpoly/feedback.coffee +2 -2
  20. data/lib/assets/javascripts/unpoly/form.coffee.erb +23 -12
  21. data/lib/assets/javascripts/unpoly/history.coffee +2 -2
  22. data/lib/assets/javascripts/unpoly/layout.coffee.erb +118 -99
  23. data/lib/assets/javascripts/unpoly/link.coffee.erb +12 -5
  24. data/lib/assets/javascripts/unpoly/log.coffee +6 -5
  25. data/lib/assets/javascripts/unpoly/modal.coffee.erb +9 -2
  26. data/lib/assets/javascripts/unpoly/motion.coffee.erb +2 -6
  27. data/lib/assets/javascripts/unpoly/namespace.coffee.erb +2 -2
  28. data/lib/assets/javascripts/unpoly/params.coffee.erb +522 -0
  29. data/lib/assets/javascripts/unpoly/popup.coffee.erb +3 -3
  30. data/lib/assets/javascripts/unpoly/proxy.coffee +42 -34
  31. data/lib/assets/javascripts/unpoly/{syntax.coffee → syntax.coffee.erb} +59 -117
  32. data/lib/assets/javascripts/unpoly/{util.coffee → util.coffee.erb} +206 -171
  33. data/lib/unpoly/rails/version.rb +1 -1
  34. data/package.json +1 -1
  35. data/spec_app/Gemfile.lock +1 -1
  36. data/spec_app/app/assets/javascripts/integration_test.coffee +0 -4
  37. data/spec_app/app/assets/stylesheets/integration_test.sass +7 -1
  38. data/spec_app/app/controllers/pages_controller.rb +4 -0
  39. data/spec_app/app/views/form_test/basics/new.erb +34 -5
  40. data/spec_app/app/views/form_test/submission_result.erb +2 -2
  41. data/spec_app/app/views/form_test/uploads/new.erb +15 -2
  42. data/spec_app/app/views/hash_test/unpoly.erb +30 -0
  43. data/spec_app/app/views/pages/start.erb +2 -1
  44. data/spec_app/spec/javascripts/helpers/parse_form_data.js.coffee +17 -2
  45. data/spec_app/spec/javascripts/helpers/reset_up.js.coffee +5 -0
  46. data/spec_app/spec/javascripts/helpers/to_be_error.coffee +1 -1
  47. data/spec_app/spec/javascripts/helpers/to_match_selector.coffee +5 -0
  48. data/spec_app/spec/javascripts/up/browser_spec.js.coffee +8 -8
  49. data/spec_app/spec/javascripts/up/bus_spec.js.coffee +58 -20
  50. data/spec_app/spec/javascripts/up/classes/cache_spec.js.coffee +78 -0
  51. data/spec_app/spec/javascripts/up/classes/focus_tracker_spec.coffee +31 -0
  52. data/spec_app/spec/javascripts/up/classes/request_spec.coffee +50 -0
  53. data/spec_app/spec/javascripts/up/classes/store/memory_spec.js.coffee +67 -0
  54. data/spec_app/spec/javascripts/up/classes/store/session_spec.js.coffee +113 -0
  55. data/spec_app/spec/javascripts/up/dom_spec.js.coffee +133 -45
  56. data/spec_app/spec/javascripts/up/form_spec.js.coffee +13 -13
  57. data/spec_app/spec/javascripts/up/layout_spec.js.coffee +110 -26
  58. data/spec_app/spec/javascripts/up/link_spec.js.coffee +1 -1
  59. data/spec_app/spec/javascripts/up/modal_spec.js.coffee +1 -0
  60. data/spec_app/spec/javascripts/up/motion_spec.js.coffee +52 -51
  61. data/spec_app/spec/javascripts/up/namespace_spec.js.coffee +2 -2
  62. data/spec_app/spec/javascripts/up/params_spec.coffee +768 -0
  63. data/spec_app/spec/javascripts/up/proxy_spec.js.coffee +75 -36
  64. data/spec_app/spec/javascripts/up/syntax_spec.js.coffee +48 -15
  65. data/spec_app/spec/javascripts/up/util_spec.js.coffee +148 -131
  66. metadata +17 -5
  67. data/spec_app/spec/javascripts/up/classes/.keep +0 -0
@@ -122,8 +122,13 @@ up.link = (($) ->
122
122
  An element or selector which is either an `<a>` tag or any element with an `up-href` attribute.
123
123
  @param {string} [options.target]
124
124
  The selector to replace.
125
- Defaults to the `[up-target]`, `[up-modal]` or `[up-popup]` attribute on `link`.
125
+
126
+ Defaults to the link's `[up-target]`, `[up-modal]` or `[up-popup]` attribute.
126
127
  If no target is given, the `<body>` element will be replaced.
128
+ @param {String} [options.url]
129
+ The URL to navigate to.
130
+
131
+ Defaults to the link's `[up-href]` or `[href]` attribute.
127
132
  @param {boolean|string} [options.reveal=true]
128
133
  Whether to [reveal](/up.reveal) the target fragment after it was replaced.
129
134
 
@@ -145,8 +150,10 @@ up.link = (($) ->
145
150
  ###**
146
151
  This event is [emitted](/up.emit) when a link is [followed](/up.follow) through Unpoly.
147
152
 
153
+ The event is emitted on the `<a>` element that is being followed.
154
+
148
155
  @event up:link:follow
149
- @param {jQuery} event.$element
156
+ @param {jQuery} event.$link
150
157
  The link element that will be followed.
151
158
  @param event.preventDefault()
152
159
  Event listeners may call this method to prevent the link from being followed.
@@ -162,7 +169,7 @@ up.link = (($) ->
162
169
 
163
170
  options = u.options(options)
164
171
 
165
- url = u.option($link.attr('up-href'), $link.attr('href'))
172
+ url = u.option(options.url, $link.attr('up-href'), $link.attr('href'))
166
173
  target = u.option(options.target, $link.attr('up-target'))
167
174
  options.failTarget = u.option(options.failTarget, $link.attr('up-fail-target'))
168
175
  options.fallback = u.option(options.fallback, $link.attr('up-fallback'))
@@ -396,9 +403,9 @@ up.link = (($) ->
396
403
  or make an educated guess (default).
397
404
  @param {string} [up-layer='auto']
398
405
  The name of the layer that ought to be updated. Valid values are
399
- `auto`, `page`, `modal` and `popup`.
406
+ `'auto'`, `'page'`, `'modal'` and `'popup'`.
400
407
 
401
- If set to `auto` (default), Unpoly will try to find a match in the link's layer.
408
+ If set to `'auto'` (default), Unpoly will try to find a match in the link's layer.
402
409
  If no match was found in that layer,
403
410
  Unpoly will search in other layers, starting from the topmost layer.
404
411
  @param {string} [up-fail-layer='auto']
@@ -18,7 +18,7 @@ up.log = (($) ->
18
18
  u = up.util
19
19
  b = up.browser
20
20
 
21
- SESSION_KEY_ENABLED = 'up.log.enabled'
21
+ sessionStore = new up.store.Session('up.log')
22
22
 
23
23
  ###**
24
24
  Configures the logging output on the developer console.
@@ -41,7 +41,7 @@ up.log = (($) ->
41
41
  ###
42
42
  config = u.config
43
43
  prefix: '[UP] '
44
- enabled: (b.sessionStorage().getItem(SESSION_KEY_ENABLED) == 'true')
44
+ enabled: sessionStore.get('enabled')
45
45
  collapse: false
46
46
 
47
47
  reset = ->
@@ -75,7 +75,7 @@ up.log = (($) ->
75
75
  b.puts('log', prefix(message), args...)
76
76
 
77
77
  ###**
78
- @function up.log.warn
78
+ @function up.warn
79
79
  @internal
80
80
  ###
81
81
  warn = (message, args...) ->
@@ -126,8 +126,7 @@ up.log = (($) ->
126
126
  up.on 'up:framework:reset', reset
127
127
 
128
128
  setEnabled = (value) ->
129
- # Session storage can only store string values
130
- b.sessionStorage().setItem(SESSION_KEY_ENABLED, value.toString())
129
+ sessionStore.set('enabled', value)
131
130
  config.enabled = value
132
131
 
133
132
  ###**
@@ -165,3 +164,5 @@ up.log = (($) ->
165
164
  )(jQuery)
166
165
 
167
166
  up.puts = up.log.puts
167
+ up.warn = up.log.warn
168
+
@@ -439,7 +439,8 @@ up.modal = (($) ->
439
439
  $link = u.option(u.pluckKey(options, '$link'), u.nullJQuery())
440
440
  url = u.option(u.pluckKey(options, 'url'), $link.attr('up-href'), $link.attr('href'))
441
441
  html = u.option(u.pluckKey(options, 'html'))
442
- target = u.option(u.pluckKey(options, 'target'), $link.attr('up-modal'), 'body')
442
+ target = u.option(u.pluckKey(options, 'target'), $link.attr('up-modal'))
443
+ validateTarget(target)
443
444
  options.flavor = u.option(options.flavor, $link.attr('up-flavor'), config.flavor)
444
445
  options.position = u.option(options.position, $link.attr('up-position'), flavorDefault('position', options.flavor))
445
446
  options.position = u.evalOption(options.position, $link: $link)
@@ -497,6 +498,12 @@ up.modal = (($) ->
497
498
  up.emit('up:modal:opened', message: 'Modal opened')
498
499
  promise
499
500
 
501
+ validateTarget = (target) ->
502
+ if u.isBlank(target)
503
+ up.fail('Cannot open a modal without a target selector')
504
+ else if target == 'body'
505
+ up.fail('Cannot open the <body> in a modal')
506
+
500
507
  ###**
501
508
  This event is [emitted](/up.emit) when a modal dialog is starting to open.
502
509
 
@@ -629,7 +636,7 @@ up.modal = (($) ->
629
636
  $element.closest('.up-modal').length > 0
630
637
 
631
638
  flavor = (name, overrideConfig = {}) ->
632
- up.log.warn 'up.modal.flavor() is deprecated. Use the up.modal.flavors property instead.'
639
+ up.warn 'up.modal.flavor() is deprecated. Use the up.modal.flavors property instead.'
633
640
  u.assign(flavorOverrides(name), overrideConfig)
634
641
 
635
642
  ###**
@@ -176,11 +176,7 @@ up.motion = (($) ->
176
176
 
177
177
  willAnimate = ($elements, animationOrTransition, options) ->
178
178
  options = animateOptions(options)
179
- isEnabled() && !isNone(animationOrTransition) && options.duration > 0 && !isSingletonElement($elements)
180
-
181
- isSingletonElement = ($element) ->
182
- # jQuery's is() returns true if at least one element in the collection matches the selector
183
- $element.is('body')
179
+ isEnabled() && !isNone(animationOrTransition) && options.duration > 0 && !u.isSingletonElement($elements)
184
180
 
185
181
  skipAnimate = ($element, animation) ->
186
182
  if u.isOptions(animation)
@@ -355,7 +351,7 @@ up.motion = (($) ->
355
351
  # Don't animate the scrolling. The { duration } option was meant for the transition.
356
352
  scrollOptions = u.merge(options, duration: 0)
357
353
  # Scroll $new into position before we start the enter animation.
358
- up.layout.revealOrRestoreScroll($new, scrollOptions)
354
+ up.layout.scrollAfterInsertFragment($new, scrollOptions)
359
355
 
360
356
  if willMorph
361
357
  if motionTracker.isActive($old) && options.trackMotion is false
@@ -4,7 +4,7 @@
4
4
  window.up =
5
5
  version: <%= Unpoly::Rails::VERSION.to_json %>
6
6
 
7
- renamedModule: (oldName, newName) ->
7
+ deprecateRenamedModule: (oldName, newName) ->
8
8
  Object.defineProperty? up, oldName, get: ->
9
- up.log.warn("up.#{oldName} has been renamed to up.#{newName}")
9
+ up.warn("Deprecated: up.#{oldName} has been renamed to up.#{newName}")
10
10
  up[newName]
@@ -0,0 +1,522 @@
1
+ ###**
2
+ Request parameters
3
+ ==================
4
+
5
+ Methods like [`up.replace()`](/up.replace) accept request parameters (or form data values) as a `{ params }` option.
6
+
7
+ This module offers a consistent API to read and manipulate request parameters independent of their type.
8
+
9
+
10
+ \#\#\# Supported parameter types
11
+
12
+ The following types of parameters are supported:
13
+
14
+ 1. an object like `{ email: 'foo@bar.com' }`
15
+ 2. a [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData) object
16
+ 3. a query string like `email=foo%40bar.com`
17
+ 4. an array of `{ name, value }` objects like `[{ name: 'email', value: 'foo@bar.com' }]`
18
+
19
+ @class up.params
20
+ ###
21
+ up.params = (($) ->
22
+ u = up.util
23
+
24
+ NATURE_MISSING = 0
25
+ NATURE_ARRAY = 1
26
+ NATURE_QUERY = 2
27
+ NATURE_FORM_DATA = 3
28
+ NATURE_OBJECT = 4
29
+
30
+ natureOf = (params) ->
31
+ if u.isMissing(params)
32
+ NATURE_MISSING
33
+ else if u.isArray(params)
34
+ NATURE_ARRAY
35
+ else if u.isString(params)
36
+ NATURE_QUERY
37
+ else if u.isFormData(params)
38
+ NATURE_FORM_DATA
39
+ else if u.isObject(params)
40
+ NATURE_OBJECT
41
+ else
42
+ up.fail("Unsupport params type: %o", params)
43
+
44
+ ###**
45
+ Returns an array representation of the given `params`.
46
+
47
+ The given params value may be of any [supported type](/up.params).
48
+
49
+ Each element in the returned array is an object with `{ name }` and `{ value }` properties.
50
+
51
+ \#\#\# Example
52
+
53
+ var array = up.params.toArray('foo=bar&baz=bam')
54
+
55
+ // array is now: [
56
+ // { name: 'foo', value: 'bar' },
57
+ // { name: 'baz', value: 'bam' },
58
+ // ]
59
+
60
+ @function up.params.toArray
61
+ @param {Object|FormData|string|Array|undefined} params
62
+ the params to convert
63
+ @return {Array}
64
+ an array representation of the given params
65
+ @experimental
66
+ ###
67
+ toArray = (params) ->
68
+ switch natureOf(params)
69
+ when NATURE_MISSING
70
+ []
71
+ when NATURE_ARRAY
72
+ params
73
+ when NATURE_QUERY
74
+ buildArrayFromQuery(params)
75
+ when NATURE_FORM_DATA
76
+ buildArrayFromFormData(params)
77
+ when NATURE_OBJECT
78
+ buildArrayFromObject(params)
79
+
80
+ ###**
81
+ Returns an object representation of the given `params`.
82
+
83
+ The given params value may be of any [supported type](/up.params).
84
+
85
+ The returned value is a simple JavaScript object whose properties correspond
86
+ to the key/values in the given `params`.
87
+
88
+ \#\#\# Example
89
+
90
+ var object = up.params.toObject('foo=bar&baz=bam')
91
+
92
+ // object is now: {
93
+ // foo: 'bar',
94
+ // baz: 'bam'
95
+ // ]
96
+
97
+ @function up.params.toObject
98
+ @param {Object|FormData|string|Array|undefined} params
99
+ the params to convert
100
+ @return {Array}
101
+ an object representation of the given params
102
+ @experimental
103
+ ###
104
+ toObject = (params) ->
105
+ switch natureOf(params)
106
+ when NATURE_MISSING
107
+ {}
108
+ when NATURE_ARRAY, NATURE_QUERY, NATURE_FORM_DATA
109
+ buildObjectFromArray(toArray(params))
110
+ when NATURE_OBJECT
111
+ params
112
+
113
+ ###**
114
+ Returns [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) representation of the given `params`.
115
+
116
+ The given params value may be of any [supported type](/up.params).
117
+
118
+ \#\#\# Example
119
+
120
+ var formData = up.params.toFormData('foo=bar&baz=bam')
121
+
122
+ formData.get('foo') // 'bar'
123
+ formData.get('baz') // 'bam'
124
+
125
+ @function up.params.toFormData
126
+ @param {Object|FormData|string|Array|undefined} params
127
+ the params to convert
128
+ @return {FormData}
129
+ a [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) representation of the given params
130
+ @experimental
131
+ ###
132
+ toFormData = (params) ->
133
+ switch natureOf(params)
134
+ when NATURE_MISSING
135
+ # Return an empty FormData object
136
+ new FormData()
137
+ when NATURE_ARRAY, NATURE_QUERY, NATURE_OBJECT
138
+ buildFormDataFromArray(toArray(params))
139
+ when NATURE_FORM_DATA
140
+ params
141
+
142
+ ###**
143
+ Returns an query string for the given `params`.
144
+
145
+ The given params value may be of any [supported type](/up.params).
146
+
147
+ The keys and values in the returned query string will be [percent-encoded](https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding).
148
+ Non-primitive values (like [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File) will be omitted from
149
+ the retuned query string.
150
+
151
+ \#\#\# Example
152
+
153
+ var query = up.params.toQuery({ foo: 'bar', baz: 'bam' })
154
+
155
+ // query is now: 'foo=bar&baz=bam'
156
+
157
+ @function up.params.toQuery
158
+ @param {Object|FormData|string|Array|undefined} params
159
+ the params to convert
160
+ @return {string}
161
+ a query string built from the given params
162
+ @experimental
163
+ ###
164
+ toQuery = (params) ->
165
+ switch natureOf(params)
166
+ when NATURE_MISSING
167
+ ''
168
+ when NATURE_QUERY
169
+ params
170
+ when NATURE_ARRAY, NATURE_FORM_DATA, NATURE_OBJECT
171
+ buildQueryFromArray(toArray(params))
172
+
173
+ arrayEntryToQuery = (entry) ->
174
+ value = entry.value
175
+ return undefined unless isPrimitiveValue(value)
176
+ query = encodeURIComponent(entry.name)
177
+ # There is a subtle difference when encoding blank values:
178
+ # 1. An undefined or null value is encoded to `key` with no equals sign
179
+ # 2. An empty string value is encoded to `key=` with an equals sign but no value
180
+ if u.isGiven(value)
181
+ query += "="
182
+ query += encodeURIComponent(value)
183
+ query
184
+
185
+ ###**
186
+ Returns whether the given value can be encoded into a query string.
187
+
188
+ We will have `File` values in our params when we serialize a form with a file input.
189
+ These entries will be filtered out when converting to a query string.
190
+ ###
191
+ isPrimitiveValue = (value) ->
192
+ u.isMissing(value) || u.isString(value) || u.isNumber(value) || u.isBoolean(value)
193
+
194
+ safeSet = (obj, k, value) ->
195
+ obj[k] = value unless u.isBasicObjectProperty(k)
196
+
197
+ safeGet = (obj, k) ->
198
+ obj[k] unless u.isBasicObjectProperty(k)
199
+
200
+ buildQueryFromArray = (array) ->
201
+ parts = u.map(array, arrayEntryToQuery)
202
+ parts = u.compact(parts)
203
+ parts.join('&')
204
+
205
+ buildArrayFromQuery = (query) ->
206
+ array = []
207
+ for part in query.split('&')
208
+ if part
209
+ [name, value] = part.split('=')
210
+ name = decodeURIComponent(name)
211
+ # There are three forms we need to handle:
212
+ # (1) foo=bar should become { name: 'foo', bar: 'bar' }
213
+ # (2) foo= should become { name: 'foo', bar: '' }
214
+ # (3) foo should become { name: 'foo', bar: null }
215
+ if u.isGiven(value)
216
+ value = decodeURIComponent(value)
217
+ else
218
+ value = null
219
+ array.push({ name, value })
220
+ array
221
+
222
+ buildArrayFromObject = (object) ->
223
+ array = []
224
+ for k, v of object
225
+ array.push(name: k, value: v)
226
+ array
227
+
228
+ buildObjectFromArray = (array) ->
229
+ obj = {}
230
+ for entry in array
231
+ safeSet(obj, entry.name, entry.value)
232
+ obj
233
+
234
+ buildArrayFromFormData = (formData) ->
235
+ array = []
236
+ u.eachIterator formData.entries(), (value) ->
237
+ [name, value] = value
238
+ array.push({ name, value })
239
+ array
240
+
241
+ buildFormDataFromArray = (array) ->
242
+ formData = new FormData()
243
+ for entry in array
244
+ formData.append(entry.name, entry.value)
245
+ <% if ENV['JS_KNIFE'] %>formData.originalArray = array<% end %>
246
+ formData
247
+
248
+ buildURL = (base, params) ->
249
+ parts = [base, toQuery(params)]
250
+ parts = u.select(parts, u.isPresent)
251
+ separator = if u.contains(base, '?') then '&' else '?'
252
+ parts.join(separator)
253
+
254
+ ###**
255
+ Adds to the given `params` a new entry with the given `name` and `value`.
256
+
257
+ The given params value may be of any [supported type](/up.params).
258
+
259
+ The given `params` value is changed in-place, if possible. Some types, such as query strings,
260
+ cannot be changed in-place. The return value is always a params value that includes the new entry.
261
+
262
+ \#\#\# Example
263
+
264
+ var obj = { foo: 'bar' }
265
+ up.params.add(obj, 'baz', 'bam')
266
+ // obj is now: { foo: 'bar', baz: 'bam' }
267
+
268
+ @function up.params.add
269
+ @param {string|object|FormData|Array|undefined} params
270
+ @param {string} name
271
+ @param {any} value
272
+ @return {string|object|FormData|Array}
273
+ @experimental
274
+ ###
275
+ add = (params, name, value) ->
276
+ newEntry = [{ name, value }]
277
+ assign(params, newEntry)
278
+
279
+ ###**
280
+ Returns a new params value that contains entries from both `params` and `otherParams`.
281
+
282
+ The given params value may be of any [supported type](/up.params).
283
+
284
+ This function creates a new params value. The given `params` argument is not changed.
285
+
286
+ @function up.params.merge
287
+ @param {string|object|FormData|Array|undefined} params
288
+ @param {string|object|FormData|Array|undefined} otherParams
289
+ @return {string|object|FormData|Array}
290
+ @experimental
291
+ ###
292
+ merge = (params, otherParams) ->
293
+ switch natureOf(params)
294
+ when NATURE_MISSING
295
+ merge({}, otherParams)
296
+ when NATURE_ARRAY
297
+ otherArray = toArray(otherParams)
298
+ params.concat(otherArray)
299
+ when NATURE_FORM_DATA
300
+ formData = new FormData()
301
+ assign(formData, params)
302
+ assign(formData, otherParams)
303
+ formData
304
+ when NATURE_QUERY
305
+ otherQuery = toQuery(otherParams)
306
+ parts = u.select([params, otherQuery], u.isPresent)
307
+ parts.join('&')
308
+ when NATURE_OBJECT
309
+ u.merge(params, toObject(otherParams))
310
+
311
+ ###**
312
+ Returns the first param value with the given `name` from the given `params`.
313
+
314
+ The given params value may be of any [supported type](/up.params).
315
+
316
+ \#\#\# Example
317
+
318
+ var array = [
319
+ { name: 'foo', value: 'bar' },
320
+ { name: 'baz', value: 'bam' }
321
+ }
322
+
323
+ value = up.params.get(array, 'baz')
324
+ // value is now: 'bam'
325
+
326
+ @function up.params.get
327
+ @experimental
328
+ ###
329
+ get = (params, name) ->
330
+ switch natureOf(params)
331
+ when NATURE_MISSING
332
+ undefined
333
+ when NATURE_ARRAY
334
+ entry = u.detect(params, (entry) -> entry.name == name)
335
+ entry?.value
336
+ when NATURE_FORM_DATA
337
+ value = params.get(name)
338
+ value = undefined if u.isNull(value)
339
+ value
340
+ when NATURE_QUERY
341
+ safeGet(toObject(params), name)
342
+ when NATURE_OBJECT
343
+ safeGet(params, name)
344
+
345
+ ###**
346
+ Extends the given `params` with entries from the given `otherParams`.
347
+
348
+ The given params value may be of any [supported type](/up.params).
349
+
350
+ The given `params` is changed in-place, if possible. Some types, such as query strings,
351
+ cannot be changed in-place. The return value is always a params value that includes the new entries.
352
+
353
+ @function up.params.assign
354
+ @param {string|object|FormData|Array|undefined} params
355
+ @param {string|object|FormData|Array|undefined} otherParams
356
+ @return {string|object|FormData|Array}
357
+ @experimental
358
+ ###
359
+ assign = (params, otherParams) ->
360
+ switch natureOf(params)
361
+ when NATURE_ARRAY
362
+ otherArray = toArray(otherParams)
363
+ params.push(otherArray...)
364
+ params
365
+ when NATURE_FORM_DATA
366
+ otherArray = toArray(otherParams)
367
+ for entry in otherArray
368
+ params.append(entry.name, entry.value)
369
+ params
370
+ when NATURE_OBJECT
371
+ u.assign(params, toObject(otherParams))
372
+ when NATURE_QUERY, NATURE_MISSING
373
+ # Strings and undefined are immutable, so we merge instead.
374
+ merge(params, otherParams)
375
+
376
+ submittingButton = (form) ->
377
+ $form = $(form)
378
+ submitButtonSelector = up.form.submitButtonSelector()
379
+ $activeElement = $(document.activeElement)
380
+ if $activeElement.is(submitButtonSelector) && $form.has($activeElement)
381
+ $activeElement[0]
382
+ else
383
+ # If no button is focused, we assume the first button in the form.
384
+ $form.find(submitButtonSelector)[0]
385
+
386
+ ###**
387
+ Serializes request params from the given `<form>`.
388
+
389
+ The returned params may be passed as `{ params }` option to
390
+ [`up.request()`](/up.request) or [`up.replace()`](/up.replace).
391
+
392
+ The serialized params will include the form's submit button, if that
393
+ button as a `name` attribute.
394
+
395
+ \#\#\#\# Example
396
+
397
+ Given this HTML form:
398
+
399
+ <form>
400
+ <input type="text" name="name" value="Foo Bar">
401
+ <input type="text" name="email" value="foo@bar.com">
402
+ </form>
403
+
404
+ This would serialize the form into an array representation:
405
+
406
+ var params = up.params.fromForm('input[name=email]')
407
+
408
+ // params is now: [
409
+ // { name: 'name', value: 'Foo Bar' },
410
+ // { name: 'email', value: 'foo@bar.com' }
411
+ // ]
412
+
413
+ @function up.params.fromForm
414
+ @param {Element|jQuery|string} form
415
+ @return {Array}
416
+ @experimental
417
+ ###
418
+ fromForm = (form) ->
419
+ if form = u.element(form)
420
+ fields = form.querySelectorAll(up.form.fieldSelector())
421
+ if button = submittingButton(form)
422
+ fields = u.toArray(fields)
423
+ fields.push(button)
424
+ return u.flatMap(fields, fromField)
425
+
426
+ ###**
427
+ Serializes request params from a single [input field](/up.form.config#config.fields).
428
+ To serialize an entire form, use [`up.params.fromForm()`](/up.params.fromForm).
429
+
430
+ Note that some fields may produce multiple params, such as `<select multiple>`.
431
+
432
+ \#\#\#\# Example
433
+
434
+ Given this HTML form:
435
+
436
+ <form>
437
+ <input type="text" name="email" value="foo@bar.com">
438
+ <input type="password" name="password">
439
+ </form>
440
+
441
+ This would retrieve request parameters from the `email` field:
442
+
443
+ var params = up.params.fromField('input[name=email]')
444
+
445
+ // params is now: [{ name: 'email', value: 'foo@bar.com' }]
446
+
447
+ @function up.params.fromField
448
+ @param {Element|jQuery|string} form
449
+ @return {Array}
450
+ an array of `{ name, value }` objects
451
+ @experimental
452
+ ###
453
+ fromField = (field) ->
454
+ params = []
455
+ if (field = u.element(field)) && (name = field.name) && (!field.disabled)
456
+ tagName = field.tagName
457
+ type = field.type
458
+ if tagName == 'SELECT'
459
+ for option in field.querySelectorAll('option')
460
+ if option.selected
461
+ params.push
462
+ name: name
463
+ value: option.value
464
+ else if type == 'checkbox' || type == 'radio'
465
+ if field.checked
466
+ params.push
467
+ name: name
468
+ value: field.value
469
+ else if type == 'file'
470
+ # The value of an input[type=file] is the local path displayed in the form.
471
+ # The actual File objects are in the #files property.
472
+ for file in field.files
473
+ params.push
474
+ name: name
475
+ value: file
476
+ else
477
+ params.push
478
+ name: name
479
+ value: field.value
480
+ params
481
+
482
+ ###**
483
+ Returns the [query string](https://en.wikipedia.org/wiki/Query_string) from the given URL.
484
+
485
+ The query string is returned **without** a leading question mark (`?`).
486
+ Returns `undefined` if the given URL has no query component.
487
+
488
+ You can access individual values from the returned query string using functions like
489
+ [`up.params.get()`](/up.params.get) or [`up.params.toObject()`](/up.params.toObject).
490
+
491
+ \#\#\# Example
492
+
493
+ var query = up.params.fromURL('http://foo.com?bar=baz')
494
+
495
+ // query is now: 'bar=baz'
496
+
497
+ @function up.params.fromURL
498
+ @param {string} url
499
+ The URL from which to extract the query string.
500
+ @return {string|undefined}
501
+ The given URL's query string, or `undefined` if the URL has no query component.
502
+ @experimental
503
+ ###
504
+ fromURL = (url) ->
505
+ urlParts = u.parseUrl(url)
506
+ if query = urlParts.search
507
+ query = query.replace(/^\?/, '')
508
+ query
509
+
510
+ toArray: toArray
511
+ toObject: toObject
512
+ toQuery: toQuery
513
+ toFormData: toFormData
514
+ buildURL: buildURL
515
+ get: get
516
+ add: add
517
+ assign: assign
518
+ merge: merge
519
+ fromForm: fromForm
520
+ fromURL: fromURL
521
+
522
+ )(jQuery)