upjs-rails 0.17.0 → 0.18.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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -1
  3. data/dist/up.js +929 -374
  4. data/dist/up.min.js +2 -2
  5. data/lib/assets/javascripts/up/browser.js.coffee +31 -14
  6. data/lib/assets/javascripts/up/bus.js.coffee +87 -22
  7. data/lib/assets/javascripts/up/flow.js.coffee +119 -43
  8. data/lib/assets/javascripts/up/form.js.coffee +188 -57
  9. data/lib/assets/javascripts/up/link.js.coffee +57 -21
  10. data/lib/assets/javascripts/up/modal.js.coffee +77 -63
  11. data/lib/assets/javascripts/up/motion.js.coffee +10 -9
  12. data/lib/assets/javascripts/up/popup.js.coffee +54 -40
  13. data/lib/assets/javascripts/up/proxy.js.coffee +46 -17
  14. data/lib/assets/javascripts/up/rails.js.coffee +22 -4
  15. data/lib/assets/javascripts/up/syntax.js.coffee +2 -2
  16. data/lib/assets/javascripts/up/util.js.coffee +100 -16
  17. data/lib/upjs/rails/inspector.rb +3 -3
  18. data/lib/upjs/rails/version.rb +1 -1
  19. data/spec_app/Gemfile.lock +1 -4
  20. data/spec_app/app/controllers/test_controller.rb +2 -2
  21. data/spec_app/spec/controllers/test_controller_spec.rb +5 -5
  22. data/spec_app/spec/javascripts/helpers/browser_switches.js.coffee +9 -0
  23. data/spec_app/spec/javascripts/helpers/knife.js.coffee +0 -1
  24. data/spec_app/spec/javascripts/helpers/reset_path.js.coffee +4 -5
  25. data/spec_app/spec/javascripts/helpers/to_be_present.js.coffee +5 -0
  26. data/spec_app/spec/javascripts/helpers/to_have_request_method.js.coffee +8 -0
  27. data/spec_app/spec/javascripts/up/bus_spec.js.coffee +26 -0
  28. data/spec_app/spec/javascripts/up/flow_spec.js.coffee +203 -91
  29. data/spec_app/spec/javascripts/up/form_spec.js.coffee +244 -49
  30. data/spec_app/spec/javascripts/up/history_spec.js.coffee +8 -2
  31. data/spec_app/spec/javascripts/up/link_spec.js.coffee +83 -30
  32. data/spec_app/spec/javascripts/up/modal_spec.js.coffee +23 -17
  33. data/spec_app/spec/javascripts/up/motion_spec.js.coffee +4 -4
  34. data/spec_app/spec/javascripts/up/navigation_spec.js.coffee +1 -1
  35. data/spec_app/spec/javascripts/up/popup_spec.js.coffee +26 -16
  36. data/spec_app/spec/javascripts/up/proxy_spec.js.coffee +45 -13
  37. data/spec_app/spec/javascripts/up/rails_spec.js.coffee +48 -0
  38. data/spec_app/spec/javascripts/up/util_spec.js.coffee +48 -0
  39. metadata +5 -2
@@ -114,31 +114,26 @@ up.form = (($) ->
114
114
  $form = $(formOrSelector).closest('form')
115
115
 
116
116
  options = u.options(options)
117
- successSelector = u.option(options.target, $form.attr('up-target'), 'body')
118
- successSelector = up.flow.resolveSelector(successSelector, options)
119
- failureSelector = u.option(options.failTarget, $form.attr('up-fail-target')) || u.selectorForElement($form)
120
- failureSelector = up.flow.resolveSelector(failureSelector, options)
121
-
122
- historyOption = u.option(options.history, u.castedAttr($form, 'up-history'), true)
123
- successTransition = u.option(options.transition, u.castedAttr($form, 'up-transition'))
124
- failureTransition = u.option(options.failTransition, u.castedAttr($form, 'up-fail-transition'), successTransition)
125
- httpMethod = u.option(options.method, $form.attr('up-method'), $form.attr('data-method'), $form.attr('method'), 'post').toUpperCase()
126
- headers = u.option(options.headers, {})
127
-
128
- implantOptions = {}
129
- implantOptions.reveal = u.option(options.reveal, u.castedAttr($form, 'up-reveal'), true)
130
- implantOptions.cache = u.option(options.cache, u.castedAttr($form, 'up-cache'))
131
- implantOptions.restoreScroll = u.option(options.restoreScroll, u.castedAttr($form, 'up-restore-scroll'))
132
- implantOptions.origin = u.option(options.origin, $form)
133
- implantOptions = u.extend(implantOptions, up.motion.animateOptions(options, $form))
134
-
135
- useCache = u.option(options.cache, u.castedAttr($form, 'up-cache'))
117
+ target = u.option(options.target, $form.attr('up-target'), 'body')
136
118
  url = u.option(options.url, $form.attr('action'), up.browser.url())
119
+ options.failTarget = u.option(options.failTarget, $form.attr('up-fail-target')) || u.selectorForElement($form)
120
+ options.history = u.option(options.history, u.castedAttr($form, 'up-history'), true)
121
+ options.transition = u.option(options.transition, u.castedAttr($form, 'up-transition'), 'none')
122
+ options.failTransition = u.option(options.failTransition, u.castedAttr($form, 'up-fail-transition'), 'none')
123
+ options.method = u.option(options.method, $form.attr('up-method'), $form.attr('data-method'), $form.attr('method'), 'post').toUpperCase()
124
+ options.headers = u.option(options.headers, {})
125
+ options.reveal = u.option(options.reveal, u.castedAttr($form, 'up-reveal'), true)
126
+ options.cache = u.option(options.cache, u.castedAttr($form, 'up-cache'))
127
+ options.restoreScroll = u.option(options.restoreScroll, u.castedAttr($form, 'up-restore-scroll'))
128
+ options.origin = u.option(options.origin, $form)
129
+ options.data = $form.serializeArray()
130
+ options = u.merge(options, up.motion.animateOptions(options, $form))
137
131
 
138
132
  hasFileInputs = $form.find('input[type=file]').length
139
133
 
140
134
  if options.validate
141
- headers['X-Up-Validate'] = options.validate
135
+ options.headers ||= {}
136
+ options.headers['X-Up-Validate'] = options.validate
142
137
  # Since we cannot (yet) submit file inputs via AJAX, we cannot
143
138
  # offer inline validation for such forms.
144
139
  if hasFileInputs
@@ -146,43 +141,13 @@ up.form = (($) ->
146
141
 
147
142
  $form.addClass('up-active')
148
143
 
149
- if hasFileInputs || (!up.browser.canPushState() && historyOption != false)
144
+ if hasFileInputs || (!up.browser.canPushState() && options.history != false)
150
145
  $form.get(0).submit()
151
146
  return u.unresolvablePromise()
152
147
 
153
- request = {
154
- url: url
155
- method: httpMethod
156
- data: $form.serialize()
157
- selector: successSelector
158
- cache: useCache
159
- headers: headers
160
- }
161
-
162
- successUrl = (xhr) ->
163
- url = undefined
164
- if u.isGiven(historyOption)
165
- if historyOption == false || u.isString(historyOption)
166
- url = historyOption
167
- else if currentLocation = u.locationFromXhr(xhr)
168
- url = currentLocation
169
- else if request.type == 'GET'
170
- url = request.url + '?' + request.data
171
- u.option(url, false)
172
-
173
- up.proxy.ajax(request)
174
- .always ->
175
- $form.removeClass('up-active')
176
- .done (html, textStatus, xhr) ->
177
- successOptions = u.merge(implantOptions,
178
- history: successUrl(xhr),
179
- transition: successTransition
180
- )
181
- up.flow.implant(successSelector, html, successOptions)
182
- .fail (xhr, textStatus, errorThrown) ->
183
- html = xhr.responseText
184
- failureOptions = u.merge(implantOptions, transition: failureTransition)
185
- up.flow.implant(failureSelector, html, failureOptions)
148
+ promise = up.replace(target, url, options)
149
+ promise.always -> $form.removeClass('up-active')
150
+ return promise
186
151
 
187
152
  ###*
188
153
  Observes a field or form and runs a callback when a value changes.
@@ -355,7 +320,6 @@ up.form = (($) ->
355
320
  @stable
356
321
  ###
357
322
  autosubmit = (selectorOrElement, options) ->
358
- console.log("autosubmit %o", selectorOrElement)
359
323
  observe(selectorOrElement, options, (value, $field) ->
360
324
  $form = $field.closest('form')
361
325
  $field.addClass('up-active')
@@ -366,7 +330,7 @@ up.form = (($) ->
366
330
  target = u.option(options.target, $field.attr('up-validate'))
367
331
  if u.isBlank(target)
368
332
  target ||= u.detect(config.validateTargets, (defaultTarget) ->
369
- resolvedDefault = up.flow.resolveSelector(defaultTarget, options)
333
+ resolvedDefault = up.flow.resolveSelector(defaultTarget, options.origin)
370
334
  $field.closest(resolvedDefault).length
371
335
  )
372
336
  if u.isBlank(target)
@@ -415,6 +379,95 @@ up.form = (($) ->
415
379
  promise = up.submit($form, options)
416
380
  promise
417
381
 
382
+ currentValuesForToggle = ($field) ->
383
+ values = undefined
384
+ if $field.is('input[type=checkbox]')
385
+ if $field.is(':checked')
386
+ values = [':checked', ':present', $field.val()]
387
+ else
388
+ values = [':unchecked', ':blank']
389
+ else if $field.is('input[type=radio]')
390
+ console.log('-- it is a radio button --')
391
+ $checkedButton = $field.closest('form, body').find("input[type='radio'][name='#{$field.attr('name')}']:checked")
392
+ console.log('checked button is %o', $checkedButton)
393
+ console.log('checked button val is %o', $checkedButton.val())
394
+ if $checkedButton.length
395
+ values = [':checked', ':present', $checkedButton.val()]
396
+ else
397
+ values = [':unchecked', ':blank']
398
+ else
399
+ console.log('-- else -- for %o', $field)
400
+ value = $field.val()
401
+ if u.isPresent(value)
402
+ values = [':present', value]
403
+ else
404
+ values = [':blank']
405
+ values
406
+
407
+ currentValuesForToggle = ($field) ->
408
+ if $field.is('input[type=checkbox]')
409
+ if $field.is(':checked')
410
+ value = $field.val()
411
+ meta = ':checked'
412
+ else
413
+ meta = ':unchecked'
414
+ else if $field.is('input[type=radio]')
415
+ $checkedButton = $field.closest('form, body').find("input[type='radio'][name='#{$field.attr('name')}']:checked")
416
+ if $checkedButton.length
417
+ meta = ':checked'
418
+ value = $checkedButton.val()
419
+ else
420
+ meta = ':unchecked'
421
+ else
422
+ value = $field.val()
423
+ values = []
424
+ if u.isPresent(value)
425
+ values.push(value)
426
+ values.push(':present')
427
+ else
428
+ values.push(':blank')
429
+ if u.isPresent(meta)
430
+ values.push(meta)
431
+ values
432
+
433
+ ###*
434
+ Shows or hides a target selector depending on the value.
435
+
436
+ See [`[up-toggle]`](/up-toggle) for more documentation and examples.
437
+
438
+ This function does not currently have a very useful API outside
439
+ of our use for `up-toggle`'s UJS behavior, that's why it's currently
440
+ still marked `@internal`.
441
+
442
+ @function up.form.toggle
443
+ @param {String|Element|jQuery} fieldOrSelector
444
+ @param {String} [options.target]
445
+ The target selectors to toggle.
446
+ Defaults to an `up-toggle` attribute on the given field.
447
+ @internal
448
+ ###
449
+ toggleTargets = (fieldOrSelector, options) ->
450
+ $field = $(fieldOrSelector)
451
+ options = u.options(options)
452
+ targets = u.option(options.target, $field.attr('up-toggle'))
453
+ u.isPresent(targets) or u.error("No toggle target given for %o", $field)
454
+ fieldValues = currentValuesForToggle($field)
455
+ $(targets).each ->
456
+ $target = $(this)
457
+ if hideValues = $target.attr('up-hide-for')
458
+ hideValues = hideValues.split(' ')
459
+ show = u.intersect(fieldValues, hideValues).length == 0
460
+ else
461
+ if showValues = $target.attr('up-show-for')
462
+ showValues = showValues.split(' ')
463
+ else
464
+ # If the target has neither up-show-for or up-hide-for attributes,
465
+ # assume the user wants the target to be visible whenever anything
466
+ # is checked or entered.
467
+ showValues = [':present', ':checked']
468
+ show = u.intersect(fieldValues, showValues).length > 0
469
+ $target.toggle(show)
470
+
418
471
  ###*
419
472
  Forms with an `up-target` attribute are [submitted via AJAX](/up.submit)
420
473
  instead of triggering a full page reload.
@@ -468,7 +521,7 @@ up.form = (($) ->
468
521
 
469
522
  When the form's action performs a redirect, the server should echo
470
523
  the new request's URL as a response header `X-Up-Location`
471
- and the request's HTTP method as `X-Up-Method`.
524
+ and the request's HTTP method as `X-Up-Method: GET`.
472
525
 
473
526
  If you are using Up.js via the `upjs-rails` gem, these headers
474
527
  are set automatically for every request.
@@ -662,6 +715,83 @@ up.form = (($) ->
662
715
  up.on 'change', '[up-validate]', (event, $field) ->
663
716
  validate($field)
664
717
 
718
+ ###*
719
+ Show or hide part of a form if certain options are selected or boxes are checked.
720
+
721
+ \#\#\#\# Example
722
+
723
+ The triggering input gets an `up-toggle` attribute with a selector for the elements to show or hide:
724
+
725
+ <select name="advancedness" up-toggle=".target">
726
+ <option value="basic">Basic parts</option>
727
+ <option value="advanced">Advanced parts</option>
728
+ <option value="very-advanced">Very advanced parts</option>
729
+ </select>
730
+
731
+ The target elements get a space-separated list of select values for which they are shown or hidden:
732
+
733
+ <div class="target" up-show-for="basic">
734
+ only shown for advancedness = basic
735
+ </div>
736
+
737
+ <div class="target" up-hide-for="basic">
738
+ hidden for advancedness = basic
739
+ </div>
740
+
741
+ <div class="target" up-show-for="advanced very-advanced">
742
+ shown for advancedness = advanced or very-advanced
743
+ </div>
744
+
745
+ For checkboxes you can also use the pseudo-values `:checked` or `:unchecked` like so:
746
+
747
+ <input type="checkbox" name="flag" up-toggle=".target">
748
+
749
+ <div class="target" up-show-for=":checked">
750
+ only shown when checkbox is checked
751
+ </div>
752
+
753
+ You can also use the pseudo-values `:blank` to match an empty input value,
754
+ or `:present` to match a non-empty input value:
755
+
756
+ <input type="text" name="email" up-toggle=".target">
757
+
758
+ <div class="target" up-show-for=":blank">
759
+ please enter an email address
760
+ </div>
761
+
762
+ @selector [up-toggle]
763
+ @stable
764
+ ###
765
+
766
+ ###*
767
+ Show this element only if a form field has a given value.
768
+
769
+ See [`[up-toggle]`](/up-toggle) for more documentation and examples.
770
+
771
+ @selector [up-show-for]
772
+ @param up-show-for
773
+ A space-separated list of values for which to show this element.
774
+ @stable
775
+ ###
776
+
777
+ ###*
778
+ Hide this element if a form field has a given value.
779
+
780
+ See [`[up-toggle]`](/up-toggle) for more documentation and examples.
781
+
782
+ @selector [up-hide-for]
783
+ @param up-hide-for
784
+ A space-separated list of values for which to show this element.
785
+ @stable
786
+ ###
787
+
788
+ up.on 'change', '[up-toggle]', (event, $field) ->
789
+ console.log("CHANGE EVENT")
790
+ toggleTargets($field)
791
+
792
+ up.compiler '[up-toggle]', ($field) ->
793
+ toggleTargets($field)
794
+
665
795
  ###*
666
796
  Observes this field or form and runs a callback when a value changes.
667
797
 
@@ -728,6 +858,7 @@ up.form = (($) ->
728
858
  submit: submit
729
859
  observe: observe
730
860
  validate: validate
861
+ toggleTargets: toggleTargets
731
862
 
732
863
  )(jQuery)
733
864
 
@@ -130,8 +130,13 @@ up.link = (($) ->
130
130
  or any element that is marked up with an `up-href` attribute.
131
131
  @param {String} [options.target]
132
132
  The selector to replace.
133
- Defaults to the `up-target` attribute on `link`,
134
- or to `body` if such an attribute does not exist.
133
+ Defaults to the `up-target` attribute on `link`, or to `body` if such an attribute does not exist.
134
+ @param {String} [options.failTarget]
135
+ The selector to replace if the server responds with a non-200 status code.
136
+ Defaults to the `up-fail-target` attribute on `link`, or to `body` if such an attribute does not exist.
137
+ @param {String} [options.confirm]
138
+ A message that will be displayed in a cancelable confirmation dialog
139
+ before the link is followed.
135
140
  @param {Function|String} [options.transition]
136
141
  A transition function or name.
137
142
  @param {Number} [options.duration]
@@ -162,17 +167,21 @@ up.link = (($) ->
162
167
 
163
168
  options = u.options(options)
164
169
  url = u.option($link.attr('up-href'), $link.attr('href'))
165
- selector = u.option(options.target, $link.attr('up-target'), 'body')
166
- options.transition = u.option(options.transition, u.castedAttr($link, 'up-transition'), u.castedAttr($link, 'up-animation'))
170
+ target = u.option(options.target, $link.attr('up-target'), 'body')
171
+ options.failTarget = u.option(options.failTarget, $link.attr('up-fail-target'), 'body')
172
+ options.transition = u.option(options.transition, u.castedAttr($link, 'up-transition'), 'none')
173
+ options.failTransition = u.option(options.failTransition, u.castedAttr($link, 'up-fail-transition'), 'none')
167
174
  options.history = u.option(options.history, u.castedAttr($link, 'up-history'))
168
175
  options.reveal = u.option(options.reveal, u.castedAttr($link, 'up-reveal'), true)
169
176
  options.cache = u.option(options.cache, u.castedAttr($link, 'up-cache'))
170
177
  options.restoreScroll = u.option(options.restoreScroll, u.castedAttr($link, 'up-restore-scroll'))
171
178
  options.method = followMethod($link, options)
172
179
  options.origin = u.option(options.origin, $link)
180
+ options.confirm = u.option(options.confirm, $link.attr('up-confirm'))
173
181
  options = u.merge(options, up.motion.animateOptions(options, $link))
174
182
 
175
- up.replace(selector, url, options)
183
+ up.browser.confirm(options.confirm).then ->
184
+ up.replace(target, url, options)
176
185
 
177
186
  ###*
178
187
  Returns the HTTP method that should be used when following the given link.
@@ -213,7 +222,7 @@ up.link = (($) ->
213
222
  ###
214
223
  allowDefault = (event) ->
215
224
 
216
- registerFollowVariant = (selector, handler) ->
225
+ onAction = (selector, handler) ->
217
226
  followVariantSelectors.push(selector)
218
227
  up.on 'click', "a#{selector}, [up-href]#{selector}", (event, $link) ->
219
228
  if shouldProcessLinkEvent(event, $link)
@@ -298,9 +307,14 @@ up.link = (($) ->
298
307
  @selector a[up-target]
299
308
  @param {String} up-target
300
309
  The CSS selector to replace
310
+ @param [up-fail-target='body']
311
+ The selector to replace if the server responds with a non-200 status code.
301
312
  @param {String} [up-href]
302
313
  The destination URL to follow.
303
314
  If omitted, the the link's `href` attribute will be used.
315
+ @param {String} [up-confirm]
316
+ A message that will be displayed in a cancelable confirmation dialog
317
+ before the link is followed.
304
318
  @param {String} [up-reveal='true']
305
319
  Whether to reveal the target element within its viewport before updating.
306
320
  @param {String} [up-restore-scroll='false']
@@ -314,7 +328,7 @@ up.link = (($) ->
314
328
  Set this to `'false'` to prevent the current URL from being updated.
315
329
  @stable
316
330
  ###
317
- registerFollowVariant '[up-target]', ($link) ->
331
+ onAction '[up-target]', ($link) ->
318
332
  follow($link)
319
333
 
320
334
  ###*
@@ -361,9 +375,14 @@ up.link = (($) ->
361
375
  opening the destination in a new tab.
362
376
 
363
377
  @selector a[up-follow]
378
+ @param [up-fail-target='body']
379
+ The selector to replace if the server responds with a non-200 status code.
364
380
  @param [up-href]
365
381
  The destination URL to follow.
366
382
  If omitted, the the link's `href` attribute will be used.
383
+ @param {String} [up-confirm]
384
+ A message that will be displayed in a cancelable confirmation dialog
385
+ before the link is followed.
367
386
  @param [up-history]
368
387
  Set this to `'false'` to prevent the current URL from being updated.
369
388
  @param [up-restore-scroll='false']
@@ -371,7 +390,7 @@ up.link = (($) ->
371
390
  within the response.
372
391
  @stable
373
392
  ###
374
- registerFollowVariant '[up-follow]', ($link) ->
393
+ onAction '[up-follow]', ($link) ->
375
394
  follow($link)
376
395
 
377
396
  ###*
@@ -393,22 +412,39 @@ up.link = (($) ->
393
412
 
394
413
  `up-expand` also expands links that open [modals](/up.modal) or [popups](/up.popup).
395
414
 
415
+ \#\#\#\# Elements with multiple contained links
416
+
417
+ If a container contains more than one link, you can set the value of the
418
+ `up-expand` attribute to a CSS selector to define which link should be expanded:
419
+
420
+ <div class="notification" up-expand=".close">
421
+ Record was saved!
422
+ <a class="details" href="/records/5">Details</a>
423
+ <a class="close" href="/records">Close</a>
424
+ </div>
425
+
396
426
  @selector [up-expand]
427
+ @param {String} [up-expand]
428
+ A CSS selector that defines which containing link should be expanded.
429
+
430
+ If omitted, the first contained link will be expanded.
397
431
  @stable
398
432
  ###
399
433
  up.compiler '[up-expand]', ($area) ->
400
- link = $area.find('a, [up-href]').get(0)
401
- link or u.error('No link to expand within %o', $area)
402
- upAttributePattern = /^up-/
403
- newAttrs = {}
404
- newAttrs['up-href'] = $(link).attr('href')
405
- for attribute in link.attributes
406
- name = attribute.name
407
- if name.match(upAttributePattern)
408
- newAttrs[name] = attribute.value
409
- u.setMissingAttrs($area, newAttrs)
410
- $area.removeAttr('up-expand')
411
- makeFollowable($area)
434
+ $childLinks = $area.find('a, [up-href]')
435
+ if selector = $area.attr('up-expand')
436
+ $childLinks = $childLinks.filter(selector)
437
+ if link = $childLinks.get(0)
438
+ upAttributePattern = /^up-/
439
+ newAttrs = {}
440
+ newAttrs['up-href'] = $(link).attr('href')
441
+ for attribute in link.attributes
442
+ name = attribute.name
443
+ if name.match(upAttributePattern)
444
+ newAttrs[name] = attribute.value
445
+ u.setMissingAttrs($area, newAttrs)
446
+ $area.removeAttr('up-expand')
447
+ makeFollowable($area)
412
448
 
413
449
  ###*
414
450
  Marks up the current link to be followed *as fast as possible*.
@@ -452,7 +488,7 @@ up.link = (($) ->
452
488
  shouldProcessLinkEvent: shouldProcessLinkEvent
453
489
  childClicked: childClicked
454
490
  followMethod: followMethod
455
- registerFollowVariant: registerFollowVariant
491
+ onAction: onAction
456
492
 
457
493
  )(jQuery)
458
494