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
@@ -136,11 +136,10 @@ up.modal = (($) ->
136
136
  @experimental
137
137
  ###
138
138
  coveredUrl = ->
139
- $modal = $('.up-modal')
140
- $modal.attr('up-covered-url')
139
+ $('.up-modal').attr('up-covered-url')
141
140
 
142
141
  reset = ->
143
- close()
142
+ close(animation: false)
144
143
  currentUrl = undefined
145
144
  config.reset()
146
145
 
@@ -151,17 +150,13 @@ up.modal = (($) ->
151
150
  else
152
151
  template
153
152
 
154
- rememberHistory = ->
155
- $modal = $('.up-modal')
156
- $modal.attr('up-covered-url', up.browser.url())
157
- $modal.attr('up-covered-title', document.title)
158
-
159
153
  discardHistory = ->
160
154
  $modal = $('.up-modal')
161
155
  $modal.removeAttr('up-covered-url')
162
156
  $modal.removeAttr('up-covered-title')
163
157
 
164
- createHiddenModal = (options) ->
158
+ createFrame = (target, options) ->
159
+ shiftElements()
165
160
  $modal = $(templateHtml())
166
161
  $modal.attr('up-sticky', '') if options.sticky
167
162
  $modal.attr('up-covered-url', up.browser.url())
@@ -171,14 +166,14 @@ up.modal = (($) ->
171
166
  $dialog.css('max-width', options.maxWidth) if u.isPresent(options.maxWidth)
172
167
  $dialog.css('height', options.height) if u.isPresent(options.height)
173
168
  $content = $modal.find('.up-modal-content')
174
- $placeholder = u.$createElementFromSelector(options.selector)
169
+ # Create an empty element that will match the
170
+ # selector that is being replaced.
171
+ $placeholder = u.$createElementFromSelector(target)
175
172
  $placeholder.appendTo($content)
176
173
  $modal.appendTo(document.body)
177
- rememberHistory()
178
- $modal.hide()
179
174
  $modal
180
175
 
181
- unshiftElements = []
176
+ unshifters = []
182
177
 
183
178
  # Gives `<body>` a right padding in the width of a scrollbar.
184
179
  # Also gives elements anchored to the right side of the screen
@@ -195,21 +190,26 @@ up.modal = (($) ->
195
190
  'padding-right': "#{bodyRightShift}px",
196
191
  'overflow-y': 'hidden'
197
192
  )
198
- unshiftElements.push(unshiftBody)
193
+ unshifters.push(unshiftBody)
199
194
  up.layout.anchoredRight().each ->
200
195
  $element = $(this)
201
196
  elementRight = parseInt($element.css('right'))
202
197
  elementRightShift = scrollbarWidth + elementRight
203
- unshiftElement = u.temporaryCss($element, 'right': elementRightShift)
204
- unshiftElements.push(unshiftElement)
198
+ unshifter = u.temporaryCss($element, 'right': elementRightShift)
199
+ unshifters.push(unshifter)
205
200
 
206
- updated = (animation, animateOptions) ->
207
- $modal = $('.up-modal')
208
- if $modal.is(':hidden')
209
- shiftElements()
210
- $modal.show()
211
- deferred = up.animate($modal, animation, animateOptions)
212
- deferred.then -> up.emit('up:modal:opened')
201
+ # Reverts the effects of `shiftElements`.
202
+ unshiftElements = ->
203
+ unshifter() while unshifter = unshifters.pop()
204
+
205
+ ###*
206
+ Returns whether a modal is currently open.
207
+
208
+ @function up.modal.isOpen
209
+ @stable
210
+ ###
211
+ isOpen = ->
212
+ $('.up-modal').length > 0
213
213
 
214
214
  ###*
215
215
  Opens the given link's destination in a modal overlay:
@@ -235,6 +235,9 @@ up.modal = (($) ->
235
235
  @param {Boolean} [options.sticky=false]
236
236
  If set to `true`, the modal remains
237
237
  open even if the page changes in the background.
238
+ @param {String} [options.confirm]
239
+ A message that will be displayed in a cancelable confirmation dialog
240
+ before the modal is being opened.
238
241
  @param {Object} [options.history=true]
239
242
  Whether to add a browser history entry for the modal's source URL.
240
243
  @param {String} [options.animation]
@@ -246,7 +249,8 @@ up.modal = (($) ->
246
249
  @param {String} [options.easing]
247
250
  The timing function that controls the animation's acceleration. [`up.animate`](/up.animate).
248
251
  @return {Promise}
249
- A promise that will be resolved when the popup has been loaded and rendered.
252
+ A promise that will be resolved when the modal has been loaded and
253
+ the opening animation has completed.
250
254
  @stable
251
255
  ###
252
256
  follow = (linkOrSelector, options) ->
@@ -275,7 +279,8 @@ up.modal = (($) ->
275
279
  @param {Object} options
276
280
  See options for [`up.modal.follow`](/up.modal.follow).
277
281
  @return {Promise}
278
- A promise that will be resolved when the popup has been loaded and rendered.
282
+ A promise that will be resolved when the modal has been loaded and the opening
283
+ animation has completed.
279
284
  @stable
280
285
  ###
281
286
  visit = (url, options) ->
@@ -291,34 +296,35 @@ up.modal = (($) ->
291
296
  options = u.options(options)
292
297
  $link = u.option(options.$link, u.nullJQuery())
293
298
  url = u.option(options.url, $link.attr('up-href'), $link.attr('href'))
294
- selector = u.option(options.target, $link.attr('up-modal'), 'body')
295
- width = u.option(options.width, $link.attr('up-width'), config.width)
296
- maxWidth = u.option(options.maxWidth, $link.attr('up-max-width'), config.maxWidth)
297
- height = u.option(options.height, $link.attr('up-height'), config.height)
298
- animation = u.option(options.animation, $link.attr('up-animation'), config.openAnimation)
299
- sticky = u.option(options.sticky, u.castedAttr($link, 'up-sticky'))
299
+ target = u.option(options.target, $link.attr('up-modal'), 'body')
300
+ options.width = u.option(options.width, $link.attr('up-width'), config.width)
301
+ options.maxWidth = u.option(options.maxWidth, $link.attr('up-max-width'), config.maxWidth)
302
+ options.height = u.option(options.height, $link.attr('up-height'), config.height)
303
+ options.animation = u.option(options.animation, $link.attr('up-animation'), config.openAnimation)
304
+ options.sticky = u.option(options.sticky, u.castedAttr($link, 'up-sticky'))
300
305
  # Although we usually fall back to full page loads if a browser doesn't support pushState,
301
306
  # in the case of modals we assume that the developer would rather see a dialog
302
307
  # without an URL update.
303
- history = if up.browser.canPushState() then u.option(options.history, u.castedAttr($link, 'up-history'), config.history) else false
308
+ options.history = if up.browser.canPushState() then u.option(options.history, u.castedAttr($link, 'up-history'), config.history) else false
309
+ options.confirm = u.option(options.confirm, $link.attr('up-confirm'))
304
310
  animateOptions = up.motion.animateOptions(options, $link)
305
311
 
306
- close()
307
-
308
- if up.bus.nobodyPrevents('up:modal:open', url: url)
309
- createHiddenModal
310
- selector: selector
311
- width: width
312
- maxWidth: maxWidth
313
- height: height
314
- sticky: sticky
315
- promise = up.replace(selector, url, history: history, requireMatch: false)
316
- promise.then -> updated(animation, animateOptions)
317
- promise
318
- else
319
- # Although someone prevented the destruction, keep a uniform API for
320
- # callers by returning a Deferred that will never be resolved.
321
- u.unresolvableDeferred()
312
+ up.browser.confirm(options.confirm).then ->
313
+ if up.bus.nobodyPrevents('up:modal:open', url: url)
314
+ wasOpen = isOpen()
315
+ close(animation: false) if wasOpen
316
+ options.beforeSwap = -> createFrame(target, options)
317
+ promise = up.replace(target, url, u.merge(options, animation: false))
318
+ unless wasOpen
319
+ promise = promise.then ->
320
+ up.animate($('.up-modal'), options.animation, animateOptions)
321
+ promise = promise.then ->
322
+ up.emit('up:modal:opened')
323
+ promise
324
+ else
325
+ # Although someone prevented opening the modal, keep a uniform API for
326
+ # callers by returning a Deferred that will never be resolved.
327
+ u.unresolvablePromise()
322
328
 
323
329
  ###*
324
330
  This event is [emitted](/up.emit) when a modal dialog is starting to open.
@@ -361,11 +367,11 @@ up.modal = (($) ->
361
367
  title: $modal.attr('up-covered-title')
362
368
  )
363
369
  currentUrl = undefined
364
- deferred = up.destroy($modal, options)
365
- deferred.then ->
366
- unshifter() while unshifter = unshiftElements.pop()
370
+ promise = up.destroy($modal, options)
371
+ promise = promise.then ->
372
+ unshiftElements()
367
373
  up.emit('up:modal:closed')
368
- deferred
374
+ promise
369
375
  else
370
376
  # Although someone prevented the destruction,
371
377
  # keep a uniform API for callers by returning
@@ -423,19 +429,26 @@ up.modal = (($) ->
423
429
  a modal dialog.
424
430
 
425
431
  @selector a[up-modal]
426
- @param [up-sticky]
427
- @param [up-animation]
428
- @param [up-height]
432
+ @param {String} [up-confirm]
433
+ A message that will be displayed in a cancelable confirmation dialog
434
+ before the modal is opened.
435
+ @param {String} [up-sticky]
436
+ If set to `"true"`, the modal remains
437
+ open even if the page changes in the background.
438
+ @param {String} [up-animation]
439
+ The animation to use when opening the modal.
440
+ @param {String} [up-height]
441
+ The width of the dialog in pixels.
442
+ By [default](/up.modal.config) the dialog will grow to fit its contents.
429
443
  @param [up-width]
430
- @param [up-history]
444
+ The width of the dialog in pixels.
445
+ By [default](/up.modal.config) the dialog will grow to fit its contents.
446
+ @param [up-history="true"]
447
+ Whether to add a browser history entry for the modal's source URL.
431
448
  @stable
432
449
  ###
433
- up.link.registerFollowVariant '[up-modal]', ($link) ->
434
- event.preventDefault()
435
- if $link.is('.up-current')
436
- close()
437
- else
438
- follow($link)
450
+ up.link.onAction '[up-modal]', ($link) ->
451
+ follow($link)
439
452
 
440
453
  # Close the modal when someone clicks outside the dialog
441
454
  # (but not on a modal opener).
@@ -491,6 +504,7 @@ up.modal = (($) ->
491
504
  config: config
492
505
  defaults: -> u.error('up.modal.defaults(...) no longer exists. Set values on he up.modal.config property instead.')
493
506
  contains: contains
494
- source: -> up.error('up.popup.source no longer exists. Please use up.popup.url instead.')
507
+ source: -> up.error('up.modal.source no longer exists. Please use up.popup.url instead.')
508
+ isOpen: isOpen
495
509
 
496
510
  )(jQuery)
@@ -156,7 +156,7 @@ up.motion = (($) ->
156
156
  options = animateOptions(options)
157
157
  if animation == 'none' || animation == false
158
158
  none()
159
- if u.isFunction(animation)
159
+ else if u.isFunction(animation)
160
160
  assertIsDeferred(animation($element, options), animation)
161
161
  else if u.isString(animation)
162
162
  animate($element, findAnimation(animation), options)
@@ -168,7 +168,7 @@ up.motion = (($) ->
168
168
  $element.css(animation)
169
169
  u.resolvedDeferred()
170
170
  else
171
- u.error("Unknown animation type %o", animation)
171
+ u.error("Unknown animation type for %o", animation)
172
172
 
173
173
  ###*
174
174
  Extracts animation-related options from the given options hash.
@@ -356,12 +356,13 @@ up.motion = (($) ->
356
356
  finish($old)
357
357
  finish($new)
358
358
 
359
- if transitionOrName == 'none' || transitionOrName == false || animation = animations[transitionOrName]
360
- deferred = skipMorph($old, $new, parsedOptions)
361
- deferred.then -> animate($new, animation || 'none', options)
362
- deferred
359
+ if transitionOrName == 'none' || transitionOrName == false
360
+ return skipMorph($old, $new, parsedOptions)
361
+ else if animation = animations[transitionOrName]
362
+ skipMorph($old, $new, parsedOptions)
363
+ return animate($new, animation, parsedOptions)
363
364
  else if transition = u.presence(transitionOrName, u.isFunction) || transitions[transitionOrName]
364
- withGhosts $old, $new, parsedOptions, ($oldGhost, $newGhost) ->
365
+ return withGhosts $old, $new, parsedOptions, ($oldGhost, $newGhost) ->
365
366
  transitionPromise = transition($oldGhost, $newGhost, parsedOptions)
366
367
  assertIsDeferred(transitionPromise, transitionOrName)
367
368
  else if u.isString(transitionOrName) && transitionOrName.indexOf('/') >= 0
@@ -371,11 +372,11 @@ up.motion = (($) ->
371
372
  animate($old, parts[0], options),
372
373
  animate($new, parts[1], options)
373
374
  )
374
- morph($old, $new, transition, parsedOptions)
375
+ return morph($old, $new, transition, parsedOptions)
375
376
  else
376
377
  u.error("Unknown transition %o", transitionOrName)
377
378
  else
378
- skipMorph($old, $new, parsedOptions)
379
+ return skipMorph($old, $new, parsedOptions)
379
380
 
380
381
  ###*
381
382
  This causes the side effects of a successful transition, but instantly.
@@ -65,8 +65,7 @@ up.popup = (($) ->
65
65
  @experimental
66
66
  ###
67
67
  coveredUrl = ->
68
- $popup = $('.up-popup')
69
- $popup.attr('up-covered-url')
68
+ $('.up-popup').attr('up-covered-url')
70
69
 
71
70
  ###*
72
71
  Sets default options for future popups.
@@ -91,10 +90,10 @@ up.popup = (($) ->
91
90
  history: false
92
91
 
93
92
  reset = ->
94
- close()
93
+ close(animation: false)
95
94
  config.reset()
96
95
 
97
- setPosition = ($link, $popup, position) ->
96
+ setPosition = ($link, position) ->
98
97
  linkBox = u.measure($link, full: true)
99
98
  css = switch position
100
99
  when "bottom-right"
@@ -111,6 +110,7 @@ up.popup = (($) ->
111
110
  bottom: linkBox.top
112
111
  else
113
112
  u.error("Unknown position %o", position)
113
+ $popup = $('.up-popup')
114
114
  $popup.attr('up-position', position)
115
115
  $popup.css(css)
116
116
  ensureInViewport($popup)
@@ -141,33 +141,31 @@ up.popup = (($) ->
141
141
  else if bottom = parseInt($popup.css('bottom'))
142
142
  $popup.css('bottom', bottom + errorY)
143
143
 
144
- rememberHistory = ->
145
- $popup = $('.up-popup')
146
- $popup.attr('up-covered-url', up.browser.url())
147
- $popup.attr('up-covered-title', document.title)
148
-
149
144
  discardHistory = ->
150
145
  $popup = $('.up-popup')
151
146
  $popup.removeAttr('up-covered-url')
152
147
  $popup.removeAttr('up-covered-title')
153
148
 
154
- createHiddenPopup = ($link, selector, sticky) ->
149
+ createFrame = (target, options) ->
155
150
  $popup = u.$createElementFromSelector('.up-popup')
156
- $popup.attr('up-sticky', '') if sticky
157
- $placeholder = u.$createElementFromSelector(selector)
151
+ $popup.attr('up-sticky', '') if options.sticky
152
+ $popup.attr('up-covered-url', up.browser.url())
153
+ $popup.attr('up-covered-title', document.title)
154
+ # Create an empty element that will match the
155
+ # selector that is being replaced.
156
+ $placeholder = u.$createElementFromSelector(target)
158
157
  $placeholder.appendTo($popup)
159
158
  $popup.appendTo(document.body)
160
- rememberHistory()
161
- $popup.hide()
162
159
  $popup
163
-
164
- updated = ($link, position, animation, animateOptions) ->
165
- $popup = $('.up-popup')
166
- if $popup.is(':hidden')
167
- $popup.show()
168
- setPosition($link, $popup, position)
169
- deferred = up.animate($popup, animation, animateOptions)
170
- deferred.then -> up.emit('up:popup:opened')
160
+
161
+ ###*
162
+ Returns whether popup modal is currently open.
163
+
164
+ @function up.popup.isOpen
165
+ @stable
166
+ ###
167
+ isOpen = ->
168
+ $('.up-popup').length > 0
171
169
 
172
170
  ###*
173
171
  Attaches a popup overlay to the given element or selector.
@@ -181,6 +179,9 @@ up.popup = (($) ->
181
179
  Defines where the popup is attached to the opening element.
182
180
 
183
181
  Valid values are `bottom-right`, `bottom-left`, `top-right` and `top-left`.
182
+ @param {String} [options.confirm]
183
+ A message that will be displayed in a cancelable confirmation dialog
184
+ before the modal is being opened.
184
185
  @param {String} [options.animation]
185
186
  The animation to use when opening the popup.
186
187
  @param {Number} [options.duration]
@@ -194,7 +195,8 @@ up.popup = (($) ->
194
195
  open even if the page changes in the background.
195
196
  @param {Object} [options.history=false]
196
197
  @return {Promise}
197
- A promise that will be resolved when the popup has been loaded and rendered.
198
+ A promise that will be resolved when the popup has been loaded and
199
+ the opening animation has completed.
198
200
  @stable
199
201
  ###
200
202
  attach = (linkOrSelector, options) ->
@@ -203,24 +205,32 @@ up.popup = (($) ->
203
205
 
204
206
  options = u.options(options)
205
207
  url = u.option(options.url, $link.attr('href'))
206
- selector = u.option(options.target, $link.attr('up-popup'), 'body')
207
- position = u.option(options.position, $link.attr('up-position'), config.position)
208
- animation = u.option(options.animation, $link.attr('up-animation'), config.openAnimation)
209
- sticky = u.option(options.sticky, u.castedAttr($link, 'up-sticky'))
210
- history = if up.browser.canPushState() then u.option(options.history, u.castedAttr($link, 'up-history'), config.history) else false
208
+ target = u.option(options.target, $link.attr('up-popup'), 'body')
209
+ options.position = u.option(options.position, $link.attr('up-position'), config.position)
210
+ options.animation = u.option(options.animation, $link.attr('up-animation'), config.openAnimation)
211
+ options.sticky = u.option(options.sticky, u.castedAttr($link, 'up-sticky'))
212
+ options.history = if up.browser.canPushState() then u.option(options.history, u.castedAttr($link, 'up-history'), config.history) else false
213
+ options.confirm = u.option(options.confirm, $link.attr('up-confirm'))
211
214
  animateOptions = up.motion.animateOptions(options, $link)
212
215
 
213
- close()
214
-
215
- if up.bus.nobodyPrevents('up:popup:open', url: url)
216
- createHiddenPopup($link, selector, sticky)
217
- promise = up.replace(selector, url, history: history, requireMatch: false)
218
- promise.then -> updated($link, position, animation, animateOptions)
219
- promise
220
- else
221
- # Although someone prevented the destruction, keep a uniform API for
222
- # callers by returning a Deferred that will never be resolved.
223
- u.unresolvableDeferred()
216
+ up.browser.confirm(options.confirm).then ->
217
+ if up.bus.nobodyPrevents('up:popup:open', url: url)
218
+ wasOpen = isOpen()
219
+ close(animation: false) if wasOpen
220
+ options.beforeSwap = -> createFrame(target, options)
221
+ promise = up.replace(target, url, u.merge(options, animation: false))
222
+ promise = promise.then ->
223
+ setPosition($link, options.position)
224
+ unless wasOpen
225
+ promise = promise.then ->
226
+ up.animate($('.up-popup'), options.animation, animateOptions)
227
+ promise = promise.then ->
228
+ up.emit('up:popup:opened')
229
+ promise
230
+ else
231
+ # Although someone prevented the destruction, keep a uniform API for
232
+ # callers by returning a Deferred that will never be resolved.
233
+ u.unresolvableDeferred()
224
234
 
225
235
  ###*
226
236
  This event is [emitted](/up.emit) when a popup is starting to open.
@@ -325,12 +335,15 @@ up.popup = (($) ->
325
335
  Defines where the popup is attached to the opening element.
326
336
 
327
337
  Valid values are `bottom-right`, `bottom-left`, `top-right` and `top-left`.
338
+ @param {String} [up-confirm]
339
+ A message that will be displayed in a cancelable confirmation dialog
340
+ before the popup is opened.
328
341
  @param [up-sticky]
329
342
  If set to `true`, the popup remains
330
343
  open even if the page changes in the background.
331
344
  @stable
332
345
  ###
333
- up.link.registerFollowVariant('[up-popup]', ($link) ->
346
+ up.link.onAction('[up-popup]', ($link) ->
334
347
  if $link.is('.up-current')
335
348
  close()
336
349
  else
@@ -392,5 +405,6 @@ up.popup = (($) ->
392
405
  contains: contains
393
406
  open: -> up.error('up.popup.open no longer exists. Please use up.popup.attach instead.')
394
407
  source: -> up.error('up.popup.source no longer exists. Please use up.popup.url instead.')
408
+ isOpen: isOpen
395
409
 
396
410
  )(jQuery)