Skip to content

Commit

Permalink
Change appHistory.navigate(options) to appHistory.reload(options)
Browse files Browse the repository at this point in the history
Closes #112. Note that the behavior differs in that previously appHistory.navigate(options) would do a same-URL replace, which is subtly different from a reload in observable ways.

Some follow-up discussion in #117.
  • Loading branch information
domenic authored May 25, 2021
1 parent e7f230f commit eae73ce
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 48 deletions.
53 changes: 31 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,15 +194,15 @@ The entry point for the app history API is `window.appHistory`. Let's start with
It also has a method `getState()`, which retrieve the app history state for the entry. This is somewhat similar to `history.state`, but it will survive fragment navigations, and `getState()` always returns a fresh clone of the state to avoid the [misleading nature of `history.state`](https://github.com/WICG/app-history/issues/36):
```js
appHistory.navigate({ state: { test: 2 } });
appHistory.reload({ state: { test: 2 } });

// Don't do this: it won't be saved to the stored state.
appHistory.current.getState().test = 3;

console.assert(appHistory.current.getState().test === 2);

// Instead do this:
appHistory.navigate({ state: { ...appHistory.current.getState(), test: 3 });
appHistory.reload({ state: { ...appHistory.current.getState(), test: 3 });
```
Crucially, `appHistory.current` stays the same regardless of what iframe navigations happen. It only reflects the current entry for the current frame. The complete list of ways the current app history entry can change to a new entry (with a new `AppHistoryEntry` object, and a new `key` value) are:
Expand All @@ -219,7 +219,7 @@ The current entry can be replaced with a new entry, with a new `AppHistoryEntry`
- Via the same-document navigation API `history.replaceState()`.
- Via cross-document replace navigations generated by `location.replace()` or `appHistory.navigate({ replace: true, ... })`. Note that if the navigation is cross-origin, then we'll end up in a separate app history list for that other origin, where `key` will not be preserved.
- Via cross-document replace navigations generated by `location.replace()` or `appHistory.navigate(url, { replace: true, ... })`. Note that if the navigation is cross-origin, then we'll end up in a separate app history list for that other origin, where `key` will not be preserved.
- When using the `navigate` event to [convert a cross-document replace navigation into a same-document navigation](#navigation-monitoring-and-interception).
Expand Down Expand Up @@ -594,7 +594,7 @@ In a single-page app using `window.history`, the typical flow is:
In a single-page app using the app history API, instead the router listens to the `navigate` event. This automatically takes care of step (2), and provides a centralized place for the router and framework to perform step (3). And now the application code can use traditional navigation mechanisms, like `<a>` or `location.href`, without any extra code; the browser takes care of sending all of those to the `navigate` event!
There's one gap remaining, which is the ability to send additional state or info along with a navigation. We solve this by introducing a new API, `appHistory.navigate()`, which can be thought of as an augmented and combined version of `location.assign()` and `location.replace()`. The non-replace usage of `appHistory.navigate()` is as follows:
There's one gap remaining, which is the ability to send additional state or info along with a navigation. We solve this by introducing new APIs, `appHistory.navigate()` and `appHistory.reload()`, which can be thought of as an augmented and combined versions of `location.assign()`, `location.replace()`, and `location.reload()`. The non-replace usage of `appHistory.navigate()` is as follows:

```js
// Navigate to a new URL, resetting the state to undefined:
Expand All @@ -606,10 +606,6 @@ await appHistory.navigate(url, { state });
// You can also pass navigateInfo for the navigate event handler to receive:
await appHistory.navigate(url, { state, navigateInfo });
// Navigate to the same URL as the current one, but with a new state value:
// (Probably not very common.)
await appHistory.navigate({ state });
```

Note how unlike `history.pushState()`, `appHistory.navigate()` will by default perform a full navigation, e.g. scrolling to a fragment or navigating across documents. Single-page apps will usually intercept these using the `navigate` event, and convert them into same-document navigations by using `event.respondWith()`.
Expand All @@ -624,9 +620,6 @@ Regardless of whether the navigation gets converted or not, calling `appHistory.
// (equivalent to `location.replace(url)`)
await appHistory.navigate(url, { replace: true });
// Update the state, without changing the URL.
await appHistory.navigate({ replace: true, state });
// Replace the URL and state at the same time.
await appHistory.navigate(url, { replace: true, state });
Expand All @@ -636,6 +629,19 @@ await appHistory.navigate(url, { replace: true, state, navigateInfo });

Again, unlike `history.replaceState()`, `appHistory.navigate(url, { replace: true })` will by default perform a full navigation. And again, single-page apps will usually intercept these using `navigate`.

Finally, we have `appHistory.reload()`. This can be used as a replacement for `location.reload()`, but it also allows passing `navigateInfo` and `state`, which are useful when a single-page app intercepts the reload using the `navigate` event:

```js
// Just like location.reload().
await appHistory.reload();
// Leave the state as-is, but pass some navigateInfo.
await appHistory.reload({ navigateInfo });
// Overwrite the state with a new value.
await appHistory.reload({ state, navigateInfo });
```

Note that both of these methods return promises. In the event that the navigations get converted into same-document navigations via `event.respondWith(promise)` in a `navigate` handler, these returned promises will settle in the same way that `promise` does. This gives your navigation call site an indication of the navigation's success or failure. (If they are non-intercepted fragment navigations, then the promises will fulfill immediately. And if they are non-intercepted cross-document navigations, then the returned promise, along with the entire JavaScript global environment, will disappear as the current document gets unloaded.)
#### Example: using `navigateInfo`
Expand Down Expand Up @@ -690,7 +696,7 @@ appHistory.addEventListener("navigate", e => {
});
```

Note that in addition to `appHistory.navigate()`, the [previously-discussed](#navigation-through-the-app-history-list) `appHistory.back()`, `appHistory.forward()`, `appHistory.goTo()`, and `appHistory.transition.rollback()` methods can also take a `navigateInfo` option.
Note that in addition to `appHistory.navigate()`, the previously-discussed `appHistory.reload()`, `appHistory.back()`, `appHistory.forward()`, `appHistory.goTo()`, and `appHistory.transition.rollback()` methods can also take a `navigateInfo` option.

#### Example: next/previous buttons

Expand Down Expand Up @@ -786,9 +792,9 @@ async function showPhoto(photoId) {
} });
// When we navigate away from this photo, save any changes the user made.
// NOTE: this is kind of awkward; see https://github.com/WICG/app-history/issues/115.
appHistory.current.addEventListener("navigatefrom", e => {
appHistory.navigate({
replace: true,
appHistory.reload({
state: {
dateTaken: document.querySelector("#photo-container > .date-taken").value,
caption: document.querySelector("#photo-container > .caption").value
Expand Down Expand Up @@ -907,7 +913,7 @@ For web developers using the API, here's a guide to explain how you would replac

Instead of using `history.pushState(state, "", url)`, use `await appHistory.navigate(url, { state })` and combine it with a `navigate` handler to convert the default cross-document navigation into a same-document navigation.

Instead of using `history.replaceState(state, "", url)`, use `await appHistory.navigate(url, { replace: true, state })`, again combined with a `navigate` handler. Note that if you omit the state value, i.e. if you say `appHistory.navigate(url, { replace: true })`, then unlike `history.replaceState()`, this will copy over the current entry's state.
Instead of using `history.replaceState(state, "", url)`, use `await appHistory.navigate(url, { replace: true, state })`, again combined with a `navigate` handler. Note that if you omit the state value, i.e. if you say `appHistory.navigate(url, { replace: true })`, then this will overwrite the entry's state with `undefined`.
Instead of using `history.back()` and `history.forward()`, use `await appHistory.back()` and `await appHistory.forward()`. Note that unlike the `history` APIs, the `appHistory` APIs will ignore other frames, and will only control the navigation of your frame. This means it might move through multiple entries in the joint session history, skipping over any entries that were generated purely by other-frame navigations.
Expand Down Expand Up @@ -936,9 +942,7 @@ Note that unlike the `history` APIs, these `appHistory` APIs will not go to anot
Instead of using `history.go(offset)`, use `await appHistory.goTo(key)` to navigate to a specific entry. As with `back()` and `forward()`, `appHistory.goTo()` will ignore other frames, and will only control the navigation of your frame. If you specifically want to reproduce the pattern of navigating by an offset (not recommended), you can use code such as the following:

```js
const entries = appHistory.entries();
const offsetIndex = entries.indexOf(appHistory.current) + offset;
const entry = entries[offsetIndex];
const entry = appHistory.entries()[appHistory.current.index + offset];
if (entry) {
await appHistory.goTo(entry.key);
}
Expand Down Expand Up @@ -995,9 +999,9 @@ Note how in this case we don't need to use `appHistory.navigate()`, even though
### Attaching and using history state
To update the current entry's state, instead of using `history.replaceState(newState)`, use `appHistory.navigate({ state: newState })`, combined with a `navigate` handler to convert the cross-document navigation into a same-document one.
To update the current entry's state, instead of using `history.replaceState(newState)`, use `appHistory.reload({ state: newState })`, combined with a `navigate` handler to convert the cross-document navigation into a same-document one. (See also [#115](https://github.com/WICG/app-history/issues/115) for cases where that pattern might be a bit awkward, and we're discussing something better.)

To create a new entry with the same URL but a new state value, instead of using `history.pushState(newState)`, use `appHistory.navigate({ state: newState })`, again combined with a `navigate` handler.
To create a new entry with the same URL but a new state value, instead of using `history.pushState(newState)`, use `appHistory.navigate(appHistory.current.url, { state: newState })`, again combined with a `navigate` handler.

To read the current entry's state, instead of using `history.state`, use `appHistory.current.getState()`. Note that this will give a clone of the state, so you cannot set properties on it: to update state, use `appHistory.navigate()`.
Expand Down Expand Up @@ -1206,7 +1210,7 @@ The web platform has many ways of initiating a navigation. For the purposes of t
- `history.back()`, `history.forward()`, and `history.go()`
- `history.pushState()` and `history.replaceState()`
- `appHistory.back()`, `appHistory.forward()`, `appHistory.goTo()`
- `appHistory.navigate()`
- `appHistory.navigate()`, `appHistory.reload()`
- [`document.open()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/open)

**Cross-document navigations** are navigations where, after the navigation completes, you end up in a different `Document` object than the one you are curently on. Notably, these unload the old document, and stop running any JavaScript code from there.
Expand Down Expand Up @@ -1237,6 +1241,7 @@ Here's a summary table:
|`history.{pushState,replaceState}()`|Same|Yes|No|Yes|Yes|
|`appHistory.{back,forward,goTo}()`|Either|Yes|No|Yes|Yes †*|
|`appHistory.navigate()`|Either|Yes|No|Yes|Yes *|
|`appHistory.reload()`|Cross|Yes|No|Yes|Yes|
|`window.open(url, "_self")`|Either|Yes|No|Yes|Yes *|
|`window.open(url, name)`|Either|Yes Δ|No|Yes|Yes *|
|`document.open()`|Same|No|—|—|—|
Expand Down Expand Up @@ -1270,7 +1275,7 @@ interface AppHistory : EventTarget {
readonly attribute boolean canGoForward;
Promise<undefined> navigate(USVString url, optional AppHistoryNavigateOptions options = {});
Promise<undefined> navigate(optional AppHistoryNavigateOptions options = {}); // one non-replace member required: see issue #52
Promise<undefined> reload(optional AppHistoryReloadOptions options = {});
Promise<undefined> goTo(DOMString key, optional AppHistoryNavigationOptions = {});
Promise<undefined> back(optional AppHistoryNavigationOptions = {});
Expand Down Expand Up @@ -1322,6 +1327,10 @@ dictionary AppHistoryNavigateOptions : AppHistoryNavigationOptions {
boolean replace = false;
};
dictionary AppHistoryReloadOptions : AppHistoryNavigationOptions {
any state;
};
[Exposed=Window]
interface AppHistoryNavigateEvent : Event {
constructor(DOMString type, optional AppHistoryNavigateEventInit eventInit = {});
Expand Down
Loading

0 comments on commit eae73ce

Please sign in to comment.