Skip to content

How Overlays Work

Erik Hetzner edited this page Apr 18, 2018 · 2 revisions

How Overlays Work

Four components are used:

  1. ember-wormhole
  2. overlay-animate
  3. overlay-fullscreen-layout
  4. overlay-fullscreen

Breaking responsibilities out into small components will allow us to make changes where needed for the special snowflake overlays.

ember-wormhole

A third-party component that renders content outside of the local template context. This allows us to put overlays at the root of the DOM tree and worry less about z-index, etc. This is similar to how we were rendering overlays into an outlet at the root: `{{outlet "overlay"}}`

overlay-animate

Component that just animates overlays in and out. It uses Velocity.js and maybe it's just me but the frame rate seems higher.

overlay-fullscreen-layout

This component is (mostly) for styling. Wraps fullscreen overlay markup and styles around a child context. In the future we could have `overlay-card-layout`, `task-accordion-layout`, etc.

overlay-fullscreen

Convenience wrapper. Brings together `ember-wormhole`, `overlay-animate` and `overlay-fullscreen-layout`. Responsible for adding/removing them from the DOM.

Basic Example

Chunks of functionality are simply components. At most they only need to know how to `close`. I'm hoping this will help in many ways like: reduce markup, make testing easier, make future development easier, etc. Some bugs/hacks/data-cleanup can be avoided because we are no longer using controllers which are singletons. And bonus: one step closer to Ember 2.0.

/*
-- controller.js --
Before, overlay display was managed at the top application route level.
While that allowed for simple action calls: `showOverlay` and `closeOverlay`,
people found the action bubbling confusing and harder to debug.
While moving previous overlays to this new style I found having a much more
local scope helped with that issue. Also, no more odd controller lookups for
setting data; you just set properties like any other component (see template).
*/

showMyOverlay: false,

actions: {
showMyOverlay() {
this.set('showMyOverlay', true);
}

hideMyOverlay() {
this.set('showMyOverlay', false);
}
}

{{!-- template.hbs --}}

{{#overlay-fullscreen visible=showMyOverlay
outAnimationComplete=(action "hideMyOverlay")
title="My Stuff"
overlayClass="my-stuff-overlay"
as |overlay|}}
{{my-stuff close=(action overlay.animateOut)}}
{{/overlay-fullscreen}}

The lifecycle should go like this:

  1. User somehow triggers `showMyOverlay` in the controller
  2. `overlay-fullscreen` `visible` property is changed, rendering everything in its template
  3. `ember-wormole` renders `animate-overlay` with the component at the DOM root
  4. `animate-overlay` animates in the component
  5. ...
  6. User somehow triggers `close` action (save button, escape key, click "X", etc.)
  7. `animate-overlay` animates out the component
  8. `animate-overlay` calls `outAnimationComplete` callback
  9. This callback from the controller toggles a property
  10. `overlay-fullscreen` `visible` property is changed, removing everything in its template from the DOM

All the nesting ends up looking like this:

{{#overlay-fullscreen}}
{{#if visible}}
{{#ember-wormhole}}
{{#overlay-animate}}
{{#overlay-fullscreen-layout}}
{{my-stuff}}
{{/overlay-fullscreen-layout}}
{{/overlay-animate}}
{{/ember-wormhole}}
{{/if}}
{{/overlay-fullscreen}

Data

Sometimes overlays need data. I think the best practice should be: use controllers to load data and pass that into the component. Keep the components as dumb as possible. There are a couple components in this PR that lookup the DS.Store and load data themselves.

/* -- controller.js -- */

showMyOverlay: false,
stuffIsLoading: false,
stuff: [],

actions: {
showMyOverlay() {
this.setProperties({
stuffIsLoading: true,
showMyOverlay: true
});

this.doAjax().then(stuff => {
this.setProperties({
stuffIsLoading: false,
stuff: stuff
});
});
}

hideMyOverlay() {
this.set('showMyOverlay', false);
}
}

{{!-- template.hbs --}}

{{#overlay-fullscreen visible=showMyOverlay
outAnimationComplete=(action "hideMyOverlay")
title="My Stuff"
overlayClass="my-stuff-overlay"
as |overlay|}}
{{my-stuff isLoading=stuffIsLoading
data=stuff
close=(action overlay.animateOut)}}
{{/overlay-fullscreen}}

Clone this wiki locally