diff --git a/files/en-us/_redirects.txt b/files/en-us/_redirects.txt index bbc8628342a2a99..f38481a93d6bff3 100644 --- a/files/en-us/_redirects.txt +++ b/files/en-us/_redirects.txt @@ -1385,7 +1385,7 @@ /en-US/docs/DOM/LinkStyle /en-US/docs/Web/API/HTMLLinkElement /en-US/docs/DOM/Locating_DOM_elements_using_selectors /en-US/docs/Web/API/Document_object_model/Locating_DOM_elements_using_selectors /en-US/docs/DOM/Manipulating_the_browser_history /en-US/docs/Web/API/History_API -/en-US/docs/DOM/Manipulating_the_browser_history/Example /en-US/docs/Web/API/History_API/Example +/en-US/docs/DOM/Manipulating_the_browser_history/Example /en-US/docs/Web/API/History_API/Working_with_the_History_API /en-US/docs/DOM/MediaQueryList /en-US/docs/Web/API/MediaQueryList /en-US/docs/DOM/MediaQueryListListener /en-US/docs/Web/API/MediaQueryList /en-US/docs/DOM/MediaStream /en-US/docs/Web/API/MediaStream @@ -8617,6 +8617,7 @@ /en-US/docs/Web/API/HashChangeEvent/HashChangeEvent.oldURL /en-US/docs/Web/API/HashChangeEvent/oldURL /en-US/docs/Web/API/Headers/getAll /en-US/docs/Web/API/Headers/get /en-US/docs/Web/API/History.length /en-US/docs/Web/API/History/length +/en-US/docs/Web/API/History_API/Example /en-US/docs/Web/API/History_API/Working_with_the_History_API /en-US/docs/Web/API/IDBCursor.advance /en-US/docs/Web/API/IDBCursor/advance /en-US/docs/Web/API/IDBCursor.continue /en-US/docs/Web/API/IDBCursor/continue /en-US/docs/Web/API/IDBCursor.delete /en-US/docs/Web/API/IDBCursor/delete @@ -11992,7 +11993,7 @@ /en-US/docs/Web/Guide/API/DOM/Events/Using_device_orientation_with_3D_transforms /en-US/docs/Web/API/Device_orientation_events/Using_device_orientation_with_3D_transforms /en-US/docs/Web/Guide/API/DOM/Locating_DOM_elements_using_selectors /en-US/docs/Web/API/Document_object_model/Locating_DOM_elements_using_selectors /en-US/docs/Web/Guide/API/DOM/Manipulating_the_browser_history /en-US/docs/Web/API/History_API -/en-US/docs/Web/Guide/API/DOM/Manipulating_the_browser_history/Example /en-US/docs/Web/API/History_API/Example +/en-US/docs/Web/Guide/API/DOM/Manipulating_the_browser_history/Example /en-US/docs/Web/API/History_API/Working_with_the_History_API /en-US/docs/Web/Guide/API/DOM/Storage /en-US/docs/Web/API/Web_Storage_API /en-US/docs/Web/Guide/API/DOM/The_structured_clone_algorithm /en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm /en-US/docs/Web/Guide/API/DOM/Using_dynamic_styling_information /en-US/docs/Web/API/CSS_Object_Model/Using_dynamic_styling_information @@ -12061,7 +12062,7 @@ /en-US/docs/Web/Guide/DOM/Events/Using_device_orientation_with_3D_transforms /en-US/docs/Web/API/Device_orientation_events/Using_device_orientation_with_3D_transforms /en-US/docs/Web/Guide/DOM/Locating_DOM_elements_using_selectors /en-US/docs/Web/API/Document_object_model/Locating_DOM_elements_using_selectors /en-US/docs/Web/Guide/DOM/Manipulating_the_browser_history /en-US/docs/Web/API/History_API -/en-US/docs/Web/Guide/DOM/Manipulating_the_browser_history/Example /en-US/docs/Web/API/History_API/Example +/en-US/docs/Web/Guide/DOM/Manipulating_the_browser_history/Example /en-US/docs/Web/API/History_API/Working_with_the_History_API /en-US/docs/Web/Guide/DOM/Storage /en-US/docs/Web/API/Web_Storage_API /en-US/docs/Web/Guide/DOM/The_structured_clone_algorithm /en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm /en-US/docs/Web/Guide/DOM/Using_dynamic_styling_information /en-US/docs/Web/API/CSS_Object_Model/Using_dynamic_styling_information diff --git a/files/en-us/_wikihistory.json b/files/en-us/_wikihistory.json index ce41a03f844d5e0..5167e28b2cb213b 100644 --- a/files/en-us/_wikihistory.json +++ b/files/en-us/_wikihistory.json @@ -42313,23 +42313,6 @@ "peterson.victor222" ] }, - "Web/API/History_API/Example": { - "modified": "2019-09-05T07:52:37.814Z", - "contributors": [ - "irenesmith", - "RichWK", - "sunnywalker", - "teoli", - "fusionchess", - "wbamberg", - "alispivak", - "sickOfNamesPasswordsetc", - "wctaiwan", - "Sheppy", - "giabao", - "jamel" - ] - }, "Web/API/History_API/Working_with_the_History_API": { "modified": "2019-12-27T13:55:25.264Z", "contributors": [ diff --git a/files/en-us/web/api/history/replacestate/index.md b/files/en-us/web/api/history/replacestate/index.md index 824ec33310bcdf9..d0da00631c71a71 100644 --- a/files/en-us/web/api/history/replacestate/index.md +++ b/files/en-us/web/api/history/replacestate/index.md @@ -14,8 +14,6 @@ URL passed in the method parameters. This method is particularly useful when you want to update the state object or URL of the current history entry in response to some user action. -This method is {{glossary("asynchronous")}}. Add a listener for the {{domxref("Window/popstate_event", "popstate")}} event in order to determine when the navigation has completed. The `state` parameter will be available in it. - ## Syntax ```js-nolint diff --git a/files/en-us/web/api/history_api/example/index.md b/files/en-us/web/api/history_api/example/index.md deleted file mode 100644 index 8eb54bcc8f06006..000000000000000 --- a/files/en-us/web/api/history_api/example/index.md +++ /dev/null @@ -1,426 +0,0 @@ ---- -title: History navigation example -slug: Web/API/History_API/Example -page-type: guide ---- - -This is an example of a website composed only of three pages (_first_page.php_, _second_page.php_ and _third_page.php_). To see how it works, please create the following files (or git clone [https://github.com/giabao/mdn-ajax-nav-example.git](https://github.com/giabao/mdn-ajax-nav-example) ): - -> **Note:** For fully integrating the {{HTMLElement("form")}} elements within this _mechanism_, please take a look at the paragraph [Submitting forms and uploading files](/en-US/docs/Web/API/XMLHttpRequest_API/Using_XMLHttpRequest#submitting_forms_and_uploading_files). - -**first_page.php**: - -```php - - - - -" . $page_title . ""; -?> - - - - - - -

This paragraph is shown only when the navigation starts from first_page.php.

- -
- - -

This is the content of first_page.php.

- - $page_title, "content" => ob_get_clean())); - } else { -?> -
- -

This paragraph is shown only when the navigation starts from first_page.php.

- -\n"; - } -?> -``` - -**second_page.php**: - -```php - - - - -" . $page_title . ""; -?> - - - - - - -

This paragraph is shown only when the navigation starts from second_page.php.

- -
- - -

This is the content of second_page.php.

- - $page_title, "content" => ob_get_clean())); - } else { -?> -
- -

This paragraph is shown only when the navigation starts from second_page.php.

- -\n"; - } -?> -``` - -**third_page.php**: - -```php -This is the content of third_page.php. This content is stored into a PHP variable.

"; - - if (isset($_GET["view_as"]) && $_GET["view_as"] == "json") { - echo json_encode(array("page" => $page_title, "content" => $page_content)); - } else { -?> - - - -" . $page_title . ""; -?> - - - - - - -

This paragraph is shown only when the navigation starts from third_page.php.

- -
- -
- -

This paragraph is shown only when the navigation starts from third_page.php.

- -\n"; - } -?> -``` - -**css/style.css**: - -```css -#ajax-loader { - position: fixed; - display: table; - top: 0; - left: 0; - width: 100%; - height: 100%; -} - -#ajax-loader > div { - display: table-cell; - width: 100%; - height: 100%; - vertical-align: middle; - text-align: center; - background-color: #000000; - opacity: 0.65; -} -``` - -**include/after_content.php**: - -```php -

This is the footer. It is shared between all Ajax pages.

-``` - -**include/before_content.php**: - -```php -

-[ First example -| Second example -| Third example -| Unexisting page ] -

-``` - -**include/header.php**: - -```html - - - -``` - -**js/ajax_nav.js**: - -```js -"use strict"; - -const ajaxRequest = new (function () { - let req; - let isLoading = false; - let updateURL = false; - - /* customizable constants */ - const targetId = "ajax-content"; - const viewKey = "view_as"; - const ajaxClass = "ajax-nav"; - - /* not customizable constants */ - const searchRegex = /\?.*$/; - const hostRegex = /^[^?]*\?*&*/; - const viewRegex = new RegExp(`&${viewKey}\\=[^&]*|&*$`, "i"); - const endQstMarkRegex = /\?$/; - const loadingBox = document.createElement("div"); - const cover = document.createElement("div"); - const loadingImg = new Image(); - const pageInfo = { - title: null, - url: location.href, - }; - /* http://www.iana.org/assignments/http-status-codes/http-status-codes.xml */ - const HTTP_STATUS = { - 100: "Continue", - 101: "Switching Protocols", - 102: "Processing", - 200: "OK", - 201: "Created", - 202: "Accepted", - 203: "Non-Authoritative Information", - 204: "No Content", - 205: "Reset Content", - 206: "Partial Content", - 207: "Multi-Status", - 208: "Already Reported", - 226: "IM Used", - 300: "Multiple Choices", - 301: "Moved Permanently", - 302: "Found", - 303: "See Other", - 304: "Not Modified", - 305: "Use Proxy", - 306: "Reserved", - 307: "Temporary Redirect", - 308: "Permanent Redirect", - 400: "Bad Request", - 401: "Unauthorized", - 402: "Payment Required", - 403: "Forbidden", - 404: "Not Found", - 405: "Method Not Allowed", - 406: "Not Acceptable", - 407: "Proxy Authentication Required", - 408: "Request Timeout", - 409: "Conflict", - 410: "Gone", - 411: "Length Required", - 412: "Precondition Failed", - 413: "Request Entity Too Large", - 414: "Request-URI Too Long", - 415: "Unsupported Media Type", - 416: "Requested Range Not Satisfiable", - 417: "Expectation Failed", - 422: "Unprocessable Content", - 423: "Locked", - 424: "Failed Dependency", - 425: "Unassigned", - 426: "Upgrade Required", - 427: "Unassigned", - 428: "Precondition Required", - 429: "Too Many Requests", - 430: "Unassigned", - 431: "Request Header Fields Too Large", - 500: "Internal Server Error", - 501: "Not Implemented", - 502: "Bad Gateway", - 503: "Service Unavailable", - 504: "Gateway Timeout", - 505: "HTTP Version Not Supported", - 506: "Variant Also Negotiates (Experimental)", - 507: "Insufficient Storage", - 508: "Loop Detected", - 509: "Unassigned", - 510: "Not Extended", - 511: "Network Authentication Required", - }; - - function closeReq() { - loadingBox.parentNode && document.body.removeChild(loadingBox); - isLoading = false; - } - req.abort(); - closeReq(); - } - - function ajaxError() { - alert("Unknown error."); - } - - function ajaxLoad() { - let msg; - const status = this.status; - switch (status) { - case 200: - msg = JSON.parse(this.responseText); - document.title = pageInfo.title = msg.page; - document.getElementById(targetId).innerHTML = msg.content; - if (updateURL) { - history.pushState(pageInfo, pageInfo.title, pageInfo.url); - updateURL = false; - } - break; - default: - msg = `${status}: ${HTTP_STATUS[status] || "Unknown"}`; - switch (Math.floor(status / 100)) { - /* - case 1: - // Informational 1xx - console.log("Information code " + vMsg); - break; - case 2: - // Successful 2xx - console.log("Successful code " + vMsg); - break; - case 3: - // Redirection 3xx - console.log("Redirection code " + vMsg); - break; - */ - case 4: - /* Client Error 4xx */ - alert(`Client Error #${msg}`); - break; - case 5: - /* Server Error 5xx */ - alert(`Server Error #${msg}`); - break; - default: - /* Unknown status */ - ajaxError(); - } - } - closeReq(); - } - - function filterURL(url, viewMode) { - return ( - url.replace(searchRegex, "") + - `?${url - .replace(hostRegex, "&") - .replace(viewRegex, viewMode ? `&${viewKey}=${viewMode}` : "") - .slice(1)}`.replace(endQstMarkRegex, "") - ); - } - - function getPage(page) { - if (isLoading) { - return; - } - req = new XMLHttpRequest(); - isLoading = true; - req.onload = ajaxLoad; - req.onerror = ajaxError; - if (page) { - pageInfo.url = filterURL(page, null); - } - req.open("get", filterURL(pageInfo.url, "json"), true); - req.send(); - loadingBox.parentNode || document.body.appendChild(loadingBox); - } - - function requestPage(url) { - if (history.pushState) { - updateURL = true; - getPage(url); - } else { - /* Ajax navigation is not supported */ - location.assign(url); - } - } - - function processLink() { - if (this.className === ajaxClass) { - requestPage(this.href); - return false; - } - return true; - } - - function init() { - pageInfo.title = document.title; - history.replaceState(pageInfo, pageInfo.title, pageInfo.url); - for (const link of document.links) { - link.onclick = processLink; - } - } - - loadingBox.id = "ajax-loader"; - cover.onclick = abortReq; - loadingImg.src = - "data:image/gif;base64,R0lGODlhEAAQAPIAAP///wAAAMLCwkJCQgAAAGJiYoKCgpKSkiH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAADMwi63P4wyklrE2MIOggZnAdOmGYJRbExwroUmcG2LmDEwnHQLVsYOd2mBzkYDAdKa+dIAAAh+QQJCgAAACwAAAAAEAAQAAADNAi63P5OjCEgG4QMu7DmikRxQlFUYDEZIGBMRVsaqHwctXXf7WEYB4Ag1xjihkMZsiUkKhIAIfkECQoAAAAsAAAAABAAEAAAAzYIujIjK8pByJDMlFYvBoVjHA70GU7xSUJhmKtwHPAKzLO9HMaoKwJZ7Rf8AYPDDzKpZBqfvwQAIfkECQoAAAAsAAAAABAAEAAAAzMIumIlK8oyhpHsnFZfhYumCYUhDAQxRIdhHBGqRoKw0R8DYlJd8z0fMDgsGo/IpHI5TAAAIfkECQoAAAAsAAAAABAAEAAAAzIIunInK0rnZBTwGPNMgQwmdsNgXGJUlIWEuR5oWUIpz8pAEAMe6TwfwyYsGo/IpFKSAAAh+QQJCgAAACwAAAAAEAAQAAADMwi6IMKQORfjdOe82p4wGccc4CEuQradylesojEMBgsUc2G7sDX3lQGBMLAJibufbSlKAAAh+QQJCgAAACwAAAAAEAAQAAADMgi63P7wCRHZnFVdmgHu2nFwlWCI3WGc3TSWhUFGxTAUkGCbtgENBMJAEJsxgMLWzpEAACH5BAkKAAAALAAAAAAQABAAAAMyCLrc/jDKSatlQtScKdceCAjDII7HcQ4EMTCpyrCuUBjCYRgHVtqlAiB1YhiCnlsRkAAAOwAAAAAAAAAAAA=="; - cover.appendChild(loadingImg); - loadingBox.appendChild(cover); - - onpopstate = (event) => { - updateURL = false; - pageInfo.title = event.state.title; - pageInfo.url = event.state.url; - getPage(); - }; - - window.addEventListener("load", init, false); - - // Public methods - - this.open = requestPage; - this.stop = abortReq; - this.rebuildLinks = init; -})(); -``` - -For more information, please see: [Working with the History API](/en-US/docs/Web/API/History_API/Working_with_the_History_API). - -## See also - -- {{domxref("window.history", "history")}} global object -- {{domxref("Window/popstate_event", "popstate")}} event diff --git a/files/en-us/web/api/history_api/working_with_the_history_api/index.md b/files/en-us/web/api/history_api/working_with_the_history_api/index.md index b5b9cc3ce790727..5af4a5b38b9fee9 100644 --- a/files/en-us/web/api/history_api/working_with_the_history_api/index.md +++ b/files/en-us/web/api/history_api/working_with_the_history_api/index.md @@ -6,108 +6,175 @@ page-type: guide {{DefaultAPISidebar("History API")}} -The {{DOMxRef("History.pushState","pushState()")}} and {{DOMxRef("History.replaceState","replaceState()")}} methods add and modify history entries, respectively. These methods work in conjunction with the {{domxref("Window/popstate_event", "popstate")}} event. +The History API enables a website to interact with the browser's session history: that is, the list of pages that the user has visited in a given window. As the user visits new pages, for example by clicking links, those new pages are added to the session history. The user can also move back and forward through the history using the browser's "Back" and "Forward" buttons. -## Adding and modifying history entries +The main interface defined in the History API is the {{domxref("History")}} interface, and this defines two quite distinct sets of methods: -Using {{DOMxRef("History.pushState","pushState()")}} changes the referrer that gets used in the HTTP header for {{domxref("XMLHttpRequest")}} objects created after you change the state. The referrer will be the URL of the document whose window is `this` at the time of creation of the {{domxref("XMLHttpRequest")}} object. +1. Methods to navigate to a page in the session history: -### Example of pushState() method + - {{domxref("History.back()")}} + - {{domxref("History.forward()")}} + - {{domxref("History.go()")}} -Suppose `https://mozilla.org/foo.html` executes the following JavaScript: +2. Methods to modify the session history: -```js -const stateObj = { - foo: "bar", -}; + - {{domxref("History.pushState()")}} + - {{domxref("History.replaceState()")}} -history.pushState(stateObj, "page 2", "bar.html"); -``` +In this guide we'll be concerned only with the second set of methods, as these have more complex behavior. -This will cause the URL bar to display `https://mozilla.org/bar.html`, but won't cause the browser to load `bar.html` or even check that `bar.html` exists. +The `pushState()` method adds a new entry to the session history, while the `replaceState()` method updates the session history entry for the current page. Both these methods take a `state` parameter which can contain any {{Glossary("Serializable_object", "serializable object")}} . When the browser navigates to this history entry, the browser fires a {{domxref("Window.popstate_event", "popstate")}} event, which contains the state object associated with that entry. -Suppose now that the user navigates to `https://google.com`, then clicks the **Back** button. At this point, the URL bar will display `https://mozilla.org/bar.html` and `history.state` will contain the `stateObj`. The `popstate` event won't be fired because the page has been reloaded. The page itself will look like `bar.html`. +The main purpose of these APIs is to support websites like {{Glossary("SPA", "Single-page applications")}}, that use JavaScript APIs such as {{domxref("fetch()")}} to update the page with new content, instead of loading a whole new page. -If the user clicks **Back** once again, the URL will change to `https://mozilla.org/foo.html`, and the document will get a `popstate` event, this time with a `null` state object. Here too, going back doesn't change the document's contents from what they were in the previous step, although the document might update its contents manually upon receiving the `popstate` event. +## Single-page applications and session history -### The pushState() method +Traditionally, websites are implemented as a collection of pages, and when users navigate to different parts of the site by clicking links, the browser loads a whole new page each time. -`pushState()` takes three parameters: a **state object**; a **title** (currently ignored); and (optionally), a **URL**. +While this is great for many sites, it can have some disadvantages: -Let's examine each of these three parameters in more detail. +- It can be inefficient to load a whole page every time, when only part of the page needs to be updated. +- It is hard to maintain application state when navigating across pages -- **state object** +For these reasons, a popular pattern for web apps is the {{Glossary("SPA", "single-page application")}} (SPA), in which the site consists of a single page, and when the user clicks links, the page: - - : The state object is a JavaScript object which is associated with the new history entry created by `pushState()`. Whenever the user navigates to the new state, a `popstate` event is fired, and the `state` property of the event contains a copy of the history entry's state object. - The state object can be anything that can be serialized. +1. Prevents the default behavior of loading a new page +2. {{domxref("fetch()", "Fetches", "", "nocode")}} new content to display +3. Updates the page with the new content - > **Note:** Some browsers save state objects to the user's disk so they can be restored after the user restarts the browser, and impose a size limit on the serialized representation of a state object, and will throw an exception if you pass a state object whose serialized representation is larger than that size limit. So in cases where you want to ensure you have more space than what some browsers might impose, you're encouraged to use {{domxref("Window.sessionStorage", "sessionStorage")}} and/or {{domxref("Window.localStorage", "localStorage")}}. +For example: + +```js +document.addEventListener("click", async (event) => { + const creature = event.target.getAttribute("data-creature"); + if (creature) { + // Prevent a new page from loading + event.preventDefault(); + try { + // Fetch new content + const response = await fetch(`creatures/${creature}.json`); + const json = await response.json(); + // Update the page with the new content + displayContent(json); + } catch (err) { + console.error(err); + } + } +}); +``` -- **title** - - : [All browsers but Safari currently ignore this parameter](https://github.com/whatwg/html/issues/2174), although they may use it in the future. Passing the empty string here should be safe against future changes to the method. Alternatively, you could pass a short title for the state to which you're moving. -- **URL** - - : The new history entry's URL is given by this parameter. Note that the browser won't attempt to load this URL after a call to `pushState()`, but it might attempt to load the URL later, for instance after the user restarts the browser. The new URL does not need to be absolute; if it's relative, it's resolved relative to the current URL. The new URL must be of the same origin as the current URL; otherwise, `pushState()` will throw an exception. This parameter is optional; if it isn't specified, it's set to the document's current URL. +In this click handler, if the link contains a data attribute `"data-creature"`, then we use the value of that attribute to fetch a JSON file containing the new content for the page. -In a sense, calling `pushState()` is similar to setting `window.location = "#foo"`, in that both will also create and activate another history entry associated with the current document. +Our `displayContent()` function updates the page with the JSON: -But `pushState()` has a few advantages: +```js +// Update the page with the new content +function displayContent(content) { + document.title = `Creatures: ${content.name}`; -- The new URL can be any URL in the same origin as the current URL. In contrast, setting `window.location` keeps you at the same {{ domxref("document") }} only if you modify only the hash. -- You don't have to change the URL if you don't want to. In contrast, setting `window.location = "#foo";` creates a new history entry only if the current hash isn't `#foo`. -- You can associate arbitrary data with your new history entry. With the hash-based approach, you need to encode all of the relevant data into a short string. -- If `title` is subsequently used by browsers, this data can be utilized (independent of, say, the hash). + const description = document.querySelector("#description"); + description.textContent = content.description; -Note that `pushState()` never causes a `hashchange` event to be fired, even if the new URL differs from the old URL only in its hash. + const photo = document.querySelector("#photo"); + photo.setAttribute("src", content.image.src); + photo.setAttribute("alt", content.image.alt); +} +``` -In other documents, it creates an element with a `null` namespace URI. +The problem with this is that it breaks the expected behavior of the browser's "Back" and "Forward" buttons. -### The replaceState() method +From the user's point of view, they clicked a link and the page updated, so it looks like a new page. If they then press the browser's "Back" button, they expect to go to the state before they clicked the link. -`history.replaceState()` operates exactly like `history.pushState()`, except that `replaceState()` modifies the current history entry instead of creating a new one. Note that this doesn't prevent the creation of a new entry in the global browser history. +But as far as the browser is concerned, the last link didn't load a new page, so "Back" will take the browser to whichever page was loaded before the SPA loaded. -`replaceState()` is particularly useful when you want to update the state object or URL of the current history entry in response to some user action. +This is essentially the problem that `pushState()`, `replaceState()`, and `popstate` are designed to solve. They enable us to synthesize history entries, and to be notified when the current session history entry changes to one of these entries (for example, because the user pressed the "Back" or "Forward" buttons). -### Example of replaceState() method +## Using `pushState()` -Suppose `https://mozilla.org/foo.html` executes the following JavaScript: +We can add a history entry to the click handler above as follows: ```js -const stateObj = { - foo: "bar", -}; -history.pushState(stateObj, "page 2", "bar.html"); +document.addEventListener("click", async (event) => { + const creature = event.target.getAttribute("data-creature"); + if (creature) { + event.preventDefault(); + try { + const response = await fetch(`creatures/${creature}.json`); + const json = await response.json(); + displayContent(json); + // Add a new entry to the history. + // This simulates loading a new page. + history.pushState(json, "", creature); + } catch (err) { + console.error(err); + } + } +}); ``` -The explanation of these two lines above can be found at the above section _[Example of pushState() method](#example_of_pushstate_method)_ section. +Here, we're calling `pushState()` with three arguments: -Next, suppose `https://mozilla.org/bar.html` executes the following JavaScript: +- `json`: this is the content we just fetched. It will be stored with the history entry, and later included as the {{domxref("PopStateEvent.state", "state")}} property of the argument passed to the `popstate` event handler. +- `""`: this is needed for backwards compatibility reasons only, and should always be an empty string +- `creature`: this will be used as the URL for the entry. It will be shown in the browser's URL bar, and will be used as the value of the {{httpheader("Referer")}} header in any HTTP requests that the page makes. Note that this must be {{Glossary("Same-origin policy", "same-origin")}} with the page. -```js -history.replaceState(stateObj, "page 3", "bar2.html"); -``` +## Using `popState` -This will cause the URL bar to display `https://mozilla.org/bar2.html`, but won't cause the browser to load `bar2.html` or even check that `bar2.html` exists. +Suppose the user: -Suppose now that the user navigates to `https://www.microsoft.com`, then clicks the **Back** button. At this point, the URL bar will display `https://mozilla.org/bar2.html`. If the user now clicks **Back** again, the URL bar will display `https://mozilla.org/foo.html`, and totally bypass `bar.html`. +1. Clicks a link in our SPA, so we update the page and add history entry A using `pushState()` +2. Clicks another link in our SPA, so we update the page and add history entry B using `pushState()` +3. Presses the "Back" button -### The popstate event +Now the new current history entry is A, so the browser fires the `popstate` event, and the event handler argument includes the JSON that we passed to `pushState()` when we handled the navigation to A. This means we can restore the correct content with an event handler like this: -A `popstate` event is dispatched to the window every time the active history entry changes. If the history entry being activated was created by a call to {{DOMxRef("History.pushState","pushState")}} or affected by a call to {{DOMxRef("History.replaceState","replaceState")}}, the `popstate` event's `state` property contains a copy of the history entry's state object. +```js +// Handle forward/back buttons +window.addEventListener("popstate", (event) => { + // If a state has been provided, we have a "simulated" page + // and we update the current page. + if (event.state) { + // Simulate the loading of the previous page + displayContent(event.state); + } +}); +``` -See {{domxref("Window/popstate_event", "popstate")}} for sample usage. +## Using `replaceState()` -### Reading the current state +There's one more piece we need to add. When the user first loads the SPA, the browser adds a history entry for it. Because this was a real page load, the entry doesn't have any state associated with it. So suppose the user: -When your page loads, it might have a non-null state object. This can happen, for example, if the page sets a state object (using {{DOMxRef("History.pushState","pushState()")}} or {{DOMxRef("History.replaceState","replaceState()")}}) and then the user restarts their browser. When the page reloads, the page will receive an `onload` event, but no `popstate` event. However, if you read the {{DOMxRef("History.state","history.state")}} property, you'll get back the state object you would have gotten if a `popstate` had fired. +1. Loads the SPA: the browser adds a history entry +2. Clicks a link inside the SPA: the click handler updates the page and adds a history entry with `pushState()` +3. Presses the "Back" button -You can read the state of the current history entry without waiting for a `popstate` event using the {{DOMxRef("History.state","history.state")}} property like this: +Now we want to go back to the SPA's initial state, but since this is a navigation in the same document, the page will not be reloaded, and since the history entry for the initial page has no state, we can't use `popstate` to restore it. + +The solution here is to use `replaceState()` to set the state object for the initial page. For example: ```js -const currentState = history.state; +// Create state on page load and replace the current history with it +const image = document.querySelector("#photo"); +const initialState = { + description: document.querySelector("#description").textContent, + image: { + src: image.getAttribute("src"), + alt: image.getAttribute("alt"), + }, + name: "Home", +}; +history.replaceState(initialState, "", document.location.href); ``` +On page load, we collect all the parts of the page that we need to restore when the user returns to the starting point for the SPA. This has just the same structure as the JSON that we fetch when handling other navgations. We pass this `initialState` into `replaceState()`, which effectively adds the state object to the history entry. + +Now, when the user goes back to our starting point, the `popstate` event will contain this initial state, and we can use our `displayContent()` function to update the page. + +## A complete example + +You can find this complete example at , and see the demo live at . + ## See also - [History API](/en-US/docs/Web/API/History_API) -- [History navigation example](/en-US/docs/Web/API/History_API/Example) - {{domxref("window.history", "history")}} global object diff --git a/files/en-us/web/guide/ajax/index.md b/files/en-us/web/guide/ajax/index.md index 076306e44c0dd77..f8552c9c2099c1b 100644 --- a/files/en-us/web/guide/ajax/index.md +++ b/files/en-us/web/guide/ajax/index.md @@ -363,8 +363,6 @@ While Ajax is still useful, other APIs have been developed since 2005 that enabl - [Monitoring the progress of a request](/en-US/docs/Web/API/XMLHttpRequest_API/Using_XMLHttpRequest#monitoring_progress) - [Submitting forms and upload binary files](/en-US/docs/Web/API/XMLHttpRequest_API/Using_XMLHttpRequest#submitting_forms_and_uploading_files) – in _pure_ Ajax, or using {{domxref("FormData")}} objects - Using Ajax within [Web workers](/en-US/docs/Web/API/Worker) -- [_Pure-Ajax_ navigation example](/en-US/docs/Web/API/History_API/Example) - - : This article provides a working (minimalist) example of a _pure-Ajax_ website composed only of three pages. - [Sending and Receiving Binary Data](/en-US/docs/Web/API/XMLHttpRequest_API/Sending_and_Receiving_Binary_Data) - : The `responseType` property of the `XMLHttpRequest` object can be set to change the expected response type from the server. Possible values are the empty string (default), `arraybuffer`, `blob`, `document`, `json`, and `text`. The `response` property will contain the entity body according to `responseType`, as an `ArrayBuffer`, `Blob`, `Document`, `JSON`, or string. This article will show some Ajax I/O techniques. - [XML](/en-US/docs/Web/XML)