Skip to content

Commit

Permalink
feat: pass custom editors on the fly via customEditorInterfaces pro…
Browse files Browse the repository at this point in the history
…perty (#1454)

* feat: added custom editor interfaces property to jsonform

* feat: added story and test for custom input editor

* chores added a small comment

* chores: fix lint

* chore: improve docs

* chore: copy class to test

* chore: import missing moduel

---------

Co-authored-by: silvester-pari <[email protected]>
  • Loading branch information
srijitcoder and silvester-pari authored Jan 8, 2025
1 parent 0efb0b8 commit d68e7e6
Show file tree
Hide file tree
Showing 10 changed files with 266 additions and 12 deletions.
9 changes: 6 additions & 3 deletions elements/jsonform/src/custom-inputs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,15 +154,18 @@ const inputs = [
* Add custom input fields to @json-editor
*
* @param {{[key: string]: any}} startVals - Initial values for the custom inputs
* @param {Array} customEditorInterfaces - List of custom editor interface
*/
export const addCustomInputs = (startVals) => {
export const addCustomInputs = (startVals, customEditorInterfaces) => {
const finalInputs = [...inputs, ...customEditorInterfaces];

// Add custom validators for spatial inputs
JSONEditor.defaults["custom_validators"].push(
spatialValidatorCreator(inputs),
spatialValidatorCreator(finalInputs),
);

// Iterate over each custom input definition
inputs.map(({ type, format, func }) => {
finalInputs.map(({ type, format, func }) => {
JSONEditor.defaults["startVals"] = startVals;
JSONEditor.defaults.editors[format] = func;

Expand Down
2 changes: 1 addition & 1 deletion elements/jsonform/src/helpers/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ window.SimpleMDE = EasyMDE;
*/
export const createEditor = (element) => {
// Add custom inputs if any
addCustomInputs(element.value || {});
addCustomInputs(element.value || {}, element.customEditorInterfaces || []);

// Get the form element from the shadow DOM
const formEle = element.renderRoot.querySelector("form");
Expand Down
15 changes: 15 additions & 0 deletions elements/jsonform/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export class EOxJSONForm extends LitElement {
schema: { attribute: false, type: Object },
value: { attribute: false, type: Object },
options: { attribute: false, type: Object },
customEditorInterfaces: { attribute: false, type: Array },
noShadow: { attribute: "no-shadow", type: Boolean },
unstyled: { type: Boolean },
};
Expand Down Expand Up @@ -69,6 +70,15 @@ export class EOxJSONForm extends LitElement {
* @type {Boolean}
*/
this.unstyled = false;

/**
* List of custom editor interface
* Read more about the implementation of custom editor interfaces here:
* https://github.com/json-editor/json-editor/blob/master/docs/custom-editor.html
*
* @type {Array}
*/
this.customEditorInterfaces = [];
}

/**
Expand Down Expand Up @@ -160,6 +170,11 @@ export class EOxJSONForm extends LitElement {
this.#editor = await createEditor(this);
this.#dispatchEvent();
}
} else if (changedProperties.has("customEditorInterfaces")) {
if (this.customEditorInterfaces) {
this.#editor = await createEditor(this);
this.#dispatchEvent();
}
}
}

Expand Down
111 changes: 111 additions & 0 deletions elements/jsonform/stories/custom-editor-interfaces.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/**
* Custom editor interface for eox-jsonform
*/
import { html } from "lit";
import { JSONEditor } from "@json-editor/json-editor/src/core.js";

class CustomEditorExample extends JSONEditor.AbstractEditor {
build() {
super.build();

// control
this.control = document.createElement("div");

// Create a select element
this.input = document.createElement("select");
this.input.setAttribute("id", this.path + "test");
this.input.classList.add("form-select");

const options = [
{
value: "true",
label: "True",
data: true,
color: "green",
},
{
value: "false",
label: "False",
data: false,
color: "red",
},
];

// build options
options.forEach((option) => {
const optionElement = document.createElement("option");
optionElement.setAttribute("value", option.value);
optionElement.textContent = option.label;
optionElement.style.background = option.color;
optionElement.style.color = "white";
if (this.defaults.startVals[this.key] === option.data) {
optionElement.selected = true;
}
this.input.appendChild(optionElement);
});

// label
this.label = document.createElement("label");
this.label.setAttribute("for", this.path + "test");
this.label.classList.add("form-check-label");
this.label.textContent = this.schema.title;
this.label.style.fontStyle = "italic";

// appends
this.container.appendChild(this.control);
this.control.appendChild(this.label);
this.control.appendChild(this.input);
}

postBuild() {
super.postBuild();

this.input.addEventListener("change", (e) => {
e.preventDefault();
e.stopPropagation();
this.value = e.currentTarget.checked;
this.onChange(true);
});
}
}

const customEditorInterfaces = {
args: {
schema: {
properties: {
test: {
type: "boolean",
title: "Normal Boolean Editor Field",
},
test2: {
type: "boolean",
format: "custom",
title: "Custom Boolean Editor Field",
},
},
},
value: {
test: true,
test2: true,
},
customEditorInterfaces: [
{
type: "boolean",
format: "custom",
func: CustomEditorExample,
},
],
},
render: (args) => html`
<eox-jsonform
.schema=${args.schema}
.value=${args.value}
.noShadow=${false}
.unstyled=${args.unstyled}
.customEditorInterfaces=${args.customEditorInterfaces}
@change=${args.onChange}
></eox-jsonform>
`,
};

export default customEditorInterfaces;
1 change: 1 addition & 0 deletions elements/jsonform/stories/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export { default as FeatureSelectionStory } from "./feature-selection"; // Input
export { default as LineStory } from "./line"; // Input form based on Drawtools - LineString
export { default as WKTStory } from "./wkt"; // Input form based on Drawtools that returns WKT string
export { default as GeoJSONStory } from "./geojson"; // Input form based on Drawtools that returns GeoJSON
export { default as CustomEditorInterfacesStory } from "./custom-editor-interfaces"; // Custom editor interfaces
9 changes: 9 additions & 0 deletions elements/jsonform/stories/jsonform.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
WKTStory,
GeoJSONStory,
LineStory,
CustomEditorInterfacesStory,
} from "./index.js";

export default {
Expand Down Expand Up @@ -88,6 +89,14 @@ export const WKT = WKTStory;
* JSON Form based on drawtools - Returns the value as GeoJSON
*/
export const Geojson = GeoJSONStory;

/**
* With the `customEditorInterfaces` property it is possible to create
* one or more custom form inputs for json-editor (based on its `JSONEditor.AbstractEditor`).
* See [json-editor readme](https://github.com/json-editor/json-editor?tab=readme-ov-file#custom-editor-interfaces) for more details.
*/
export const CustomEditorInterfaces = CustomEditorInterfacesStory;

/**
* Unstyled JSON Form
*/
Expand Down
1 change: 1 addition & 0 deletions elements/jsonform/test/cases/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export { default as triggerChangeEventTest } from "./trigger-change-event";
export { default as loadValuesTest } from "./load-values";
export { default as loadMisMatchingValuesTest } from "./load-mismatching-values";
export { default as renderDrawtools } from "./render-drawtools";
export { default as loadCustomEditorInterfaceTest } from "./load-custom-editor-interface";
112 changes: 112 additions & 0 deletions elements/jsonform/test/cases/load-custom-editor-interface.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { html } from "lit";
import { JSONEditor } from "@json-editor/json-editor/src/core.js";
import { TEST_SELECTORS } from "../../src/enums";

class CustomEditorExample extends JSONEditor.AbstractEditor {
build() {
super.build();

// control
this.control = document.createElement("div");

// Create a select element
this.input = document.createElement("select");
this.input.setAttribute("id", this.path + "test");
this.input.classList.add("form-select");

const options = [
{
value: "true",
label: "True",
data: true,
color: "green",
},
{
value: "false",
label: "False",
data: false,
color: "red",
},
];

// build options
options.forEach((option) => {
const optionElement = document.createElement("option");
optionElement.setAttribute("value", option.value);
optionElement.textContent = option.label;
optionElement.style.background = option.color;
optionElement.style.color = "white";
if (this.defaults.startVals[this.key] === option.data) {
optionElement.selected = true;
}
this.input.appendChild(optionElement);
});

// label
this.label = document.createElement("label");
this.label.setAttribute("for", this.path + "test");
this.label.classList.add("form-check-label");
this.label.textContent = this.schema.title;
this.label.style.fontStyle = "italic";

// appends
this.container.appendChild(this.control);
this.control.appendChild(this.label);
this.control.appendChild(this.input);
}

postBuild() {
super.postBuild();

this.input.addEventListener("change", (e) => {
e.preventDefault();
e.stopPropagation();
this.value = e.currentTarget.checked;
this.onChange(true);
});
}
}

// Destructure TEST_SELECTORS object
const { jsonForm } = TEST_SELECTORS;
const schema = {
properties: {
test: {
type: "boolean",
format: "custom",
title: "Custom Editor Field",
},
},
};

const loadCustomEditorInterfaceTest = () => {
cy.mount(
html`<eox-jsonform
.customEditorInterfaces=${[
{
type: "boolean",
format: "custom",
func: CustomEditorExample,
},
]}
.schema=${schema}
.value=${{
test: false,
}}
></eox-jsonform>`,
).as(jsonForm);
cy.get(jsonForm)
.shadow()
.within(() => {
cy.get("select").should("exist");
cy.get("label.form-check-label").should(
"have.text",
schema.properties.test.title,
);
cy.get("select").should("have.value", "false");
cy.get("select").select("true");
cy.get("select").should("have.value", "true");
});
};

export default loadCustomEditorInterfaceTest;
2 changes: 2 additions & 0 deletions elements/jsonform/test/general.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
loadValuesTest,
loadMisMatchingValuesTest,
renderDrawtools,
loadCustomEditorInterfaceTest,
} from "./cases";

// Test suite for Jsonform
Expand All @@ -27,5 +28,6 @@ describe("Jsonform", () => {
it("triggers a change event when typing", () => triggerChangeEventTest());
it("loads values", () => loadValuesTest());
it("loads mismatching values", () => loadMisMatchingValuesTest());
it("loads custom editor interface", () => loadCustomEditorInterfaceTest());
it.only("renders drawtools as a custom input", () => renderDrawtools());
});
Loading

0 comments on commit d68e7e6

Please sign in to comment.