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

Support HTML templates inheriting hierarchical, arbitrary context #434

Open
towerofnix opened this issue Mar 20, 2024 · 0 comments
Open

Comments

@towerofnix
Copy link
Member

We'd like to pass contextual information through one or multiple layers of HTML templates. There are various interface surfaces we could take for this, but the behavior is all that's essential. For example:

Template generatePageLayout
  {context: {[Symbol.for('hsmusic.contentContext.absoluteURLs']: true}}
  {generate: () => [
    Template linkThing { ... }
    Template transformContent {
      {context: {[Symbol.for('hsmusic.contentContext.whatever']: 'anything']}}
      {generate: () => [
        Template fooBar {}
      ]}
    }
  ]}
}

We want the content function for template linkThing to have access to absoluteURLs: true, and the content function for template fooBar to have access to absoluteURLs: true, whatever: 'anything'.

Content functions generally construct a stationery which in turn instantiates a template. At this point we don't have access to HTML-hierarchical context (the template exists in a vacuum), but the template's (i.e. stationery's) content function won't be evaluated until the template is actually placed in a context (i.e. parent) — and at this point the template should be able to inherit context, ala:

fulfilledDependencies.html.stationery({
  ...

  content(slots, context) {
    const args = [slots._cfArg1, slots._cfArg2];

    /* do something with `context` to expose its presence
     * to underlying `generate` function, etc
     */

    return callUnderlyingGenerate(args, slots);
  },
});

The primary concern of content functions are to dictate what they have access to and will depend on, so where context is required within content, a new top-level property like extraDependencies is probably appropriate.

// fooBar.js
export default {
  extraDependencies: ['html'],  // elements that require context must require 'html'
  contextDependencies: ['whatever'],

  generate: (context) =>
    `I'm feeling ${context.whatever.toUpperCase()}`,
};

Providing context is the bigger question, because context must be provided before children templates are resolved. At the moment this should be able to come after or during the actual call to generate, as templates aren't usually resolved until a page is actually stringified, but they can be resolved earlier in certain contexts. It may be appropriate to always provide context as part of a separate step, just to avoid that headache, and to make it compositionally accessible in case the structure of content function evaluation is changed later.

// generatePageLayout.js
export default {
  extraDependencies: ['html'],  // elements that provide context must require 'html'
  contextDependencies: ['absoluteURLs'],

  slots: {
    routeAbsoluteURLs: {type: 'boolean', default: false},
    content: {type: 'html', mutable: false},
  },

  // provided: data, slots
  // not provided: sprawl, query, relations
  context: (slots) => ({
    absoluteURLs: slots.routeAbsoluteURLs,
  }),

  generate: (slots) => 
    slots.content,
};

Although internally context is passed through layers of templates, this generally won't be obvious to content functions themselves (which are built on templates but mostly abstract that behavior into a more generally palatable form). We'd ideally be able to resolve a slotted template (if necessary) during generate without having to manually contextualize the template in the context of this content function (and thus also its parents).

Resolving during generate without context (aka deliberately isolating a template before resolving it) is left as an unknown at the moment since we can't think of relevant use cases, but we'd probably like to use a new interface at where the resolving actually happens, e.g. html.resolveWithoutContext.

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

No branches or pull requests

1 participant