lib/assets/javascripts/unpoly/dom.coffee.erb in unpoly-rails-0.55.1 vs lib/assets/javascripts/unpoly/dom.coffee.erb in unpoly-rails-0.56.0
- old
+ new
@@ -40,13 +40,14 @@
reset = ->
config.reset()
setSource = (element, sourceUrl) ->
- $element = $(element)
- sourceUrl = u.normalizeUrl(sourceUrl) if u.isPresent(sourceUrl)
- $element.attr("up-source", sourceUrl)
+ unless sourceUrl is false
+ $element = $(element)
+ sourceUrl = u.normalizeUrl(sourceUrl) if u.isPresent(sourceUrl)
+ $element.attr("up-source", sourceUrl)
###**
Returns the URL the given element was retrieved from.
@method up.dom.source
@@ -392,11 +393,11 @@
updateHistoryAndTitle(options)
swapPromises = []
for step in extractSteps
- up.log.group 'Updating %s', step.selector, ->
+ up.log.group 'Swapping fragment %s', step.selector, ->
# Note that we must copy the options hash instead of changing it in-place, since the
# async swapElements() is scheduled for the next microtask and we must not change the options
# for the previous iteration.
swapOptions = u.merge(options, u.only(step, 'origin', 'reveal'))
@@ -472,15 +473,17 @@
document.title = options.title if u.isString(options.title)
swapElements = ($old, $new, pseudoClass, transition, options) ->
transition ||= 'none'
+ # When the server responds with an error, or when the request method is not
+ # reloadable (not GET), we keep the same source as before.
if options.source == 'keep'
options = u.merge(options, source: source($old))
- # Ensure that all transitions and animations have completed.
- up.motion.finish($old)
+ # Remember where the element came from in case someone needs to up.reload($new) later.
+ setSource($new, options.source)
if pseudoClass
# Text nodes are wrapped in a .up-insertion container so we can
# animate them and measure their position/size for scrolling.
# This is not possible for container-less text nodes.
@@ -505,78 +508,52 @@
promise = promise.then -> up.animate($wrapper, transition, options)
# Remove the wrapper now that is has served it purpose
promise = promise.then -> u.unwrapElement($wrapper)
+ return promise
+
else if keepPlan = findKeepPlan($old, $new, options)
# Since we're keeping the element that was requested to be swapped,
- # there is nothing left to do here.
+ # there is nothing left to do here, except notify event listeners.
emitFragmentKept(keepPlan)
- promise = Promise.resolve()
+ return Promise.resolve()
else
# This needs to happen before prepareClean() below.
+ # Otherwise we would collect destructors for elements we want to keep.
options.keepPlans = transferKeepableElements($old, $new, options)
- # Collect destructor functions before swapping the elements.
- # Detaching an element from the DOM will cause jQuery to remove the data properties
- # where we store constructor functions.
- clean = up.syntax.prepareClean($old)
+ $parent = $old.parent()
- replacement = ->
- if shouldSwapElementsDirectly($old, $new, transition, options)
- # jQuery will actually let us .insertBefore the new <body> tag,
- # but that's probably bad Karma.
- swapElementsDirectly($old, $new)
- # We cannot morph the <body> tag. Also in case we have found that we won't
- # animate above, save ourselves from reepeating the expensive check in up.morph().
- transition = false
- else
- # Don't insert the new element after the old element. For some reason
- # this will make the browser scroll to the bottom of the new element.
- $new.insertBefore($old)
+ morphOptions = u.merge options,
+ afterInsert: ->
+ up.hello($new, options)
+ markElementAsDestroying($old)
+ emitFragmentDestroy($old, log: false)
+ beforeDetach: ->
+ up.syntax.clean($old)
+ afterDetach: ->
+ $old.remove() # clean up jQuery data
+ emitFragmentDestroyed($old, $parent: $parent, log: false)
- # Remember where the element came from so we can
- # offer reload functionality.
- setSource($new, options.source) unless options.source is false
+ return up.morph($old, $new, transition, morphOptions)
- autofocus($new)
-
- # The fragment should be compiled before animating,
- # so transitions see .up-current classes
- hello($new, options)
-
- # Morphing will also process options.reveal
- up.morph($old, $new, transition, options)
-
- # Wrap the replacement as a destroy animation, so $old will
- # get marked as .up-destroying right away.
- promise = destroy($old, { clean, beforeWipe: replacement, log: false })
-
- promise
-
- shouldSwapElementsDirectly = ($old, $new, transition, options) ->
- $both = $old.add($new)
- $old.is('body') || !up.motion.willAnimate($both, transition, options)
-
- # This is a separate method so we can mock it in specs
- swapElementsDirectly = ($old, $new) ->
- # jQuery will actually let us .insertBefore the new <body> tag,
- # but that's probably bad Karma.
- $old.replaceWith($new)
-
+ # This will find all [up-keep] descendants in $old an, overwrite their partner
+ # element in $new and leave a visually identical clone in $old for a later transition.
+ # Returns an array of keepPlans.
transferKeepableElements = ($old, $new, options) ->
keepPlans = []
if options.keep
for keepable in $old.find('[up-keep]')
$keepable = $(keepable)
if plan = findKeepPlan($keepable, $new, u.merge(options, descendantsOnly: true))
# Replace $keepable with its clone so it looks good in a transition between
# $old and $new. Note that $keepable will still point to the same element
# after the replacement, which is now detached.
$keepableClone = $keepable.clone()
- up.util.detachWith($keepable, $keepableClone)
+ u.detachWith($keepable, $keepableClone)
# $keepable.replaceWith($keepableClone)
# Since we're going to swap the entire $old and $new containers afterwards,
# replace the matching element with $keepable so it will eventually return to the DOM.
plan.$newElement.replaceWith($keepable)
@@ -741,31 +718,39 @@
@event up:fragment:inserted
@param {jQuery} event.$element
The fragment that has been inserted or updated.
@stable
###
- emitFragmentInserted = (fragment, options) ->
- $fragment = $(fragment)
+ emitFragmentInserted = ($element, options) ->
up.emit 'up:fragment:inserted',
- $element: $fragment
- message: ['Inserted fragment %o', $fragment.get(0)]
+ $element: $element
+ message: ['Inserted fragment %o', $element.get(0)]
origin: options.origin
emitFragmentKept = (keepPlan) ->
eventAttrs = u.merge(keepPlan, message: ['Kept fragment %o', keepPlan.$element.get(0)])
up.emit('up:fragment:kept', eventAttrs)
- autofocus = ($element) ->
- selector = '[autofocus]:last'
- $control = u.selectInSubtree($element, selector)
- if $control.length && $control.get(0) != document.activeElement
- $control.focus()
+ emitFragmentDestroy = ($element, options) ->
+ if shouldLogDestruction($element, options)
+ message = ['Destroying fragment %o', $element.get(0)]
+ up.emit 'up:fragment:destroy', { $element, message }
+ emitFragmentDestroyed = ($element, options) ->
+ if shouldLogDestruction($element, options)
+ message = ['Destroyed fragment %o', $element.get(0)]
+
+ $parent = options.$parent or up.fail("Missing { $parent } option")
+
+ up.emit 'up:fragment:destroyed',
+ $target: $parent,
+ $parent: $parent,
+ $element: $element,
+ message: message
+
isRealElement = ($element) ->
- unreal = '.up-ghost, .up-destroying'
- # Closest matches both the element itself
- # as well as its ancestors
+ unreal = '.up-destroying'
$element.closest(unreal).length == 0
###**
Returns the first element matching the given selector, but
ignores elements that are being [destroyed](/up.destroy) or [transitioned](/up.morph).
@@ -865,45 +850,37 @@
###
destroy = (selectorOrElement, options) ->
$element = $(selectorOrElement)
options = u.options(options, animation: false)
- if shouldLogDestruction($element, options)
- destroyMessage = ['Destroying fragment %o', $element.get(0)]
- destroyedMessage = ['Destroyed fragment %o', $element.get(0)]
-
if $element.length == 0
- Promise.resolve()
- else
- up.emit 'up:fragment:destroy', $element: $element, message: destroyMessage
- $element.addClass('up-destroying')
- # If e.g. a modal or popup asks us to restore a URL, do this
- # before emitting `fragment:destroy`. This way up.navigate sees the
- # new URL and can assign/remove .up-current classes accordingly.
- updateHistoryAndTitle(options)
+ return Promise.resolve()
- animate = ->
- animateOptions = up.motion.animateOptions(options)
- up.motion.animate($element, options.animation, animateOptions)
+ markElementAsDestroying($element)
+ emitFragmentDestroy($element, options)
- beforeWipe = options.beforeWipe || Promise.resolve()
+ updateHistoryAndTitle(options)
- wipe = ->
- options.clean ||= -> up.syntax.clean($element)
- options.clean()
- up.syntax.clean($element)
- # Emit this while $element is still part of the DOM, so event
- # listeners bound to the document will receive the event.
- up.emit 'up:fragment:destroyed', $element: $element, message: destroyedMessage
- $element.remove()
+ animate = ->
+ animateOptions = up.motion.animateOptions(options)
+ up.motion.animate($element, options.animation, animateOptions)
- animate().then(beforeWipe).then(wipe)
+ wipe = ->
+ $parent = $element.parent()
+ up.syntax.clean($element)
+ $element.remove()
+ emitFragmentDestroyed($element, { $parent })
+ animate().then(wipe)
+
shouldLogDestruction = ($element, options) ->
# Don't log destruction for elements that are either Unpoly internals or frequently destroyed
options.log != false && !$element.is('.up-placeholder, .up-tooltip, .up-modal, .up-popup')
+ markElementAsDestroying = ($element) ->
+ $element.addClass('up-destroying')
+
###**
Before a page fragment is being [destroyed](/up.destroy), this
event is [emitted](/up.emit) on the fragment.
If the destruction is animated, this event is emitted before the
@@ -914,18 +891,20 @@
The page fragment that is about to be destroyed.
@stable
###
###**
- This event is [emitted](/up.emit) right before a [destroyed](/up.destroy)
- page fragment is removed from the DOM.
+ This event is [emitted](/up.emit) after a page fragment was [destroyed](/up.destroy) and removed from the DOM.
- If the destruction is animated, this event is emitted after
- the animation has ended.
+ If the destruction is animated, this event is emitted after the animation has ended.
+ The event is emitted on the parent element of the fragment that was removed.
+
@event up:fragment:destroyed
@param {jQuery} event.$element
- The page fragment that is about to be removed from the DOM.
+ The page fragment that has been removed from the DOM.
+ @param {jQuery} event.$parent
+ The parent element of the fragment that has been removed from the DOM.
@stable
###
###**
Replaces the given element with a fresh copy fetched from the server.