User onboarding: Help menu and dismissible UI elements #9351
laymonage
started this conversation in
Development
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
Background
Problem
In recent feature releases, Wagtail has overhauled the UI of its page editor and the overall looks of the admin. These changes could potentially overwhelm or confuse existing users, which results in difficulties navigating around Wagtail. (We've heard feedback such as "Where did the preview go?!" [citation needed])
Solution
We initially considered adding an onboarding tour of the new features within the Wagtail admin interface. However, due to the highly customisable nature of Wagtail, such a tour might not always be applicable to the Wagtail instance the user is interacting with. In addition, if we would like the tour to be customisable by developers, it would’ve been yet another complex, moving target that we need to keep maintaining in the future.
After some more thoughtful considerations, we propose adding a new "Help" menu in the sidebar that contains submenus that provide users with links to:
In addition to the help menu, we will also show a banner on the dashboard for major releases to better indicate these changes.
Implementation
We will implement the Help menu by registering a new
Menu
that also accepts newregister_help_menu_item
andconstruct_help_menu
hooks.Each menu item in the help menu can have its own "new" badge that will disappear after the user clicks it.
The dashboard banner also has a button to dismiss the message, which will hide it permanently.
Wagtail should remember which elements have been dismissed by the user, so that they do not show the "new" state all the time. To implement these elements, we can start with a concept of "dismissibles".
Enter: Dismissibles
Dismissibles are UI elements that can be "dismissed" by the user. The element can define its own behaviour before and after the dismissal (e.g. show/hide a "new" badge, show/hide itself, etc.) and how a dismissal shall be performed. Upon dismissal, a Dismissible can either be dismissed permanently, or become "undismissed" by other means.
Each Wagtail user has their own state of Dismissibles to keep track of which elements have been dismissed. We can store this state on the server, or completely on the client side. Each option has its own pros and cons.
Client-side
Pros
Cons
localStorage
is cleared).Server-side
Pros
Cons
After a few discussions with some of the core team members, we decided to store the state server-side.
Implementation design
The client
A Dismissible is created by adding a
data-wagtail-dismissible-id
attribute with a unique value (and optionally aw-dismissible
CSS class), e.g.data-wagtail-dismissible-id="menu-new-in-wagtail-4-1"
data-wagtail-dismissible-id="menu-editors-guide"
data-wagtail-dismissible-id="dashboard-new-in-wagtail-4-1" class="w-dismissible"
The initial HTML render of a Dismissible can either be dismissed or undismissed. We can differentiate between the two states using a data attribute in JS (i.e.
data-wagtail-dismissed
) and a class in CSS (i.e.w-dismissible--dismissed
).To dismiss a Dismissible, a dismiss toggle can be defined by adding a
data-wagtail-dismissible-toggle
attribute to either (a) the Dismissible itself; or (b) a descendant element of the Dismissible.Using plain JavaScript, a default
click
event handler will be added to all dismiss toggles. This default handler will:Note that this default handler will only be applied to elements rendered by the server. In the case of elements rendered by the client, e.g. React elements, the default handler will not be attached as they are not yet rendered in the DOM when the plain JavaScript initialisation code for Dismissibles is executed.
This means that Dismissibles implemented in React shall also implement their own dismiss toggles. This is not a problem, as it means we can handle the state in a way that's more natural to the client rendering framework. For example, upon clicking the dismiss toggle, instead of setting a
data-wagtail-dismissed
attribute and aw-dismissible--dismissed
CSS class, we can update a React/Redux state and completely hide the element from being rendered in the DOM.The server
To store the Dismissibles state on the server, we need to add a new field to the
UserProfile
model.As the number and names of available Dismissibles may change over time, we need the field to be able to store arbitrary data. The Dismissibles do not necessarily need to have validations enforced on the database level, as we can just assume a default state for each Dismissible that’s missing or misconfigured. For these reasons, we will store the Dismissibles state as a JSON object in a
JSONField
.Example object:
For the initial implementation, we will stick to only use boolean values.
When rendering a Dismissible in an HTML template, the server shall check the Dismissible's state for the user and decide the appropriate output, which may vary for each Dismissible. For example, a Dismissible may not need to be rendered at all if it's already been dismissed, in which case the server can skip the element entirely. Meanwhile, a different Dismissible may still need to be rendered upon dismissal, albeit differently (e.g. without a "new" badge), in which case the server needs to add the appropriate data attribute and CSS class.
In order to accept updates from the client, the server shall have a view that can update the state of Dismissibles for a user. For simplicity, we can have a single endpoint that can both return and update the Dismissibles state. Example:
The future
The concept of Dismissibles is meant to be simple (i.e. they are elements that can be dismissed/undismissed), but generic enough so that they can be extended and used for different use cases (instead of being the means to a one-off ad-hoc use case). Possible further developments:
Demo
Screen.Recording.2022-10-14.at.16.00.21.mov
Related links
Beta Was this translation helpful? Give feedback.
All reactions