Skip to content

Instantly share code, notes, and snippets.

@gossi
Last active September 25, 2020 15:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gossi/c3d6fded03be001d8b9763a8219b0a5e to your computer and use it in GitHub Desktop.
Save gossi/c3d6fded03be001d8b9763a8219b0a5e to your computer and use it in GitHub Desktop.
Ember <head> unification

I recently was looking into an ember addon, that helped me with managing content within <head>. Wasn't that easy and lead to this gist instead of me picking one of the provided addons. Simply, because there were too much choices and each required research if it solves my problem and having too many tabs open, the information began to blur.

There are plenty of ember addons to manipulate contents within <head> with more or less features. Lots of them have good functionality but the APIs are too much tighly coupled to routes, services or components. That makes it hard to use the good functionality when you don't use the API entrypoint.

The idea is to unify this landscape and have one community standard solution to this problem.

Here are addons that help with this (partly from Header Content category from Emberobserver) and googling:

  • ember-page-title
  • ember-cli-head
  • ember-head
  • ember-cli-document-title-northm
  • ember-cli-document-title
  • @surkus/ember-cli-document-title
  • ember-meta-meta
  • mber-head
  • ember-seo-meta-tags
  • ember-frost-page-title
  • ember-meta

And related:

  • ember-crumbly
  • ember-breadcrumbs
  • @bagaar/ember-breadcrumbs

I added breadcrumbs here, too since for some they have breadcrumbs somewhere on the page and the same hierarchy in their document title. In both cases it's the same data being displayed in two different locations.

Given that information a person needs to research first on what to use and even then hope it fits their use-case.

I think we - as a community - can do better here and come up with one solution to use, which is the core of all it and then have various connectors to that, one for each given use-case. That way, the friction of which addon to use is minimalized and offers more adopters to ones individual implementations.

I think ember-cli-deploy has shown how this can work out beautifully for the community, and my hope is to have something similar for <head> contents.

I thought about how to continue with that. Here are some ideas:

  • If that would be an ember-core feature, it's easy it would start with an rfc. Starting and rfc about this, is kinda abusing the rfc repo.
  • Open an issue at one of the repos and link from everywhere else.
  • Ask for a discord channel (hard to write stuff down)
  • Use gists

Honestly I'm not happy with either of these solutions but maybe a combined effort here. What if this situation is a nice start for community rfcs. It would extend the positive effects of rfcs for ember-core to focused problem from the community that are out of scope of ember-core. This would help to channel and focus the work into a group of people working on the same thing instead of me being "I create another addon, because the existing ones don't fit my problem". At the same time, create a discord channel to use this for direct communication around this. All-in-all it would even work in harmony with embers adopted addon initiative.

That would sound to me as an environment where I would start describing the problem, outlining solutions to it, request feedback from the community to polish stuff before going into development. Just with the <head> problem at hand 😉 :)

Given the problem stated above, here is an idea to go with:

Central Content Repository

For the <head> element, there is a couple of content to be put into. Having a central repository is key. This repository is connected to the markup representing these information in the dom. Whenever there are changes to the repository, these will be reflected by the markup immediately. This is the gateway from data-entry level to html. The central repository is by all means not connected to any data-entry point, yet open to receive data from multiple data-entry points.

The information may be represented in their semantic nature, eg. title or description but also in their technical nature, e.g. <link rel="next" ... >. Both options should be possible to modify with preference to semantic properties. The semantics should relate to established <meta> or <link> properties respectively og:* or twitter:* fields.

The central repository can be a service that holds these information, as most of embers constructs have access to it. An initial idea to that was the following:

import Service from '@ember/service';
import { tracked } from '@glimmer/tracking';

import TrackedArray from 'tracked-array';

interface Link {
  href?: string;
  type?: string;
  rel?: string;
  integrity?: string;
  media?: string;
  title?: string;
}

interface Meta {
  name?: string;
  httpEquiv?: string;
  charset?: string;
  content?: string;
}

/**
 * Class to control information on the page, mainly everything that goes into
 * the `<head>` tag. Maybe it be `<title>`, `<meta>` or `<link>`.
 *
 * Based on https://github.com/keeko/framework/blob/master/src/page/Page.php
 */
export default class PageService extends Service {
  /**
   * The title for the current page
   */
  @tracked public title?: string;

  /**
   * The suffix for the `title`
   */
  @tracked public titleSuffix?: string;

  /**
   * The prefix for the `title`
   */
  @tracked public titlePrefix?: string;

  /**
   * The default title (when no `title` is set).
   */
  @tracked public defaultTitle?: string;

  public links: Link[] = new TrackedArray();
  public meta: Meta[] = new TrackedArray();

  /**
   * Returns the full title. Means when there is a `title` set it will be
   * wrapped in prefix and suffix. If there is no `title` then `defaultTitle` is
   * returned.
   */
  public get fullTitle(): string {
    if (this.title) {
      return `${this.titlePrefix} ${this.title} ${this.titleSuffix}`.trim();
    }

    return this.defaultTitle || '';
  }

  public addLink(link: Link) {
    this.links.push(link);
  }

  public addMeta(meta: Meta) {
    this.meta.push(meta);
  }

  /**
   * Reset to default contents
   */
  public reset() {
    this.title = '';
    this.links.clear();
    this.meta.clear();
  }
}

Resetting Content

Many ideas are coming from server-rendered pages, where those APIs are there to exactly render one page as is. With SPA, these pages change at runtime. Different scenarios require different strategies on resetting the content after a certain event appeared (mostly when a new route is entered or another data from the API is fetched). Having individual strategies ready and a nice configuration to adjust it is key to support multiple specification.

Different Data Entry Points

The content may be entered programmatically, per configuartion or even declaratively in some form of components. Some components are here to provide them at each route to create a hierarchic data structure, e.g. ember-page-title. Data entries is also here to map between two different data formats. For example an article or blog post may have an abstract that would then be forwarded to the content repository as description.

Data for Other Consumers

Imagine, somebody used the <Title/> approach from ember-page-title to construct the hierarchy of a page. The same data is super-relevant to construct breadcrumb navigation from it. A breadcrumb-connector can safely be used to display data already gathered somewhere else (instead of doing the same data-aggregation a second time).

What else?

Well, fastboot compatibility 😉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment