unpoly-rails 0.56.7 → 0.57.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.

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)