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.