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
@@ -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)