Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ui: Outlet Loading #8779

Merged
merged 19 commits into from
Oct 1, 2020
Merged

ui: Outlet Loading #8779

merged 19 commits into from
Oct 1, 2020

Conversation

johncowen
Copy link
Contributor

@johncowen johncowen commented Sep 30, 2020

The Route loading logic in the UI has historically been spread out amongst various pieces of the application, in the AppView component (sometimes), the Application route and the Feedback service.

The sprawly nature of this along with the fact that you could easily forget to set certain properties when creating new routes meant it was more than straightforwards for loading to 'just work' especially in sub-routes and sub-sub-routes etc. We took a look at using model hooks and loading.hbs templates, but both options had their downsides, and both required extra things to be done in order for loading to 'just work' whether adding hooks or files.

We realized that if each {{outlet}} was wrapped in a <Outlet data-outlet="dc">{{outlet}}</Outlet> component, dc being the name/identifier of the current Route/template (the Route.routeName), then we could automatically figure out which outlet in the Route hierarchy was to be loaded by traversing up the tree of outlets and comparing the clicked route name with the name of the outlet.

For example:

Clicking: dc.services.show.healthcheck

- Outlet: `application`
  - Outlet: `dc`
    - Outlet: `dc.services.show` <== dc.services.show.healthcheck - Match, this outlet will be loading
      - Outlet: `dc.services.show.intentions` <== dc.services.show.healthcheck - No Match

Clicking: dc.services.index

- Outlet: `application`
  - Outlet: `dc` <== dc.services.index - Match, this outlet will be loading
    - Outlet: `dc.services.show` <== dc.services.index - No Match
      - Outlet: `dc.services.show.intentions` <== dc.services.index - No Match

In our case we add a data-state="loading" attribute to the outlet when we find the match. Once the router has finished transitioning, we set the state of the loading outlet back to idle.

Using this attribute we can do all of out route loading visuals via CSS by hiding and showing the unloaded and subsequently loaded outlet, which massively reduces the spread of code used to control loading state in the application. Future work could involve a more subtle transition between loading pages, if that was something we wanted to do.

The one downside is that the only way to 'name' the outlets correctly is to have access to the Route.routeName property, something that is only available from within the Route. We tried various methods of retrieving this and passing it through to the template, and eventually settled on using a base Route class, which all of our Routes now inherit from. The approach means:

  1. We aren't using reopen (not Octane friendly)
  2. We don't have to manually set the routeName in every Route or template.
  3. We have to remember to inherit from this base class for every route, AND wrap outlets in the Outlet component, something we can probably mitigate somewhat by adding custom blueprints in the future.

Additionally here we took advantage of the work to re-work how we target individual pages via CSS.

Previously we used the AppView component to set classNames on the root of the document (in every single template), say for example on the kv edit page we would add class="template-kv template-edit". We've replaced this by automatically adding the name on the current route as a data attribute on the root of the document using the application outlet. For example html data-route="dc.kv.edit", which again we can use to target individual pages via CSS without having to remember to do anything in our templates. Additionally, using selectors such as html[data-route$='.edit'] etc. lets you can target all edit pages.

@johncowen johncowen added the theme/ui Anything related to the UI label Sep 30, 2020
Copy link
Contributor

@kaxcode kaxcode left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a lot, but it's a full implementation so it's understandable. There seem to be some merge conflicts. Once those are fixed :shipit:.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
theme/ui Anything related to the UI
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants