From f6a4de480560e5983ecaef6fcc3a6a5c5240a5c3 Mon Sep 17 00:00:00 2001 From: Joffrey JAFFEUX Date: Fri, 13 Dec 2024 15:43:32 +0100 Subject: [PATCH] DEV: adds support for nested collections and objects (#30265) Collections were an existing concept in FormKit but didn't allow nesting. You can now do infinite nesting: ```gjs
``` On top of this a new component has been added: `Object`. It allows you to represent objects in your form data. Collections are basically handling arrays, and Objects are objects. This is useful if you form data has this shape for example: ```javascript { foo: { bar: 1, baz: 2 } } ``` This can now be mapped in your form using this syntax: ```gjs
``` Objects accept nested collections and nested objects. Just like Collections. A small addition has also been made to `Collection`, they now support a custom `@tagName`, it's useful if each item of your collection is the row of a table for example. --- .../app/form-kit/components/fk/collection.gjs | 88 ++++++++++++----- .../app/form-kit/components/fk/field-data.gjs | 12 +-- .../app/form-kit/components/fk/field.gjs | 2 +- .../app/form-kit/components/fk/form.gjs | 12 +++ .../app/form-kit/components/fk/object.gjs | 71 ++++++++++++++ .../components/form-kit/collection-test.gjs | 94 ++++++++++++++++++- .../components/form-kit/object-test.gjs | 84 +++++++++++++++++ .../common/form-kit/_collection.scss | 8 +- .../stylesheets/common/form-kit/_index.scss | 1 + .../stylesheets/common/form-kit/_object.scss | 3 + 10 files changed, 338 insertions(+), 37 deletions(-) create mode 100644 app/assets/javascripts/discourse/app/form-kit/components/fk/object.gjs create mode 100644 app/assets/javascripts/discourse/tests/integration/components/form-kit/object-test.gjs create mode 100644 app/assets/stylesheets/common/form-kit/_object.scss diff --git a/app/assets/javascripts/discourse/app/form-kit/components/fk/collection.gjs b/app/assets/javascripts/discourse/app/form-kit/components/fk/collection.gjs index 1642c1fbcfb..2b579ab55fd 100644 --- a/app/assets/javascripts/discourse/app/form-kit/components/fk/collection.gjs +++ b/app/assets/javascripts/discourse/app/form-kit/components/fk/collection.gjs @@ -1,40 +1,78 @@ import Component from "@glimmer/component"; -import { hash } from "@ember/helper"; -import { action } from "@ember/object"; +import { concat, hash } from "@ember/helper"; +import { action, get } from "@ember/object"; import FKField from "discourse/form-kit/components/fk/field"; +import FKObject from "discourse/form-kit/components/fk/object"; +import element from "discourse/helpers/element"; export default class FKCollection extends Component { @action remove(index) { - this.args.remove(this.args.name, index); + this.args.remove(this.name, index); } - get collectionValue() { - return this.args.data.get(this.args.name); + get collectionData() { + return this.args.data.get(this.name); + } + + get name() { + return this.args.parentName + ? `${this.args.parentName}.${this.args.name}` + : this.args.name; + } + + get tagName() { + return this.args.tagName || "div"; } } diff --git a/app/assets/javascripts/discourse/app/form-kit/components/fk/field-data.gjs b/app/assets/javascripts/discourse/app/form-kit/components/fk/field-data.gjs index 3d1fa93d64f..7a3be6c168b 100644 --- a/app/assets/javascripts/discourse/app/form-kit/components/fk/field-data.gjs +++ b/app/assets/javascripts/discourse/app/form-kit/components/fk/field-data.gjs @@ -146,13 +146,11 @@ export default class FKFieldData extends Component { throw new Error("@name can't include `.` or `-`."); } - return ( - (this.args.collectionName ? `${this.args.collectionName}.` : "") + - (this.args.collectionIndex !== undefined - ? `${this.args.collectionIndex}.` - : "") + - this.args.name - ); + if (this.args.parentName) { + return `${this.args.parentName}.${this.args.name}`; + } + + return this.args.name; } /** diff --git a/app/assets/javascripts/discourse/app/form-kit/components/fk/field.gjs b/app/assets/javascripts/discourse/app/form-kit/components/fk/field.gjs index dafc2c2b2c9..575dd005c1a 100644 --- a/app/assets/javascripts/discourse/app/form-kit/components/fk/field.gjs +++ b/app/assets/javascripts/discourse/app/form-kit/components/fk/field.gjs @@ -57,7 +57,7 @@ export default class FKField extends Component { @registerField={{@registerField}} @format={{@format}} @disabled={{@disabled}} - @collectionName={{@collectionName}} + @parentName={{@parentName}} as |field| > diff --git a/app/assets/javascripts/discourse/app/form-kit/components/fk/form.gjs b/app/assets/javascripts/discourse/app/form-kit/components/fk/form.gjs index 8b9e76e852e..cc93f826bd5 100644 --- a/app/assets/javascripts/discourse/app/form-kit/components/fk/form.gjs +++ b/app/assets/javascripts/discourse/app/form-kit/components/fk/form.gjs @@ -14,6 +14,7 @@ import FKErrorsSummary from "discourse/form-kit/components/fk/errors-summary"; import FKField from "discourse/form-kit/components/fk/field"; import FKFieldset from "discourse/form-kit/components/fk/fieldset"; import FKInputGroup from "discourse/form-kit/components/fk/input-group"; +import FKObject from "discourse/form-kit/components/fk/object"; import Row from "discourse/form-kit/components/fk/row"; import FKSection from "discourse/form-kit/components/fk/section"; import FKSubmit from "discourse/form-kit/components/fk/submit"; @@ -289,6 +290,17 @@ class FKForm extends Component { unregisterField=this.unregisterField triggerRevalidationFor=this.triggerRevalidationFor ) + Object=(component + FKObject + errors=this.formData.errors + addError=this.addError + data=this.formData + set=this.set + registerField=this.registerField + unregisterField=this.unregisterField + triggerRevalidationFor=this.triggerRevalidationFor + remove=this.remove + ) InputGroup=(component FKInputGroup errors=this.formData.errors diff --git a/app/assets/javascripts/discourse/app/form-kit/components/fk/object.gjs b/app/assets/javascripts/discourse/app/form-kit/components/fk/object.gjs new file mode 100644 index 00000000000..3b809d09ad9 --- /dev/null +++ b/app/assets/javascripts/discourse/app/form-kit/components/fk/object.gjs @@ -0,0 +1,71 @@ +import Component from "@glimmer/component"; +import { hash } from "@ember/helper"; +import FKCollection from "discourse/form-kit/components/fk/collection"; +import FKField from "discourse/form-kit/components/fk/field"; + +export default class FKObject extends Component { + get objectData() { + return this.args.data.get(this.name); + } + + get name() { + return this.args.parentName + ? `${this.args.parentName}.${this.args.name}` + : this.args.name; + } + + get keys() { + return Object.keys(this.objectData); + } + + entryData(name) { + return this.objectData[name]; + } + + +} diff --git a/app/assets/javascripts/discourse/tests/integration/components/form-kit/collection-test.gjs b/app/assets/javascripts/discourse/tests/integration/components/form-kit/collection-test.gjs index 87ad6ca58e8..50994910f5e 100644 --- a/app/assets/javascripts/discourse/tests/integration/components/form-kit/collection-test.gjs +++ b/app/assets/javascripts/discourse/tests/integration/components/form-kit/collection-test.gjs @@ -3,11 +3,22 @@ import { click, render } from "@ember/test-helpers"; import { module, test } from "qunit"; import Form from "discourse/components/form"; import { setupRenderingTest } from "discourse/tests/helpers/component-test"; +import formKit from "discourse/tests/helpers/form-kit-helper"; module("Integration | Component | FormKit | Collection", function (hooks) { setupRenderingTest(hooks); - test("default", async function (assert) { + test("@tagName", async function (assert) { + await render(); + + assert.dom("tr.form-kit__collection").exists(); + }); + + test("field", async function (assert) { await render(