Skip to content

feat: Implement add action for array items.#190

Closed
kozmaadrian wants to merge 14 commits intomainfrom
issue/181
Closed

feat: Implement add action for array items.#190
kozmaadrian wants to merge 14 commits intomainfrom
issue/181

Conversation

@kozmaadrian
Copy link
Contributor

@kozmaadrian kozmaadrian commented Feb 12, 2026

Ticket: #181

Screen.Recording.2026-02-16.at.10.27.20.am.mov

Note: An issue has been identified in the JSON & HTML converter. It currently does not properly handle fields containing nested arrays having arrays within arrays. As a result, when the page is refreshed, the data is not loaded correctly. Will be fixed with #193

Schema examples used:
simple-test-form-inlined.schema.json
simple-test-form.schema.json

@aem-code-sync
Copy link

aem-code-sync bot commented Feb 12, 2026

Hello, I'm the AEM Code Sync Bot and I will run some actions to deploy your branch and validate page speed.
In case there are problems, just click a checkbox below to rerun the respective action.

  • Re-run PSI checks
  • Re-sync branch
Commits

@kozmaadrian kozmaadrian marked this pull request as ready for review February 12, 2026 15:52
@mhaack
Copy link
Contributor

mhaack commented Feb 13, 2026

For the nested array/object combinations I would go with different labels following this rules:

  • leave object/primitive field > "+ Add Item"
  • parent array > "+ Add Item"

Comment on lines +83 to +104
async handleAddItem({ detail }) {
const { path, itemsSchema } = detail;
this.formModel.addArrayItem(path, itemsSchema);

// Update the view with the new values
this.formModel = this.formModel.clone();

// Persist the data
await this.formModel.saveHtml();
}

async handleRemoveItem({ detail }) {
const { path } = detail;
if (!this.formModel.removeArrayItem(path)) return;

// Update the view with the new values
this.formModel = this.formModel.clone();

// Persist the data
await this.formModel.saveHtml();
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I followed the existing pattern for consistency. However, in a future PR, it would be worth refactoring this approach, as the current implementation duplicates the save logic.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets keep it, could be future PR.

Comment on lines +106 to +110
case 'checkbox': inner = this.renderCheckbox(item); break;
case 'select': inner = this.renderSelect(item); break;
case 'text': inner = this.renderInput(item, 'text'); break;
case 'number': inner = this.renderInput(item, 'number'); break;
default: break;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a future PR, we could extract these into separate presentational components. The editor currently appears to handle too much logic.

* @param {string} path - The path to the array item (e.g., "data.items.[0]" or "data.items[0]")
* @returns {boolean} - True if the item was removed, false otherwise
*/
export function removeArrayItemByPath(obj, path) {
Copy link
Contributor Author

@kozmaadrian kozmaadrian Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the future, the utility methods responsible for patching the JSON data could be extracted into a separate utility file. This would improve readability and make the code easier to understand.

@mhaack
Copy link
Contributor

mhaack commented Feb 16, 2026

mhaack
mhaack previously approved these changes Feb 16, 2026
@mhaack
Copy link
Contributor

mhaack commented Feb 16, 2026

Make sure the lint error get fixed


getAddItemLabel(parent) {
const itemsSchema = parent.schema?.properties?.items;
const resolved = itemsSchema && resolvePropSchema(itemsSchema, this.formModel?.schema);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should not need this. You will already have the schema as part of the parent object.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I investigated the issue and found that the annotation process was failing to annotate array items when the schema defined the item structure using the $ref/$defs mechanism.

I’ve pushed a fix to this branch. However, after refactoring the annotation process to follow a schema-first approach, this specific fix is no longer necessary. The issue is already resolved in the refactored implementation and is handled in the new PR #198.

renderDeleteButton(item, index, isArrayItem) {
if (!isArrayItem) return nothing;
return html`
<remove-button
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not need to be a Web Component.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The form is built as a reactive UI using Lit components and the editor already manages a significant amount of logic, so to maintain clarity and separation of concerns, we should introduce well-isolated, “dumb” components. This will help keep the codebase less complex, more readable, and easier to test.

Based on my experience, as this UI continues to grow, centralizing all logic within the Editor will become increasingly difficult to maintain and, in the long term, nearly impossible to test properly.

import { LitElement, html } from 'da-lit';

const { default: getStyle } = await import('../../../../../utils/styles.js');
const style = await getStyle(import.meta.url);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is unnecessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This issue is linked to #190 (comment)


if (Array.isArray(propData)) {
const resolvedItemsSchema = resolvePropSchema(key, propSchema.items, fullSchema);
const resolvedItemsSchema = resolvePropSchema(propSchema.items, fullSchema);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes should be unnecessary. I think if we have issues getting the schema recursively once, we have architectural issues we should address first.

We should read the data once, combine with the schema to create an annotated object. If we find that object does not have what we need, we need to fix the underlying issues in that first read.

I also dislike the optional chaining here as it will mask issues down the road. We want something to fail and break in these early days so we can make sure our annotating is flawless since its so complex.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please find my findings and comment here #190 (comment)

this.updateHtml();
return true;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is good, but I also recognize I'm not sure how much logic we should put in this class.

I good guiding direction may be, "if it changes the data, it lives in the model."

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, but all of these operations ultimately modify the JSON data.

From my perspective, to keep things less complex, more testable, and more readable:

  • The model should represent a purely immutable state, without any HTML-related logic.
  • The HTML value should be generated only once during persistence and handled at a higher level of the application.
  • Updating the JSON should be implemented as a simple utility function that takes the current model, applies the necessary patches to the JSON, and then recreates the model as a new immutable state.

cc @mhaack

@kozmaadrian
Copy link
Contributor Author

Closing this PR in favour of the PR #198, which introduce an important core update to the annotation mechanism by moving to a schema-first annotation approach.

The new PR includes and adopts the changes from this PRR, which were necessary to determine and test the correct strategy during the refactoring process.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants