11454: Remotes listed on remoteHosts show "enable/disable" action button
[arvados.git] / apps / workbench / app / assets / javascripts / components / save_ui_state.js
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 // SaveUIState avoids losing scroll position due to navigation
6 // events, and saves/restores other caller-specified UI state.
7 //
8 // It does not display any content itself: do not pass any children.
9 //
10 // Use of multiple SaveUIState components on the same page is not
11 // (yet) supported.
12 //
13 // The problem being solved:
14 //
15 // Page 1 loads some content dynamically (e.g., via infinite scroll)
16 // after the initial render. User scrolls down, clicks a link, and
17 // lands on page 2. User clicks the Back button, and lands on page
18 // 1. Page 1 renders its initial content while waiting for AJAX.
19 //
20 // But (without SaveUIState) the document body is small now, so the
21 // browser resets scroll position to the top of the page. Even if we
22 // end up displaying the same dynamic content, the user's place on the
23 // page has been lost.
24 //
25 // SaveUIState fixes this by stashing the current body height when
26 // navigating away from page 1. When navigating back, it restores the
27 // body height even before the page has loaded, so the browser does
28 // not reset the scroll position.
29 //
30 // SaveUIState also saves/restores arbitrary UI state (like text typed
31 // in a search box) in response to navigation events.
32 //
33 // See CollectionsSearch for an example.
34 //
35 // Attributes:
36 //
37 // {getter-setter} currentState: the current UI state
38 //
39 // {any} defaultState: value to initialize currentState with, if
40 // nothing is stashed in browser history.
41 //
42 // {boolean} forgetSavedHeight: the body height loaded from the
43 // browser history (if any) is outdated; we should let the browser
44 // determine the correct body height from the current page
45 // content. Set this when dynamic content has been reset.
46 //
47 // {boolean} saveBodyHeight: save/restore body height as described
48 // above.
49 window.SaveUIState = {
50     saveState: function() {
51         var state = history.state || {}
52         state.bodyHeight = window.getComputedStyle(document.body)['height']
53         state.currentState = this.currentState()
54         history.replaceState(state, '')
55     },
56     oninit: function(vnode) {
57         vnode.state.currentState = vnode.attrs.currentState
58         var hstate = history.state || {}
59
60         if (vnode.attrs.saveBodyHeight && hstate.bodyHeight) {
61             document.body.style['min-height'] = hstate.bodyHeight
62             delete hstate.bodyHeight
63         }
64
65         if (hstate.currentState) {
66             vnode.attrs.currentState(hstate.currentState)
67             delete hstate.currentState
68         } else {
69             vnode.attrs.currentState(vnode.attrs.defaultState)
70         }
71
72         history.replaceState(hstate, '')
73     },
74     oncreate: function(vnode) {
75         vnode.state.saveState = vnode.state.saveState.bind(vnode.state)
76         window.addEventListener('beforeunload', vnode.state.saveState)
77         vnode.state.onupdate(vnode)
78     },
79     onupdate: function(vnode) {
80         if (vnode.attrs.saveBodyHeight && vnode.attrs.forgetSavedHeight) {
81             document.body.style['min-height'] = null
82         }
83     },
84     onremove: function(vnode) {
85         window.removeEventListener('beforeunload', vnode.state.saveState)
86     },
87     view: function(vnode) {
88         return null
89     },
90 }