From 7f2e42c8265b57e76bf966589b5f00b7346809c7 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Mon, 7 Aug 2023 13:27:26 +0100 Subject: [PATCH] DEV: Introduce dynamic wrapper attributes in RenderGlimmer (#22991) --- .../discourse/app/components/mount-widget.hbs | 5 ++- .../discourse/app/widgets/render-glimmer.js | 27 ++++++++++++++- .../components/widgets/render-glimmer-test.js | 33 +++++++++++++++++++ 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/app/components/mount-widget.hbs b/app/assets/javascripts/discourse/app/components/mount-widget.hbs index 0881f23660e..d23784b3468 100644 --- a/app/assets/javascripts/discourse/app/components/mount-widget.hbs +++ b/app/assets/javascripts/discourse/app/components/mount-widget.hbs @@ -1,5 +1,8 @@ {{#each this._childComponents as |info|}} {{#in-element info.element insertBefore=null}} - + {{/in-element}} {{/each}} \ No newline at end of file diff --git a/app/assets/javascripts/discourse/app/widgets/render-glimmer.js b/app/assets/javascripts/discourse/app/widgets/render-glimmer.js index 23caaa6f73c..f39f7a1027a 100644 --- a/app/assets/javascripts/discourse/app/widgets/render-glimmer.js +++ b/app/assets/javascripts/discourse/app/widgets/render-glimmer.js @@ -4,6 +4,8 @@ import { tracked } from "@glimmer/tracking"; import { assert } from "@ember/debug"; import { createWidgetFrom } from "discourse/widgets/widget"; +const INITIAL_CLASSES = Symbol("RENDER_GLIMMER_INITIAL_CLASSES"); + /* This class allows you to render arbitrary Glimmer templates inside widgets. @@ -71,8 +73,14 @@ createWidget("my-widget", { this.scheduleRerender(); }, }); -``` +To dynamically control the attributes of the wrapper element, a helper function is provided as an argument to your hbs template. +To use this via a template, you can do something like this: +``` +hbs`{{@setWrapperElementAttrs class="some class value" title="title value"}}` +``` +If you prefer, you can pass this function down into your own components, and call it from there. Invoked as a helper, this can +be passed (auto-)tracked values, and will update the wrapper element attributes whenever the inputs. */ export default class RenderGlimmer { @@ -104,6 +112,7 @@ export default class RenderGlimmer { const [type, ...classNames] = this.renderInto.split("."); this.element = document.createElement(type); this.element.classList.add(...classNames); + this.element[INITIAL_CLASSES] = classNames; } this.connectComponent(); return this.element; @@ -147,11 +156,27 @@ export default class RenderGlimmer { element, component, @tracked data: this.data, + setWrapperElementAttrs: (attrs) => + this.updateElementAttrs(element, attrs), }; this.parentMountWidgetComponent.mountChildComponent(this._componentInfo); } + updateElementAttrs(element, attrs) { + for (let [key, value] of Object.entries(attrs)) { + if (key === "class") { + value = [element[INITIAL_CLASSES], value].filter(Boolean).join(" "); + } + + if ([null, undefined].includes(value)) { + element.removeAttribute(key); + } else { + element.setAttribute(key, value); + } + } + } + get parentMountWidgetComponent() { return this.widget?._findView() || this._emberView; } diff --git a/app/assets/javascripts/discourse/tests/integration/components/widgets/render-glimmer-test.js b/app/assets/javascripts/discourse/tests/integration/components/widgets/render-glimmer-test.js index 6d9df71943a..5f088e20f32 100644 --- a/app/assets/javascripts/discourse/tests/integration/components/widgets/render-glimmer-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/widgets/render-glimmer-test.js @@ -133,6 +133,11 @@ module("Integration | Component | Widget | render-glimmer", function (hooks) { "div.my-wrapper", hbs`{{@data.attr1}}` ); + registerWidgetShim( + "render-glimmer-test-wrapper-attrs", + "div.initial-wrapper-class", + hbs`{{@setWrapperElementAttrs class=(concat-class "static-extra-class" @data.extraClass) data-some-attr=@data.dataAttrValue}}` + ); }); hooks.afterEach(function () { @@ -140,6 +145,7 @@ module("Integration | Component | Widget | render-glimmer", function (hooks) { this.registry.unregister("widget:toggle-demo-widget"); this.registry.unregister("component:demo-component"); deleteFromRegistry("render-glimmer-test-shim"); + deleteFromRegistry("render-glimmer-test-wrapper-attrs"); }); test("argument handling", async function (assert) { @@ -315,4 +321,31 @@ module("Integration | Component | Widget | render-glimmer", function (hooks) { assert.dom("div.my-wrapper span.shim-content").exists(); assert.dom("div.my-wrapper span.shim-content").hasText("val1"); }); + + test("setWrapperElementAttrs API", async function (assert) { + await render( + hbs`` + ); + + assert.dom("div.initial-wrapper-class").exists(); + assert + .dom("div.initial-wrapper-class") + .hasAttribute("class", "initial-wrapper-class static-extra-class"); + assert + .dom("div.initial-wrapper-class") + .doesNotHaveAttribute("data-some-attr"); + + this.set("extraClass", "dynamic-extra-class"); + this.set("dataAttrValue", "hello world"); + + assert + .dom("div.initial-wrapper-class") + .hasAttribute( + "class", + "initial-wrapper-class static-extra-class dynamic-extra-class" + ); + assert + .dom("div.initial-wrapper-class") + .hasAttribute("data-some-attr", "hello world"); + }); });