###** Fragment update API =================== The `up.fragment` module exposes a high-level Javascript API to [update](/up.replace) or [destroy](/up.destroy) page fragments. Fragments are [compiled](/up.compiler) elements that can be updated from a server URL. They also exist on a layer (page, modal, popup). Most of Unpoly's functionality (like [fragment links](/up.link) or [modals](/up.modal)) is built from `up.fragment` functions. You may use them to extend Unpoly from your [custom Javascript](/up.syntax). @module up.fragment ### up.fragment = do -> u = up.util e = up.element ###** Configures defaults for fragment insertion. @property up.fragment.config @param {string} [options.fallbacks=['body']] When a fragment updates cannot find the requested element, Unpoly will try this list of alternative selectors. The first selector that matches an element in the current page (or response) will be used. If the response contains none of the selectors, an error message will be shown. It is recommend to always keep `'body'` as the last selector in the last in the case your server or load balancer renders an error message that does not contain your application layout. @param {string} [options.fallbackTransition=null] The transition to use when using a [fallback target](/#options.fallbacks). By default this is not set and the original replacement's transition is used. @stable ### config = new up.Config fallbacks: ['body'] fallbackTransition: null reset = -> config.reset() setSource = (element, sourceUrl) -> unless sourceUrl is false sourceUrl = u.normalizeUrl(sourceUrl) if u.isPresent(sourceUrl) element.setAttribute("up-source", sourceUrl) ###** Returns the URL the given element was retrieved from. @method up.fragment.source @param {string|Element|jQuery} selectorOrElement @experimental ### source = (selectorOrElement) -> element = e.get(selectorOrElement) if element = e.closest(element, '[up-source]') element.getAttribute("up-source") else up.browser.url() ###** Replaces elements on the current page with corresponding elements from a new page fetched from the server. The current and new elements must both match the given CSS selector. The unobtrusive variant of this is the [`a[up-target]`](/a-up-target) selector. \#\#\# Example Let's say your current HTML looks like this:
old one
old two
We now replace the second `
`: up.replace('.two', '/new') The server renders a response for `/new`:
new one
new two
Unpoly looks for the selector `.two` in the response and [implants](/up.extract) it into the current page. The current page now looks like this:
old one
new two
Note how only `.two` has changed. The update for `.one` was discarded, since it didn't match the selector. \#\#\# Appending or prepending instead of replacing By default Unpoly will replace the given selector with the same selector from a freshly fetched page. Instead of replacing you can *append* the loaded content to the existing content by using the `:after` pseudo selector. In the same fashion, you can use `:before` to indicate that you would like the *prepend* the loaded content. A practical example would be a paginated list of items: In order to append more items from a URL, replace into the `.tasks:after` selector: up.replace('.tasks:after', '/page/2') \#\#\# Setting the window title from the server If the `replace` call changes history, the document title will be set to the contents of a `` tag in the response. The server can also change the document title by setting an `X-Up-Title` header in the response. \#\#\# Optimizing response rendering The server is free to optimize Unpoly requests by only rendering the HTML fragment that is being updated. The request's `X-Up-Target` header will contain the CSS selector for the updating fragment. If you are using the `unpoly-rails` gem you can also access the selector via `up.target` in all controllers, views and helpers. \#\#\# Events Unpoly will emit [`up:fragment:destroyed`](/up:fragment:destroyed) on the element that was replaced and [`up:fragment:inserted`](/up:fragment:inserted) on the new element that replaces it. @function up.replace @param {string|Element|jQuery} selectorOrElement The CSS selector to update. You can also pass a DOM element or jQuery element here, in which case a selector will be inferred from the element's class and ID. @param {string} url The URL to fetch from the server. @param {string} [options.failTarget] The CSS selector to update if the server sends a non-200 status code. @param {string} [options.fallback] The selector to update when the original target was not found in the page. @param {string} [options.title] The document title after the replacement. If the call pushes an history entry and this option is missing, the title is extracted from the response's `<title>` tag. You can also pass `false` to explicitly prevent the title from being updated. @param {string} [options.method='get'] The HTTP method to use for the request. @param {Object|FormData|string|Array} [options.params] [Parameters](/up.Params) that should be sent as the request's payload. @param {string} [options.transition='none'] @param {string|boolean} [options.history=true] If a string is given, it is used as the URL the browser's location bar and history. If omitted or true, the `url` argument will be used. If set to `false`, the history will remain unchanged. @param {boolean|string} [options.source=true] @param {boolean|string} [options.reveal=false] Whether to [reveal](/up.reveal) the new fragment. You can also pass a CSS selector for the element to reveal. @param {boolean|string} [options.failReveal=false] Whether to [reveal](/up.reveal) the new fragment when the server responds with an error. You can also pass a CSS selector for the element to reveal. @param {number} [options.revealPadding] @param {boolean} [options.restoreScroll=false] If set to true, Unpoly will try to restore the scroll position of all the viewports around or below the updated element. The position will be reset to the last known top position before a previous history change for the current URL. @param {boolean} [options.cache] Whether to use a [cached response](/up.proxy) if available. @param {string} [options.historyMethod='push'] @param {Object} [options.headers={}] An object of additional header key/value pairs to send along with the request. @param {Element|jQuery} [options.origin] The element that triggered the replacement. The element's selector will be substituted for the `&` shorthand in the target selector ([like in Sass](https://sass-lang.com/documentation/file.SASS_REFERENCE.html#parent-selector)). @param {string} [options.layer='auto'] The name of the layer that ought to be updated. Valid values are `'auto'`, `'page'`, `'modal'` and `'popup'`. If set to `'auto'` (default), Unpoly will try to find a match in the same layer as the element that triggered the replacement (see `options.origin`). If that element is not known, or no match was found in that layer, Unpoly will search in other layers, starting from the topmost layer. @param {string} [options.failLayer='auto'] The name of the layer that ought to be updated if the server sends a non-200 status code. @param {boolean} [options.keep=true] Whether this replacement will preserve [`[up-keep]`](/up-keep) elements. @param {boolean} [options.hungry=true] Whether this replacement will update [`[up-hungry]`](/up-hungry) elements. @return {Promise} A promise that will be fulfilled when the page has been updated. @stable ### replace = (selectorOrElement, url, options) -> options = u.options(options) options.inspectResponse = fullLoad = -> up.browser.navigate(url, u.only(options, 'method', 'params')) if !up.browser.canPushState() && options.history != false fullLoad() unless options.preload return u.unresolvablePromise() successOptions = u.merge options, humanizedTarget: 'target' failureOptions = u.merge options, humanizedTarget: 'failure target' provideTarget: undefined # don't provide a target if we're targeting the failTarget restoreScroll: false u.renameKey(failureOptions, 'failTransition', 'transition') u.renameKey(failureOptions, 'failLayer', 'layer') u.renameKey(failureOptions, 'failReveal', 'reveal') try improvedTarget = bestPreflightSelector(selectorOrElement, successOptions) improvedFailTarget = bestPreflightSelector(options.failTarget, failureOptions) catch error # Since we're an async function, we should not throw exceptions but return a rejected promise. # http://2ality.com/2016/03/promise-rejections-vs-exceptions.html return Promise.reject(error) requestAttrs = u.only options, 'method', 'data', # deprecated 'params', 'cache', 'preload', 'headers', 'timeout' u.assign requestAttrs, url: url target: improvedTarget failTarget: improvedFailTarget request = new up.Request(requestAttrs) onSuccess = (response) -> processResponse(true, improvedTarget, request, response, successOptions) onFailure = (response) -> rejection = -> Promise.reject(response) if response.isFatalError() rejection() else promise = processResponse(false, improvedFailTarget, request, response, failureOptions) # Although processResponse() we will perform a successful replacement of options.failTarget, # we still want to reject the promise that's returned to our API client. u.always(promise, rejection) promise = up.request(request) promise = promise.then(onSuccess, onFailure) unless options.preload promise ###** @internal ### processResponse = (isSuccess, selector, request, response, options) -> sourceUrl = response.url historyUrl = sourceUrl if hash = request.hash options.hash = hash historyUrl += hash isReloadable = (response.method == 'GET') if isSuccess if isReloadable # e.g. GET returns 200 OK options.history = historyUrl unless options.history is false || u.isString(options.history) options.source = sourceUrl unless options.source is false || u.isString(options.source) else # e.g. POST returns 200 OK # We allow the developer to pass GETable URLs as { history } and { source } options. options.history = false unless u.isString(options.history) options.source = 'keep' unless u.isString(options.source) else if isReloadable # e.g. GET returns 500 Internal Server Error options.history = historyUrl unless options.history is false options.source = sourceUrl unless options.source is false else # e.g. POST returns 500 Internal Server Error options.history = false options.source = 'keep' if shouldExtractTitle(options) && response.title options.title = response.title extract(selector, response.text, options) shouldExtractTitle = (options) -> not (options.title is false || u.isString(options.title) || (options.history is false && options.title isnt true)) ###** Updates a selector on the current page with the same selector from the given HTML string. \#\#\# Example Let's say your current HTML looks like this: <div class="one">old one</div> <div class="two">old two</div> We now replace the second `<div>`, using an HTML string as the source: html = '<div class="one">new one</div>' + '<div class="two">new two</div>'; up.extract('.two', html) Unpoly looks for the selector `.two` in the strings and updates its contents in the current page. The current page now looks like this: <div class="one">old one</div> <div class="two">new two</div> Note how only `.two` has changed. The update for `.one` was discarded, since it didn't match the selector. @function up.extract @param {string|Element|jQuery} selectorOrElement @param {string} html @param {Object} [options] See options for [`up.replace()`](/up.replace). @return {Promise} A promise that will be fulfilled then the selector was updated and all animation has finished. @stable ### extract = (selectorOrElement, html, options) -> up.log.group 'Extracting %s from %d bytes of HTML', selectorOrElement, html?.length, -> options = u.options options, historyMethod: 'push' keep: true layer: 'auto' up.viewport.saveScroll() unless options.saveScroll == false u.rejectOnError -> # Allow callers to create the targeted element right before we swap. options.provideTarget?() responseDoc = new up.HtmlParser(html) extractSteps = bestMatchingSteps(selectorOrElement, responseDoc, options) if shouldExtractTitle(options) && responseTitle = responseDoc.title() options.title = responseTitle updateHistoryAndTitle(options) swapPromises = [] for step in extractSteps 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')) responseDoc.prepareForInsertion(step.newElement) swapPromise = swapElements(step.oldElement, step.newElement, step.pseudoClass, step.transition, swapOptions) swapPromises.push(swapPromise) # Delay all further links in the promise chain until all fragments have been swapped Promise.all(swapPromises) bestPreflightSelector = (selectorOrElement, options) -> cascade = new up.ExtractCascade(selectorOrElement, options) cascade.bestPreflightSelector() bestMatchingSteps = (selectorOrElement, response, options) -> options = u.merge(options, { response }) cascade = new up.ExtractCascade(selectorOrElement, options) cascade.bestMatchingSteps() updateHistoryAndTitle = (options) -> options = u.options(options, historyMethod: 'push') up.history[options.historyMethod](options.history) if options.history document.title = options.title if u.isString(options.title) swapElements = (oldElement, newElement, 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(oldElement)) # Remember where the element came from in case someone needs to up.reload(newElement) later. setSource(newElement, 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. wrapper = e.createFromSelector('.up-insertion') while childNode = newElement.firstChild wrapper.appendChild(childNode) # Note that since we're prepending/appending instead of replacing, # newElement will not actually be inserted into the DOM, only its children. if pseudoClass == 'before' oldElement.insertAdjacentElement('afterbegin', wrapper) else oldElement.insertAdjacentElement('beforeend', wrapper) for child in wrapper.children hello(child, options) # Reveal element that was being prepended/appended. # Since we will animate (not morph) it's OK to allow animation of scrolling # if options.scrollBehavior is given. promise = up.viewport.scrollAfterInsertFragment(wrapper, options) # Since we're adding content instead of replacing, we'll only # animate newElement instead of morphing between oldElement and newElement promise = u.always(promise, up.animate(wrapper, transition, options)) # Remove the wrapper now that is has served it purpose promise = promise.then -> e.unwrap(wrapper) return promise else if keepPlan = findKeepPlan(oldElement, newElement, options) # Since we're keeping the element that was requested to be swapped, # there is nothing left to do here, except notify event listeners. emitFragmentKept(keepPlan) 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(oldElement, newElement, options) parent = oldElement.parentNode morphOptions = u.merge options, beforeStart: -> markElementAsDestroying(oldElement) afterInsert: -> up.hello(newElement, options) beforeDetach: -> up.syntax.clean(oldElement) afterDetach: -> e.remove(oldElement) # clean up jQuery data emitFragmentDestroyed(oldElement, parent: parent, log: false) return up.morph(oldElement, newElement, transition, morphOptions) # This will find all [up-keep] descendants in oldElement, overwrite their partner # element in newElement and leave a visually identical clone in oldElement for a later transition. # Returns an array of keepPlans. transferKeepableElements = (oldElement, newElement, options) -> keepPlans = [] if options.keep for keepable in oldElement.querySelectorAll('[up-keep]') if plan = findKeepPlan(keepable, newElement, u.merge(options, descendantsOnly: true)) # plan.oldElement is now keepable # Replace keepable with its clone so it looks good in a transition between # oldElement and newElement. Note that keepable will still point to the same element # after the replacement, which is now detached. keepableClone = keepable.cloneNode(true) e.replace(keepable, keepableClone) # Since we're going to swap the entire oldElement and newElement containers afterwards, # replace the matching element with keepable so it will eventually return to the DOM. e.replace(plan.newElement, keepable) keepPlans.push(plan) keepPlans findKeepPlan = (element, newElement, options) -> if options.keep keepable = element if partnerSelector = e.booleanOrStringAttr(keepable, 'up-keep') u.isString(partnerSelector) or partnerSelector = '&' partnerSelector = e.resolveSelector(partnerSelector, keepable) if options.descendantsOnly partner = e.first(newElement, partnerSelector) else partner = e.subtree(newElement, partnerSelector)[0] if partner && e.matches(partner, '[up-keep]') plan = oldElement: keepable # the element that should be kept newElement: partner # the element that would have replaced it but now does not newData: up.syntax.data(partner) # the parsed up-data attribute of the element we will discard keepEventArgs = target: keepable newFragment: partner newData: plan.newData log: ['Keeping element %o', keepable] if up.event.nobodyPrevents('up:fragment:keep', keepEventArgs) plan ###** Elements with an `up-keep` attribute will be persisted during [fragment updates](/a-up-target). For example: <audio up-keep src="song.mp3"></audio> The element you're keeping should have an umambiguous class name, ID or `up-id` attribute so Unpoly can find its new position within the page update. Emits events [`up:fragment:keep`](/up:fragment:keep) and [`up:fragment:kept`](/up:fragment:kept). \#\#\# Controlling if an element will be kept Unpoly will **only** keep an existing element if: - The existing element has an `up-keep` attribute - The response contains an element matching the CSS selector of the existing element - The matching element *also* has an `up-keep` attribute - The [`up:fragment:keep`](/up:fragment:keep) event that is [emitted](/up.emit) on the existing element is not prevented by a event listener. Let's say we want only keep an `<audio>` element as long as it plays the same song (as identified by the tag's `src` attribute). On the client we can achieve this by listening to an `up:keep:fragment` event and preventing it if the `src` attribute of the old and new element differ: up.compiler('audio', function(element) { element.addEventListener('up:fragment:keep', function(event) { if element.getAttribute('src') !== event.newElement.getAttribute('src') { event.preventDefault() } }) }) If we don't want to solve this on the client, we can achieve the same effect on the server. By setting the value of the `up-keep` attribute we can define the CSS selector used for matching elements. <audio up-keep="audio[src='song.mp3']" src="song.mp3"></audio> Now, if a response no longer contains an `<audio src="song.mp3">` tag, the existing element will be destroyed and replaced by a fragment from the response. @selector [up-keep] @stable ### ###** This event is [emitted](/up.emit) before an existing element is [kept](/up-keep) during a page update. Event listeners can call `event.preventDefault()` on an `up:fragment:keep` event to prevent the element from being persisted. If the event is prevented, the element will be replaced by a fragment from the response. @event up:fragment:keep @param event.preventDefault() Event listeners may call this method to prevent the element from being preserved. @param {Element} event.target The fragment that will be kept. @param {Element} event.newFragment The discarded element. @param {Object} event.newData The value of the [`up-data`](/up-data) attribute of the discarded element, parsed as a JSON object. @stable ### ###** This event is [emitted](/up.emit) when an existing element has been [kept](/up-keep) during a page update. Event listeners can inspect the discarded update through `event.newElement` and `event.newData` and then modify the preserved element when necessary. @event up:fragment:kept @param {Element} event.target The fragment that has been kept. @param {Element} event.newFragment The discarded fragment. @param {Object} event.newData The value of the [`up-data`](/up-data) attribute of the discarded fragment, parsed as a JSON object. @stable ### ###** Compiles a page fragment that has been inserted into the DOM by external code. **As long as you manipulate the DOM using Unpoly, you will never need to call this method.** You only need to use `up.hello()` if the DOM is manipulated without Unpoly' involvement, e.g. by setting the `innerHTML` property or calling jQuery methods like `html`, `insertAfter` or `appendTo`: element = document.createElement('div') element.innerHTML = '... HTML that needs to be activated ...' up.hello(element) This function emits the [`up:fragment:inserted`](/up:fragment:inserted) event. @function up.hello @param {string|Element|jQuery} selectorOrElement @param {string|Element|jQuery} [options.origin] @param {string|Element|jQuery} [options.kept] @return {Element} The compiled element @stable ### hello = (selectorOrElement, options) -> element = e.get(selectorOrElement) options = u.options(options, keepPlans: []) keptElements = [] for plan in options.keepPlans emitFragmentKept(plan) keptElements.push(plan.oldElement) up.syntax.compile(element, skip: keptElements) emitFragmentInserted(element, options) element ###** When any page fragment has been [inserted or updated](/up.replace), this event is [emitted](/up.emit) on the fragment. If you're looking to run code when a new fragment matches a selector, use `up.compiler()` instead. \#\#\# Example up.on('up:fragment:inserted', function(event, fragment) { console.log("Looks like we have a new %o!", fragment) }) @event up:fragment:inserted @param {Element} event.target The fragment that has been inserted or updated. @stable ### emitFragmentInserted = (element, options) -> up.emit element, 'up:fragment:inserted', log: ['Inserted fragment %o', element] origin: options.origin emitFragmentKept = (keepPlan) -> keptElement = keepPlan.oldElement eventAttrs = target: keptElement, newFragment: keepPlan.newElement newData: keepPlan.newData log: ['Kept fragment %o', keptElement] up.emit('up:fragment:kept', eventAttrs) emitFragmentDestroyed = (fragment, options) -> if shouldLogDestruction(fragment, options) log = ['Destroyed fragment %o', fragment] parent = options.parent or up.fail("Missing { parent } option") up.emit(parent, 'up:fragment:destroyed', { fragment, parent, log }) isRealElement = (element) -> !e.closest(element, '.up-destroying') ###** Returns the first element matching the given selector, but ignores elements that are being [destroyed](/up.destroy) or that are being removed by a [transition](/up.morph). Returns `undefined` if no element matches these conditions. \#\#\# Example To select the first element with the selector `.foo`: var fooInModal = up.fragment.first('.foo') You may also pass a `{ layer }` option to only match elements witin a layer: var fooInModal = up.fragment.first('.foo', { layer: 'modal' }) You may also pass a root element as a first argument: var container = up.fragment.first('.container') var fooInContainer = up.fragment.first(container, '.foo') \#\#\# Similar features - The [`.up-destroying`](/up-destroying) class is assigned to elements during their removal animation. - The [`up.element.first()`](/up.element.first) function simply returns the first element matching a selector without further filtering. @function up.fragment.first @param {Element|jQuery} [root=document] The root element for the search. Only the root's children will be matched. May be omitted to search through all elements in the `document`. @param {string} selector The selector to match @param {string} [options.layer='auto'] The name of the layer in which to find the element. Valid values are `'auto'`, `'page'`, `'modal'` and `'popup'`. @param {string|Element|jQuery} [options.origin] An second element or selector that can be referenced as `&` in the first selector: var input = document.querySelector('input.email') up.fragment.first('fieldset:has(&)', { origin: input }) // returns the <fieldset> containing input @return {Element|undefined} The first element that is neither a ghost or being destroyed, or `undefined` if no such element was found. @experimental ### first = (args...) -> options = u.extractOptions(args) selector = args.pop() root = args[0] || document layer = options.layer ? 'auto' origin = options.origin selector = e.resolveSelector(selector, origin) if layer == 'auto' firstInPriority(root, selector, origin) else firstInLayer(root, selector, layer) firstInPriority = (parent, selector, origin) -> layers = ['popup', 'modal', 'page'] if origin originLayer = layerOf(origin) # Make the origin's layer the top priority u.remove(layers, originLayer) layers.unshift(originLayer) u.findResult layers, (layer) -> return firstInLayer(parent, selector, layer) firstInLayer = (parent, selector, layer) -> elements = e.all(parent, selector) return u.findResult elements, (element) -> if isRealElement(element) && matchesLayer(element, layer) return element ###** @function up.fragment.layerOf @internal ### layerOf = (element) -> if up.popup.contains(element) 'popup' else if up.modal.contains(element) 'modal' else 'page' matchesLayer = (element, layer) -> !layer || layerOf(element) == layer ###** @function up.fragment.createPlaceHolder @internal ### createPlaceholder = (selector, container = document.body) -> e.affix(container, selector, class: 'up-placeholder') ###** Destroys the given element or selector. Takes care that all [`up.compiler()`](/up.compiler) destructors, if any, are called. The element is removed from the DOM. Note that if you choose to animate the element removal using `options.animate`, the element won't be removed until after the animation has completed. Emits events [`up:fragment:destroyed`](/up:fragment:destroyed). @function up.destroy @param {string|Element|jQuery} selectorOrElement @param {string} [options.history] A URL that will be pushed as a new history entry when the element begins destruction. @param {string} [options.title] The document title to set when the element begins destruction. @param {string|Function(element, options): Promise} [options.animation='none'] The animation to use before the element is removed from the DOM. @param {number} [options.duration] The duration of the animation. See [`up.animate()`](/up.animate). @param {number} [options.delay] The delay before the animation starts. See [`up.animate()`](/up.animate). @param {string} [options.easing] The timing function that controls the animation's acceleration. [`up.animate()`](/up.animate). @return {Promise} A promise that will be fulfilled once the element has been removed from the DOM. @stable ### destroy = (selectorOrElement, options) -> element = e.get(selectorOrElement) options = u.options(options, animation: false) unless element return Promise.resolve() markElementAsDestroying(element) updateHistoryAndTitle(options) animate = -> animateOptions = up.motion.animateOptions(options) up.motion.animate(element, options.animation, animateOptions) wipe = -> parent = element.parentNode up.syntax.clean(element) if up.browser.canJQuery() jQuery(element).remove() else e.remove(element) emitFragmentDestroyed(element, { parent: parent, log: options.log }) animate().then(wipe) shouldLogDestruction = (element, options) -> # Don't log destruction for elements that are either Unpoly internals or frequently destroyed options.log != false && !e.matches(element, '.up-placeholder, .up-tooltip, .up-modal, .up-popup') ###** Elements are assigned the `.up-destroying` class before they are [destroyed](/up.destroy) or while they are being removed by a [transition](/up.morph). If the removal is animated, the class is assigned before the animation starts. To select an element while ignoring elements that are being destroyed, see the [`up.fragment.first()`](/up.fragment.first) function. @selector .up-destroying @stable ### markElementAsDestroying = (element) -> element.classList.add('up-destroying') element.setAttribute('aria-hidden', 'true') ###** 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. The event is emitted on the parent element of the fragment that was removed. @event up:fragment:destroyed @param {Element} event.fragment The detached element that has been removed from the DOM. @param {Element} event.parent The former parent element of the fragment that has now been detached from the DOM. @param {Element} event.target The former parent element of the fragment that has now been detached from the DOM. @stable ### ###** Replaces the given element with a fresh copy fetched from the server. \#\#\# Example up.on('new-mail', function() { up.reload('.inbox') }) Unpoly remembers the URL from which a fragment was loaded, so you don't usually need to give an URL when reloading. @function up.reload @param {string|Element|jQuery} selectorOrElement @param {Object} [options] See options for [`up.replace()`](/up.replace) @param {string} [options.url] The URL from which to reload the fragment. This defaults to the URL from which the fragment was originally loaded. @stable ### reload = (selectorOrElement, options) -> options = u.options(options, cache: false) sourceUrl = options.url || source(selectorOrElement) replace(selectorOrElement, sourceUrl, options) up.on 'up:app:boot', -> body = document.body setSource(body, up.browser.url()) hello(body) up.on 'up:framework:reset', reset <% if ENV['JS_KNIFE'] %>knife: eval(Knife.point)<% end %> createPlaceholder: createPlaceholder replace: replace reload: reload destroy: destroy extract: extract first: first source: source hello: hello config: config layerOf: layerOf up.replace = up.fragment.replace up.extract = up.fragment.extract up.reload = up.fragment.reload up.destroy = up.fragment.destroy up.hello = up.fragment.hello up.first = (args...) -> up.legacy.warn('up.first() has been renamed to up.fragment.first()') up.fragment.first(args...) up.legacy.renamedModule 'flow', 'fragment' up.legacy.renamedModule 'dom', 'fragment'