upjs-rails 0.17.0 → 0.18.0

Sign up to get free protection for your applications and to get access to all the features.
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