Embedding in a Framework¶
The CEE is a custom element, so it works with any framework that supports Web Components. Three integration rules apply everywhere:
- Load the bundle as a script. It registers the custom element and exports no runtime value.
- Assign objects as properties. Attribute bindings convert objects to strings and cannot supply a template, instance, or configuration.
- Replace the element to load a different artifact. Configuration and artifact inputs are accepted once. When the template or instance changes, give the element a new framework identity.
eventHandler is the exception to the third rule: a later assignment replaces
the previous handler.
Angular¶
Allow the custom element in the module or standalone component that renders it:
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
@NgModule({
declarations: [MetadataPageComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class MetadataPageModule {}
Use Angular property bindings, shown by square brackets:
@for (artifact of artifacts; track artifact.key) {
<cedar-embeddable-editor
#cee
[config]="ceeConfig"
[templateObject]="artifact.template"
[instanceObject]="artifact.instance"
(change)="onChange($event)"
></cedar-embeddable-editor>
}
The tracked key must identify both the template and the instance. This ensures that selecting another instance of the same template creates a new element:
artifacts: { key: string; template: object; instance?: object }[] = [];
show(templateId: string, template: object, instanceId: string | null, instance?: object): void {
this.artifacts = [{ key: `${templateId}:${instanceId ?? 'new'}`, template, instance }];
}
Read the metadata through the view reference:
@ViewChild('cee') ceeRef?: ElementRef<CedarEmbeddableEditorElement>;
save(): void {
const instance = this.ceeRef?.nativeElement.currentMetadata;
// Save the instance.
}
Copy the bundle as an Angular asset:
"assets": [
{
"glob": "cedar-embeddable-editor.js",
"input": "node_modules/cedar-embeddable-editor",
"output": "/assets"
}
]
Then load it from index.html:
<script src="assets/cedar-embeddable-editor.js"></script>
React¶
Use a ref to assign the element's properties:
import { useEffect, useRef } from 'react';
const config = {
terminologyBaseUrl: 'https://terminology.metadatacenter.org/',
bridgeBaseUrl: 'https://bridge.metadatacenter.org/',
};
export function MetadataEditor({ template, instance, onReady }) {
const ref = useRef(null);
useEffect(() => {
let cancelled = false;
customElements.whenDefined('cedar-embeddable-editor').then(() => {
const cee = ref.current;
if (cancelled || !cee) return;
cee.config = config;
cee.templateAndInstanceObject = { templateObject: template, instanceObject: instance };
onReady?.(cee);
});
return () => {
cancelled = true;
};
}, [template, instance, onReady]);
return <cedar-embeddable-editor ref={ref} />;
}
The parent supplies a key that changes with either half of the artifact:
<MetadataEditor
key={`${templateId}:${instanceId ?? 'new'}`}
template={template}
instance={instance}
onReady={setEditor}
/>
Keying only on templateId is insufficient because users commonly open several
instances of one template. Load the bundle from public/index.html, or copy it
to public/ during the build.
Ember¶
Assign the properties from a modifier or component insertion hook:
import Component from '@glimmer/component';
import { action } from '@ember/object';
export default class MetadataEditorComponent extends Component {
@action
async setUpEditor(element) {
await customElements.whenDefined('cedar-embeddable-editor');
element.config = this.args.config;
element.templateObject = this.args.template;
}
}
<cedar-embeddable-editor {{did-insert this.setUpEditor}} />
{{did-insert}} runs once for each element. If the
artifact changes, use a keyed or conditional block that creates a new element.
Other Frameworks¶
Use the same sequence in Vue, Svelte, or another framework:
- obtain a reference to the element;
- wait for
customElements.whenDefined('cedar-embeddable-editor'); - assign
configand the artifact properties once; and - read
currentMetadatawhen the application needs the instance.
Vue can use a template ref and :key; Svelte can use bind:this and a keyed
block. Frameworks that reject unknown tags must be configured to allow the CEE
element.
Keep the Published Bundle Intact¶
The npm bundle is already optimized. Copy it as a static asset rather than
including it in the host application's minification pipeline. A second
minification pass can corrupt class self-references and cause a ReferenceError
before the element is registered.
Use these checks in the browser console when diagnosing an empty editor:
customElements.get('cedar-embeddable-editor'); // A constructor, not undefined.
window.cedarEmbeddableEditorVersion; // The registered bundle version.
Multiple Editors and Bundles¶
Multiple CEE elements on one page are independent. Each has its own configuration, template, instance, and language.
Loading the bundle more than once is also safe: the first copy registers the
component and later copies stand down. window.cedarEmbeddableEditorVersion
identifies the registered version.
Runnable Angular, React, and Ember examples are available in metadatacenter/cedar-component-demo.