2.1.0 • Published 2 months ago

ember-exclaim v2.1.0

Weekly downloads
457
License
MIT
Repository
github
Last release
2 months ago

ember-exclaim Build Status

This addon allows apps to expose declarative, JSON-configurable custom UIs that are backed by Ember components.

Simple Examples

The building blocks available to an ember-exclaim UI are defined by the app it's used in, but the dummy application in this project contains implementations of several basic components that might be useful. For example, in the demo application, the following would render a header with some filler content below it:

{
  "$vbox": [
    { "$header": "Very Important Content" },
    { "$text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." }
  ]
}

Components can also read from and write back to an underlying data structure. For example, given this environment:

{
  "target": "world"
}

The following UI would render "Hello, world!":

{
  "$text": {
    "$join": ["Hello, ", { "$bind": "target" }, "!"]
  }
}

And something like this would render an input that would update the underlying value of target as the user made changes:

{
  "$input": { "$bind": "target" }
}

hello

Usage

The entry point to a UI powered by ember-exclaim is the {{exclaim-ui}} component. It accepts the following properties:

  • ui: an object containing configuration for the UI that should be rendered
  • env: a hash whose keys will be bindable from the ui config, to be read from and written to
  • implementationMap: a mapping of names in the ui config to information about their backing implementations
  • resolveFieldMeta(path): an optional action that will be invoked if a component calls env.metaForField(...)
  • onChange(envKeyOfChangedValue): an optional action that will be invoked when a value in the env changes
  • wrapper: an optional component or component name string that will wrap every rendered component in your UI configuration. The wrapper component will receive the unwrapped ComponentSpec as spec (more on ComponentSpec here), the Environment as env and the component's resolved config.
  • resolveMeta(path): an optional action that will be invoked if a component calls env.metaFor(...)

Each of these things is described in further detail below.

UI Configuration

The configuration for an ember-exclaim UI boils down to three special keys: $component, $helper and $bind.

$component

Components are used to render content to the screen in an ember-exclaim UI. The basic way to invoke a component is with a hash containing a $component key. The value of this key will be used to look up the underlying Ember component implementation according to the configured implementationMap, and all other keys in the hash will become that component's config. As a concrete example, consider the following usage of a hypothetical text component:

{
  "$component": "text",
  "content": "Hello"
}

This would invoke whatever Ember component is configured under the text key of the given implementationMap, passing it a hash of configuration that looks like { content: 'Hello' }.

$helper

Helpers are used to transform data for an ember-exclaim UI. Unlike components, they don't directly render anything. Helpers are invoked by inserting a hash containing a $helper key. The value of this key will be used to look up the underlying implementation for the helper.

As a concrete example, if you wanted to render an array of items as a comma separated list using the text component above, you might use a join helper:

{
  "$component": "text",
  "content": {
    "$helper": "join",
    "items": ["one", "two", "three"],
    "separator": ", "
  }
}

This would invoke whatever helper function is configured under the join key of the given implementationMap, passing it a hash of configuration that looks like { items: ['one', 'two', 'three'], separator: ', ' }.

$bind

To read and write values in the environment, the UI author can use $bind. For example, dynamically rendering whatever the value the configured environment has for greeting using the same text component described above might look like:

{
  "$component": "text",
  "content": { "$bind": "greeting" }
}

Similarly, an input component could write to the environment's greeting value as the user makes changes:

{
  "$component": "input",
  "value": { "$bind": "greeting" }
}

Note that component implementations might also expose $bindable values to their children, such as an each component that iterates an array and exposes each item in that array under a given name.

Shorthand Syntax

You may have noted that the examples in this section appear more verbose than those at the top of the document. By supplying the name of a shorthand property, components and helpers can be invoked using their name prefixed with a $ as a key for that property, skipping the $component or $helper key completely.

For example, the text component in the demo application declares its shorthand property to be content, making this:

{
  "$component": "text",
  "content": "Hello, world!"
}

Equivalent to this:

{ "$text": "Hello, world!" }

Similarly, if the join helper mentioned above declared items to be its shorthand property, then this:

{
  "$helper": "join",
  "items": [1, 2, 3],
  "separator": " + "
}

Would be equivalent to this:

{
  "$join": [1, 2, 3],
  "separator": " + "
}

The Environment

Keys on the given env object are what powers $bind directives in the configured UI. The object in question may be a simple POJO or something richer, like an Ember Data model. Avoid swapping the env object out wholesale, as this will cause unnecessary rerenders.

Note that $bind works with paths, too, so { $bind: 'foo.bar' } would access the bar key of the foo object in the environment.

The Implementation Map

The implementationMap given to {{exclaim-ui}} dictates what components it can render. It should be a hash whose keys are the component and helper names available for use in the UI config. The value for each key should itself be a hash describing the component or helper with that name.

  • componentPath (for components): the name to the Ember component to be invoked when this exclaim-ui component is used in the config, as you'd give it to the {{component}} helper
  • helper (for helper functions): a function that receives a config hash and env information and should return the output value for the helper
  • shorthandProperty (optional for both helpers and components): the name of a property that should be populated when shorthand notation is used for this component or helper (see above)

Metadata Resolution

The env property exposed to ember-exclaim components (see below for details) includes a metaForField(object, key) method that component implementations can use to discover more information about their bound values. For instance, an $input component might call metaForField(this, 'config.value') to discover validation rules for its bound value in order to display an error message to the user.

The resolveFieldMeta action on {{exclaim-ui}} designates how this metadata is discovered. It receives the full path in the environment of the value in question.

This action should return any relevant information available about the field at valuePath. Note that, if a component calls metaForField on a path that doesn't resolve to a field on the environment, the resolveFieldMeta action will not be invoked.

Implementing Components

The demo app for this repo contains a variety of simple component implementations that you can use as a starting point for building your own.

An ember-exclaim component implementation will receive two properties when rendered: config and env.

config

The config property of the implementing component will contain all other information supplied in the $component hash representing it in the UI config. Any $bind directives in that config will be automatically be resolved when they are get or set. As an example, consider a lightweight implementation of the input component mentioned above.

<input type="text" value={{config.value}} oninput={{action (mut config.value) value='target.value'}}>

When invoked as { "$component": "input", "value": {"$bind":"x"} } with x in the environment having the value 'hello', this component will receive the equivalent of { value: 'hello' } as its config, except that reading and writing config.value will redirect back to x on the environment.

env

The env property will contain an object representing the environment that the component is being rendered in. This object has two methods:

  • extend(hash): can be used to produce a new environment based on the original that additionally contains the values from the given hash
  • metaForField(object, key): takes an object and key, resolves the canonical path for that key in the environment, and then retrieves metadata for that path according to any configured resolveFieldMeta action on the owning {{exclaim-ui}} component

Rendering Children

In many cases, components may want to accept configuration for subcomponents that they can render under different circumstances, such as an if component that conditionally renders some content, or an each component that renders the same child multiple times against different values. Implementations can accomplish this by {{yield}}ing the configuration for the child component.

For example, the vbox component in the demo application applies a class with flex-flow: column to its root element and then simply renders all its children directly beneath:

{{#each config.children as |child|}}
  {{yield child}}
{{/each}}

By default, children will inherit the environment of their parent. This environment can be overridden by passing a new env value as a second parameter to {{yield}}, typically obtained by calling extend on the base environment (see above). Check the implementation of each and let in the demo app for examples of how this can be used.

Implementing Helpers

The demo app for this repo contains a handful of helper implementations that you can use as a starting point for building your own.

An ember-exclaim helper implementation is simply a function that takes two arguments, config and env, which are the same two values described for components above. The value returned when this function is called will be the ultimate value of the { $helper: ... } hash in the UI configuration.

2.1.0

2 months ago

2.0.0

2 months ago

1.4.0

2 months ago

1.3.2

3 months ago

1.3.1

2 years ago

1.3.0

5 years ago

1.2.3

5 years ago

1.2.2

5 years ago

1.2.1

5 years ago

1.2.0

6 years ago

1.1.1

6 years ago

1.1.0

7 years ago

1.0.0

7 years ago