unpoly-rails 0.36.2 → 0.37.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.

Potentially problematic release.


This version of unpoly-rails might be problematic. Click here for more details.

Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/README.md +1 -1
  4. data/dist/unpoly.js +137 -73
  5. data/dist/unpoly.min.js +3 -3
  6. data/lib/assets/javascripts/unpoly/bus.coffee +2 -2
  7. data/lib/assets/javascripts/unpoly/dom/extract_plan.coffee +4 -2
  8. data/lib/assets/javascripts/unpoly/dom.coffee +26 -12
  9. data/lib/assets/javascripts/unpoly/form.coffee +19 -1
  10. data/lib/assets/javascripts/unpoly/layout.coffee +1 -1
  11. data/lib/assets/javascripts/unpoly/link.coffee +9 -2
  12. data/lib/assets/javascripts/unpoly/modal.coffee +1 -0
  13. data/lib/assets/javascripts/unpoly/popup.coffee +1 -0
  14. data/lib/assets/javascripts/unpoly/syntax.coffee +48 -29
  15. data/lib/assets/javascripts/unpoly/util.coffee +12 -5
  16. data/lib/unpoly/rails/version.rb +1 -1
  17. data/package.json +1 -1
  18. data/spec_app/Gemfile.lock +1 -1
  19. data/spec_app/app/assets/javascripts/integration_test.coffee +4 -0
  20. data/spec_app/app/controllers/replace_test_controller.rb +5 -0
  21. data/spec_app/app/views/pages/start.erb +4 -0
  22. data/spec_app/app/views/replace_test/_nav.erb +6 -0
  23. data/spec_app/app/views/replace_test/page1.erb +14 -0
  24. data/spec_app/app/views/replace_test/page2.erb +14 -0
  25. data/spec_app/config/routes.rb +1 -0
  26. data/spec_app/spec/javascripts/helpers/reset_path.js.coffee +1 -2
  27. data/spec_app/spec/javascripts/up/dom_spec.js.coffee +69 -1
  28. data/spec_app/spec/javascripts/up/feedback_spec.js.coffee +5 -3
  29. data/spec_app/spec/javascripts/up/history_spec.js.coffee +174 -153
  30. data/spec_app/spec/javascripts/up/link_spec.js.coffee +77 -19
  31. data/spec_app/spec/javascripts/up/syntax_spec.js.coffee +1 -1
  32. data/spec_app/spec/javascripts/up/util_spec.js.coffee +34 -0
  33. metadata +6 -2
@@ -222,6 +222,8 @@ up.dom = (($) ->
222
222
  same layer as the element that triggered the replacement (see `options.origin`).
223
223
  If that element is not known, or no match was found in that layer,
224
224
  Unpoly will search in other layers, starting from the topmost layer.
225
+ @param {String} [options.failLayer='auto']
226
+ The name of the layer that ought to be updated if the server sends a non-200 status code.
225
227
 
226
228
  @return {Promise}
227
229
  A promise that will be resolved when the page has been updated.
@@ -243,6 +245,8 @@ up.dom = (($) ->
243
245
  failureOptions = u.merge options,
244
246
  humanizedTarget: 'failure target'
245
247
  provideTarget: undefined # don't provide a target if we're targeting the failTarget
248
+ u.renameKey(failureOptions, 'failTransition', 'transition')
249
+ u.renameKey(failureOptions, 'failLayer', 'layer')
246
250
 
247
251
  target = bestPreflightSelector(selectorOrElement, successOptions)
248
252
  failTarget = bestPreflightSelector(options.failTarget, failureOptions)
@@ -304,8 +308,6 @@ up.dom = (($) ->
304
308
  options.history = false unless u.isString(options.history)
305
309
  options.source = 'keep' unless u.isString(options.source)
306
310
  else
307
- options.transition = options.failTransition
308
- options.failTransition = undefined
309
311
  if isReloadable # e.g. GET returns 500 Internal Server Error
310
312
  options.history = url unless options.history is false
311
313
  options.source = url unless options.source is false
@@ -472,14 +474,19 @@ up.dom = (($) ->
472
474
  promise = u.resolvedPromise()
473
475
 
474
476
  else
475
- replacement = ->
477
+ # This needs to happen before prepareClean() below.
478
+ options.keepPlans = transferKeepableElements($old, $new, options)
476
479
 
477
- options.keepPlans = transferKeepableElements($old, $new, options)
480
+ # Collect destructor functions before swapping the elements.
481
+ # Detaching an element from the DOM will cause jQuery to remove the data properties
482
+ # where we store constructor functions.
483
+ clean = up.syntax.prepareClean($old)
478
484
 
479
- if $old.is('body')
485
+ replacement = ->
486
+ if isSingletonElement($old)
480
487
  # jQuery will actually let us .insertBefore the new <body> tag,
481
488
  # but that's probably bad Karma.
482
- swapBody($old, $new)
489
+ swapSingletonElement($old, $new)
483
490
  # We cannot morph the <body> tag
484
491
  transition = false
485
492
  else
@@ -502,13 +509,18 @@ up.dom = (($) ->
502
509
 
503
510
  # Wrap the replacement as a destroy animation, so $old will
504
511
  # get marked as .up-destroying right away.
505
- promise = destroy($old, animation: replacement)
512
+ promise = destroy($old, { clean, animation: replacement })
506
513
 
507
514
  promise
508
515
 
516
+ isSingletonElement = ($element) ->
517
+ $element.is('body')
518
+
509
519
  # This is a separate method so we can mock it in specs
510
- swapBody = ($oldBody, $newBody) ->
511
- $oldBody.replaceWith($newBody)
520
+ swapSingletonElement = ($old, $new) ->
521
+ # jQuery will actually let us .insertBefore the new <body> tag,
522
+ # but that's probably bad Karma.
523
+ $old.replaceWith($new)
512
524
 
513
525
  transferKeepableElements = ($old, $new, options) ->
514
526
  keepPlans = []
@@ -750,6 +762,7 @@ up.dom = (($) ->
750
762
  $match = undefined
751
763
  if u.isPresent(origin)
752
764
  originLayer = layerOf(origin)
765
+ # Make the origin's layer the top priority
753
766
  u.remove(layers, originLayer)
754
767
  layers.unshift(originLayer)
755
768
  for layer in layers
@@ -809,6 +822,7 @@ up.dom = (($) ->
809
822
  @stable
810
823
  ###
811
824
  destroy = (selectorOrElement, options) ->
825
+ options = u.options(options, animation: false)
812
826
 
813
827
  $element = $(selectorOrElement)
814
828
  unless $element.is('.up-placeholder, .up-tooltip, .up-modal, .up-popup')
@@ -817,7 +831,6 @@ up.dom = (($) ->
817
831
  if $element.length == 0
818
832
  u.resolvedDeferred()
819
833
  else if up.bus.nobodyPrevents('up:fragment:destroy', $element: $element, message: destroyMessage)
820
- options = u.options(options, animation: false)
821
834
  animateOptions = up.motion.animateOptions(options)
822
835
  $element.addClass('up-destroying')
823
836
  # If e.g. a modal or popup asks us to restore a URL, do this
@@ -829,7 +842,8 @@ up.dom = (($) ->
829
842
  up.motion.animate($element, options.animation, animateOptions)
830
843
 
831
844
  animationDeferred.then ->
832
- up.syntax.clean($element)
845
+ options.clean ||= -> up.syntax.clean($element)
846
+ options.clean()
833
847
  # Emit this while $element is still part of the DOM, so event
834
848
  # listeners bound to the document will receive the event.
835
849
  up.emit 'up:fragment:destroyed', $element: $element, message: destroyedMessage
@@ -838,7 +852,7 @@ up.dom = (($) ->
838
852
  else
839
853
  # Although someone prevented the destruction, keep a uniform API for
840
854
  # callers by returning a Deferred that will never be resolved.
841
- $.Deferred()
855
+ u.unresolvableDeferred()
842
856
 
843
857
  ###*
844
858
  Before a page fragment is being [destroyed](/up.destroy), this
@@ -108,6 +108,13 @@ up.form = (($) ->
108
108
  @param {Object} [options.headers={}]
109
109
  An object of additional header key/value pairs to send along
110
110
  with the request.
111
+ @param {String} [options.layer='auto']
112
+ The name of the layer that ought to be updated. Valid values are
113
+ `auto`, `page`, `modal` and `popup`.
114
+
115
+ If set to `auto` (default), Unpoly will try to find a match in the form's layer.
116
+ @param {String} [options.failLayer='auto']
117
+ The name of the layer that ought to be updated if the server sends a non-200 status code.
111
118
  @return {Promise}
112
119
  A promise for the successful form submission.
113
120
  @stable
@@ -131,6 +138,7 @@ up.form = (($) ->
131
138
  options.restoreScroll = u.option(options.restoreScroll, u.castedAttr($form, 'up-restore-scroll'))
132
139
  options.origin = u.option(options.origin, $form)
133
140
  options.layer = u.option(options.layer, $form.attr('up-layer'), 'auto')
141
+ options.failLayer = u.option(options.failLayer, $form.attr('up-fail-layer'), 'auto')
134
142
  options.data = u.requestDataFromForm($form)
135
143
  options = u.merge(options, up.motion.animateOptions(options, $form))
136
144
 
@@ -445,7 +453,7 @@ up.form = (($) ->
445
453
  if switcher
446
454
  $(switcher)
447
455
  else
448
- u.fail('Could not find [up-switch] field for %o', $element.get(0))
456
+ u.fail('Could not find [up-switch] field for %o', $target.get(0))
449
457
 
450
458
  ###*
451
459
  Forms with an `up-target` attribute are [submitted via AJAX](/up.submit)
@@ -539,6 +547,16 @@ up.form = (($) ->
539
547
  Alternately you can use an attribute `data-method`
540
548
  ([Rails UJS](https://github.com/rails/jquery-ujs/wiki/Unobtrusive-scripting-support-for-jQuery))
541
549
  or `method` (vanilla HTML) for the same purpose.
550
+ @param {String} [up-layer='auto']
551
+ The name of the layer that ought to be updated. Valid values are
552
+ `auto`, `page`, `modal` and `popup`.
553
+
554
+ If set to `auto` (default), Unpoly will try to find a match in the form's layer.
555
+ If no match was found in that layer,
556
+ Unpoly will search in other layers, starting from the topmost layer.
557
+ @param {String} [up-fail-layer='auto']
558
+ The name of the layer that ought to be updated if the server sends a
559
+ non-200 status code.
542
560
  @param {String} [up-reveal='true']
543
561
  Whether to reveal the target element within its viewport before updating.
544
562
  @param {String} [up-restore-scroll='false']
@@ -432,7 +432,7 @@ up.layout = (($) ->
432
432
  else
433
433
  $viewports = viewports()
434
434
 
435
- scrollTopsForUrl = lastScrollTops.get(url)
435
+ scrollTopsForUrl = lastScrollTops.get(url) || {}
436
436
 
437
437
  up.log.group 'Restoring scroll positions for URL %s to %o', url, scrollTopsForUrl, ->
438
438
  $viewports.each ->
@@ -157,6 +157,9 @@ up.link = (($) ->
157
157
  If set to `auto` (default), Unpoly will try to find a match in the
158
158
  same layer as the given link. If no match was found in that layer,
159
159
  Unpoly will search in other layers, starting from the topmost layer.
160
+ @param {String} [options.failLayer='auto']
161
+ The name of the layer that ought to be updated if the server sends a non-200 status code.
162
+
160
163
  @return {Promise}
161
164
  A promise that will be resolved when the link destination
162
165
  has been loaded and rendered.
@@ -180,6 +183,7 @@ up.link = (($) ->
180
183
  options.method = followMethod($link, options)
181
184
  options.origin = u.option(options.origin, $link)
182
185
  options.layer = u.option(options.layer, $link.attr('up-layer'), 'auto')
186
+ options.failLayer = u.option(options.failLayer, $link.attr('up-fail-layer'), 'auto')
183
187
  options.confirm = u.option(options.confirm, $link.attr('up-confirm'))
184
188
  options = u.merge(options, up.motion.animateOptions(options, $link))
185
189
 
@@ -357,9 +361,12 @@ up.link = (($) ->
357
361
  The name of the layer that ought to be updated. Valid values are
358
362
  `auto`, `page`, `modal` and `popup`.
359
363
 
360
- If set to `auto` (default), Unpoly will try to find a match in the
361
- same layer as the given link. If no match was found in that layer,
364
+ If set to `auto` (default), Unpoly will try to find a match in the link's layer.
365
+ If no match was found in that layer,
362
366
  Unpoly will search in other layers, starting from the topmost layer.
367
+ @param {String} [up-fail-layer='auto']
368
+ The name of the layer that ought to be updated if the server sends a
369
+ non-200 status code.
363
370
  @param [up-history]
364
371
  Whether to push an entry to the browser history when following the link.
365
372
 
@@ -451,6 +451,7 @@ up.modal = (($) ->
451
451
  options.confirm = u.option(options.confirm, $link.attr('up-confirm'))
452
452
  options.method = up.link.followMethod($link, options)
453
453
  options.layer = 'modal'
454
+ options.failLayer = u.option(options.failLayer, $link.attr('up-fail-layer'), 'auto')
454
455
  animateOptions = up.motion.animateOptions(options, $link, duration: flavorDefault('openDuration', options.flavor), easing: flavorDefault('openEasing', options.flavor))
455
456
 
456
457
  # Although we usually fall back to full page loads if a browser doesn't support pushState,
@@ -246,6 +246,7 @@ up.popup = (($) ->
246
246
  options.confirm = u.option(options.confirm, $anchor.attr('up-confirm'))
247
247
  options.method = up.link.followMethod($anchor, options)
248
248
  options.layer = 'popup'
249
+ options.failLayer = u.option(options.failLayer, $anchor.attr('up-fail-layer'), 'auto')
249
250
  animateOptions = up.motion.animateOptions(options, $anchor, duration: config.openDuration, easing: config.openEasing)
250
251
 
251
252
  up.browser.whenConfirmed(options).then ->
@@ -129,10 +129,10 @@ up.syntax = (($) ->
129
129
  For instance, a container for a [Google Map](https://developers.google.com/maps/documentation/javascript/tutorial)
130
130
  might attach the location and names of its marker pins:
131
131
 
132
- <div class="google-map" up-data="[
133
- { lat: 48.36, lng: 10.99, title: 'Friedberg' },
134
- { lat: 48.75, lng: 11.45, title: 'Ingolstadt' }
135
- ]"></div>
132
+ <div class='google-map' up-data='[
133
+ { "lat": 48.36, "lng": 10.99, "title": "Friedberg" },
134
+ { "lat": 48.75, "lng": 11.45, "title": "Ingolstadt" }
135
+ ]'></div>
136
136
 
137
137
  The JSON will parsed and handed to your compiler as a second argument:
138
138
 
@@ -282,22 +282,32 @@ up.syntax = (($) ->
282
282
  value = if u.isString(compiler.keep) then compiler.keep else ''
283
283
  $jqueryElement.attr('up-keep', value)
284
284
  returnValue = compiler.callback.apply(nativeElement, [$jqueryElement, data($jqueryElement)])
285
- for destructor in discoverDestructors(returnValue)
286
- addDestructor($jqueryElement, destructor)
285
+ addDestructor($jqueryElement, returnValue)
287
286
 
288
- discoverDestructors = (returnValue) ->
287
+ ###*
288
+ Tries to find a list of destructors in a compiler's return value.
289
+
290
+ @param {Object} returnValue
291
+ @return {Function|undefined}
292
+ @internal
293
+ ###
294
+ normalizeDestructor = (returnValue) ->
289
295
  if u.isFunction(returnValue)
290
- [returnValue]
291
- else if u.isArray(returnValue) && u.all(returnValue, u.isFunction)
292
296
  returnValue
293
- else
294
- []
297
+ else if u.isArray(returnValue) && u.all(returnValue, u.isFunction)
298
+ u.sequence(returnValue...)
299
+
300
+ addDestructor = ($element, newDestructor) ->
301
+ if newDestructor = normalizeDestructor(newDestructor)
302
+ $element.addClass(DESTRUCTIBLE_CLASS)
303
+ # The initial destructor function is a function that removes the destructor class and data.
304
+ elementDestructor = $element.data(DESTRUCTORS_KEY) || -> removeDestructors($element)
305
+ elementDestructor = u.sequence(elementDestructor, newDestructor)
306
+ $element.data(DESTRUCTORS_KEY, elementDestructor)
295
307
 
296
- addDestructor = ($jqueryElement, destructor) ->
297
- $jqueryElement.addClass(DESTRUCTIBLE_CLASS)
298
- destructors = $jqueryElement.data(DESTRUCTORS_KEY) || []
299
- destructors.push(destructor)
300
- $jqueryElement.data(DESTRUCTORS_KEY, destructors)
308
+ removeDestructors = ($element) ->
309
+ $element.removeData(DESTRUCTORS_KEY)
310
+ $element.removeClass(DESTRUCTIBLE_CLASS)
301
311
 
302
312
  ###*
303
313
  Applies all compilers on the given element and its descendants.
@@ -317,10 +327,11 @@ up.syntax = (($) ->
317
327
  for compiler in queue
318
328
  $matches = u.findWithSelf($fragment, compiler.selector)
319
329
 
330
+ # Exclude all elements that are descendants of the subtrees we want to keep.
320
331
  $matches = $matches.filter ->
321
332
  $match = $(this)
322
- u.all $skipSubtrees, (element) ->
323
- $match.closest(element).length == 0
333
+ u.all $skipSubtrees, (skipSubtree) ->
334
+ $match.closest(skipSubtree).length == 0
324
335
 
325
336
  if $matches.length
326
337
  up.log.group ("Compiling '%s' on %d element(s)" unless compiler.isDefault), compiler.selector, $matches.length, ->
@@ -338,16 +349,23 @@ up.syntax = (($) ->
338
349
  @internal
339
350
  ###
340
351
  clean = ($fragment) ->
352
+ prepareClean($fragment)()
353
+
354
+ ###*
355
+ @function up.syntax.prepareClean
356
+ @param {jQuery} $fragment
357
+ @return {Function}
358
+ @internal
359
+ ###
360
+ prepareClean = ($fragment) ->
361
+ destructors = []
341
362
  u.findWithSelf($fragment, ".#{DESTRUCTIBLE_CLASS}").each ->
342
- $element = $(this)
343
- destructors = $element.data(DESTRUCTORS_KEY)
344
363
  # Although destructible elements should always have an array of destructors, we might be
345
364
  # destroying a clone of such an element. E.g. Unpoly creates a clone when keeping an
346
365
  # [up-keep] element, and that clone still has the .up-destructible class.
347
- if destructors
348
- destructor() for destructor in destructors
349
- $element.removeData(DESTRUCTORS_KEY)
350
- $element.removeClass(DESTRUCTIBLE_CLASS)
366
+ if destructor = $(this).data(DESTRUCTORS_KEY)
367
+ destructors.push(destructor)
368
+ u.sequence(destructors...)
351
369
 
352
370
  ###*
353
371
  Checks if the given element has an [`up-data`](/up-data) attribute.
@@ -359,7 +377,7 @@ up.syntax = (($) ->
359
377
 
360
378
  You have an element with JSON data serialized into an `up-data` attribute:
361
379
 
362
- <span class="person" up-data="{ age: 18, name: 'Bob' }">Bob</span>
380
+ <span class='person' up-data='{ "age": 18, "name": "Bob" }'>Bob</span>
363
381
 
364
382
  Calling `up.syntax.data()` will deserialize the JSON string into a JavaScript object:
365
383
 
@@ -382,10 +400,10 @@ up.syntax = (($) ->
382
400
  For instance, a container for a [Google Map](https://developers.google.com/maps/documentation/javascript/tutorial)
383
401
  might attach the location and names of its marker pins:
384
402
 
385
- <div class="google-map" up-data="[
386
- { lat: 48.36, lng: 10.99, title: 'Friedberg' },
387
- { lat: 48.75, lng: 11.45, title: 'Ingolstadt' }
388
- ]"></div>
403
+ <div class='google-map' up-data='[
404
+ { "lat": 48.36, "lng": 10.99, "title": "Friedberg" },
405
+ { "lat": 48.75, "lng": 11.45, "title": "Ingolstadt" }
406
+ ]'></div>
389
407
 
390
408
  The JSON will parsed and handed to your compiler as a second argument:
391
409
 
@@ -454,6 +472,7 @@ up.syntax = (($) ->
454
472
  macro: macro
455
473
  compile: compile
456
474
  clean: clean
475
+ prepareClean: prepareClean
457
476
  data: data
458
477
 
459
478
  )(jQuery)
@@ -28,14 +28,14 @@ up.util = (($) ->
28
28
  @internal
29
29
  ###
30
30
  memoize = (func) ->
31
- cache = undefined
31
+ cachedValue = undefined
32
32
  cached = false
33
33
  (args...) ->
34
34
  if cached
35
- cache
35
+ cachedValue
36
36
  else
37
37
  cached = true
38
- cache = func(args...)
38
+ cachedValue = func(args...)
39
39
 
40
40
  ###*
41
41
  Returns if the given port is the default port for the given protocol.
@@ -1775,6 +1775,9 @@ up.util = (($) ->
1775
1775
  delete object[key]
1776
1776
  value
1777
1777
 
1778
+ renameKey = (object, oldKey, newKey) ->
1779
+ object[newKey] = pluckKey(object, oldKey)
1780
+
1778
1781
  pluckData = (elementOrSelector, key) ->
1779
1782
  $element = $(elementOrSelector)
1780
1783
  value = $element.data(key)
@@ -1893,6 +1896,10 @@ up.util = (($) ->
1893
1896
 
1894
1897
  ###*
1895
1898
  @function up.util.sequence
1899
+ @param {Array<Function>} functions...
1900
+ @return {Function}
1901
+ A function that will call all `functions` if called.
1902
+
1896
1903
  @internal
1897
1904
  ###
1898
1905
  sequence = (functions...) ->
@@ -1940,7 +1947,7 @@ up.util = (($) ->
1940
1947
  ###*
1941
1948
  Like `$old.replaceWith($new)`, but keeps event handlers bound to `$old`.
1942
1949
 
1943
- Note that this is a memory leak unless you re-attach `$new` to the DOM aferwards.
1950
+ Note that this is a memory leak unless you re-attach `$old` to the DOM aferwards.
1944
1951
 
1945
1952
  @function up.util.detachWith
1946
1953
  @internal
@@ -1974,7 +1981,6 @@ up.util = (($) ->
1974
1981
  isTruthy = (object) ->
1975
1982
  !!object
1976
1983
 
1977
- isDetached: isDetached
1978
1984
  requestDataAsArray: requestDataAsArray
1979
1985
  requestDataAsQuery: requestDataAsQuery
1980
1986
  appendRequestData: appendRequestData
@@ -2071,6 +2077,7 @@ up.util = (($) ->
2071
2077
  error: fail
2072
2078
  pluckData: pluckData
2073
2079
  pluckKey: pluckKey
2080
+ renameKey: renameKey
2074
2081
  extractOptions: extractOptions
2075
2082
  isDetached: isDetached
2076
2083
  noop: noop
@@ -4,6 +4,6 @@ module Unpoly
4
4
  # The current version of the unpoly-rails gem.
5
5
  # This version number is also used for releases of the Unpoly
6
6
  # frontend code.
7
- VERSION = '0.36.2'
7
+ VERSION = '0.37.0'
8
8
  end
9
9
  end
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unpoly",
3
- "version": "0.36.2",
3
+ "version": "0.37.0",
4
4
  "description": "Unobtrusive JavaScript framework",
5
5
  "main": "dist/unpoly.js",
6
6
  "files": [
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- unpoly-rails (0.36.1)
4
+ unpoly-rails (0.36.2)
5
5
  rails (>= 3)
6
6
 
7
7
  GEM
@@ -1,3 +1,7 @@
1
1
  #= require jquery
2
2
  #= require jquery_ujs
3
3
  #= require unpoly
4
+
5
+ up.compiler '.compiler', ($element) ->
6
+ console.error('*** Compiler called for %o', $element.get(0))
7
+ -> console.error('*** Destructor called for %o', $element.get(0))
@@ -0,0 +1,5 @@
1
+ class ReplaceTestController < ApplicationController
2
+
3
+ layout 'integration_test'
4
+
5
+ end
@@ -43,4 +43,8 @@
43
43
  <li><%= link_to 'Error', '/error_test/trigger' %></li>
44
44
  </ul>
45
45
 
46
+ <ul>
47
+ <li><%= link_to 'Fragment update', '/replace_test/page1' %></li>
48
+ </ul>
49
+
46
50
  </div>
@@ -0,0 +1,6 @@
1
+ <ul>
2
+ <li><a href="page1" up-target="body">Link to page1, replacing <code>body</code></a></li>
3
+ <li><a href="page1" up-target=".content">Link to page1, replacing <code>.content</code></a></li>
4
+ <li><a href="page2" up-target="body">Link to page2, replacing <code>body</code></a></li>
5
+ <li><a href="page2" up-target=".content">Link to page2, replacing <code>.content</code></a></li>
6
+ </ul>
@@ -0,0 +1,14 @@
1
+ <div class="example">
2
+
3
+ <h1>Fragment update test</h1>
4
+
5
+ <p class="before">Before from page1</p>
6
+
7
+ <p class="content compiler">Content from page1</p>
8
+
9
+ <p class="before">After from page1</p>
10
+
11
+ <%= render 'nav' %>
12
+
13
+ </div>
14
+
@@ -0,0 +1,14 @@
1
+ <div class="example">
2
+
3
+ <h1>Fragment update test</h1>
4
+
5
+ <p class="before">Before from page2</p>
6
+
7
+ <p class="content compiler">Content from page2</p>
8
+
9
+ <p class="before">After from page2</p>
10
+
11
+ <%= render 'nav' %>
12
+
13
+ </div>
14
+
@@ -7,6 +7,7 @@ Rails.application.routes.draw do
7
7
  get 'css_test/:action', controller: 'css_test'
8
8
  get 'error_test/:action', controller: 'error_test'
9
9
  post 'error_test/:action', controller: 'error_test'
10
+ get 'replace_test/:action', controller: 'replace_test'
10
11
 
11
12
  namespace :form_test do
12
13
  resource :basic, only: [:new, :create]
@@ -3,6 +3,5 @@ beforeEach ->
3
3
  @titleBeforeExample = document.title
4
4
 
5
5
  afterEach ->
6
- if up.browser.canPushState()
7
- history.replaceState?({ fromResetPathHelper: true }, @titleBeforeExample, @hrefBeforeExample)
6
+ history.replaceState?({ fromResetPathHelper: true }, @titleBeforeExample, @hrefBeforeExample)
8
7
  document.title = @titleBeforeExample
@@ -47,6 +47,30 @@ describe 'up.dom', ->
47
47
  expect(resolution).toHaveBeenCalled()
48
48
  expect($('.middle')).toHaveText('new-middle')
49
49
 
50
+ describe 'cleaning up', ->
51
+
52
+ it 'calls destructors on the replaced element', ->
53
+ destructor = jasmine.createSpy('destructor')
54
+ up.compiler '.container', -> destructor
55
+ $container = affix('.container')
56
+ up.hello($container)
57
+ up.replace('.container', '/path')
58
+ @respondWith '<div class="container">new text</div>'
59
+ expect('.container').toHaveText('new text')
60
+ expect(destructor).toHaveBeenCalled()
61
+
62
+ it 'calls destructors when the replaced element is a singleton element like <body> (bugfix)', ->
63
+ # isSingletonElement() is true for body, but can't have the example replace the Jasmine test runner UI
64
+ up.dom.knife.mock('isSingletonElement').and.callFake ($element) -> $element.is('.container')
65
+ destructor = jasmine.createSpy('destructor')
66
+ up.compiler '.container', -> destructor
67
+ $container = affix('.container')
68
+ up.hello($container)
69
+ up.replace('.container', '/path')
70
+ @respondWith '<div class="container">new text</div>'
71
+ expect('.container').toHaveText('new text')
72
+ expect(destructor).toHaveBeenCalled()
73
+
50
74
  describe 'transitions', ->
51
75
 
52
76
  it 'returns a promise that will be resolved once the server response was received and the swap transition has completed', (done) ->
@@ -64,7 +88,7 @@ describe 'up.dom', ->
64
88
  done()
65
89
 
66
90
  it 'ignores a { transition } option when replacing the body element', (done) ->
67
- up.dom.knife.mock('swapBody') # can't have the example replace the Jasmine test runner UI
91
+ up.dom.knife.mock('swapSingletonElement') # can't have the example replace the Jasmine test runner UI
68
92
  up.dom.knife.mock('destroy') # if we don't swap the body, up.dom will destroy it
69
93
  replaceCallback = jasmine.createSpy()
70
94
  promise = up.replace('body', '/path', transition: 'cross-fade', duration: 50)
@@ -1201,6 +1225,50 @@ describe 'up.dom', ->
1201
1225
  Trigger.click($keeper)
1202
1226
  expect(handler).toHaveBeenCalled()
1203
1227
 
1228
+ it 'does not call destructors on a kept alement', ->
1229
+ handler = jasmine.createSpy('event handler')
1230
+ destructor = jasmine.createSpy('destructor')
1231
+ up.compiler '.keeper', ($keeper) ->
1232
+ return destructor
1233
+
1234
+ $container = affix('.container')
1235
+ $container.html """
1236
+ <div class="keeper" up-keep>old-text</div>
1237
+ """
1238
+ up.hello($container)
1239
+
1240
+ up.extract '.container', """
1241
+ <div class='container'>
1242
+ <div class="keeper" up-keep>new-text</div>
1243
+ </div>
1244
+ """
1245
+
1246
+ $keeper = $('.keeper')
1247
+ expect($keeper).toHaveText('old-text')
1248
+ expect(destructor).not.toHaveBeenCalled()
1249
+
1250
+ it 'calls destructors when a kept element is eventually removed from the DOM', ->
1251
+ handler = jasmine.createSpy('event handler')
1252
+ destructor = jasmine.createSpy('destructor')
1253
+ up.compiler '.keeper', ($keeper) ->
1254
+ return destructor
1255
+
1256
+ $container = affix('.container')
1257
+ $container.html """
1258
+ <div class="keeper" up-keep>old-text</div>
1259
+ """
1260
+ up.hello($container)
1261
+
1262
+ up.extract '.container', """
1263
+ <div class='container'>
1264
+ <div class="keeper">new-text</div>
1265
+ </div>
1266
+ """
1267
+
1268
+ $keeper = $('.keeper')
1269
+ expect($keeper).toHaveText('new-text')
1270
+ expect(destructor).toHaveBeenCalled()
1271
+
1204
1272
  it 'lets listeners cancel the keeping by preventing default on an up:fragment:keep event', ->
1205
1273
  $keeper = affix('.keeper[up-keep]').text('old-inside')
1206
1274
  $keeper.on 'up:fragment:keep', (event) -> event.preventDefault()
@@ -151,13 +151,15 @@ describe 'up.feedback', ->
151
151
  @respondWith('<div class="main">new-text</div>')
152
152
  expect($area).not.toHaveClass('up-active')
153
153
 
154
- it 'marks clicked modal openers as .up-active while the modal is loading', ->
154
+ it 'marks clicked modal openers as .up-active while the modal is loading', (done) ->
155
155
  $link = affix('a[href="/foo"][up-modal=".main"]')
156
156
  affix('.main')
157
157
  Trigger.clickSequence($link)
158
158
  expect($link).toHaveClass('up-active')
159
- @respondWith('<div class="main">new-text</div>')
160
- expect($link).not.toHaveClass('up-active')
159
+ u.nextFrame =>
160
+ @respondWith('<div class="main">new-text</div>')
161
+ expect($link).not.toHaveClass('up-active')
162
+ done()
161
163
 
162
164
  it 'removes .up-active from a clicked modal opener if the target is already preloaded (bugfix)', ->
163
165
  $link = affix('a[href="/foo"][up-modal=".main"]')