Skip to content

Commit

Permalink
jsonforms: add renderers: add object renderer which delegates to grou…
Browse files Browse the repository at this point in the history
…prenderer to nicely display groups of configsettings in frontend.
  • Loading branch information
mgineer85 committed Apr 30, 2024
1 parent 63db1eb commit b813c7d
Show file tree
Hide file tree
Showing 11 changed files with 320 additions and 12 deletions.
74 changes: 74 additions & 0 deletions src/components/form/complex/AnyOfRenderer.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<template>
<div v-if="control.visible">
<combinator-properties :schema="control.schema" combinator-keyword="anyOf" :path="path" />
<q-tabs v-model="selectedIndex" dense class="text-grey" active-color="primary" indicator-color="primary" align="justify" narrow-indicator>
<q-tab v-for="(anyOfRenderInfo, anyOfIndex) in anyOfRenderInfos" :key="`${control.path}-${anyOfIndex}`" :name="anyOfIndex">
{{ anyOfRenderInfo.label }}
</q-tab>
</q-tabs>

<q-separator />

<q-tab-panels v-model="selectedIndex" animated>
<q-tab-panel v-for="(anyOfRenderInfo, anyOfIndex) in anyOfRenderInfos" :key="`${control.path}-${anyOfIndex}`" :name="anyOfIndex">
<dispatch-renderer
v-if="selectedIndex === anyOfIndex"
:schema="anyOfRenderInfo.schema"
:uischema="anyOfRenderInfo.uischema"
:path="control.path"
:renderers="control.renderers"
:cells="control.cells"
:enabled="control.enabled"
/>
</q-tab-panel>
</q-tab-panels>
</div>
</template>

<script lang="ts">
import { CombinatorSubSchemaRenderInfo, ControlElement, createCombinatorRenderInfos } from '@jsonforms/core';
import { DispatchRenderer, rendererProps, RendererProps, useJsonFormsAnyOfControl } from '@jsonforms/vue';
import { defineComponent, ref } from 'vue';
import { useQuasarControl } from '../util';
import { CombinatorProperties } from './components';
const controlRenderer = defineComponent({
name: 'AnyOfRenderer',
components: {
DispatchRenderer,
CombinatorProperties,
},
props: {
...rendererProps<ControlElement>(),
},
setup(props: RendererProps<ControlElement>) {
const input = useJsonFormsAnyOfControl(props);
const control = input.control.value;
const selectedIndex = ref(control.indexOfFittingSchema || 0);
return {
...useQuasarControl(input),
selectedIndex,
};
},
computed: {
anyOfRenderInfos(): CombinatorSubSchemaRenderInfo[] {
const result = createCombinatorRenderInfos(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.control.schema.anyOf!,
this.control.rootSchema,
'anyOf',
this.control.uischema,
this.control.path,
this.control.uischemas,
);
return result.filter((info) => info.uischema);
},
},
});
export default controlRenderer;
</script>
76 changes: 76 additions & 0 deletions src/components/form/complex/ObjectRenderer.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<template>
<div v-if="control.visible">
<dispatch-renderer
:visible="control.visible"
:enabled="control.enabled"
:schema="control.schema"
:uischema="detailUiSchema"
:path="control.path"
:renderers="control.renderers"
:cells="control.cells"
/>
</div>
</template>

<script lang="ts">
import { ControlElement, findUISchema, Generate, GroupLayout, UISchemaElement } from '@jsonforms/core';
import { DispatchRenderer, rendererProps, RendererProps, useJsonFormsControlWithDetail } from '@jsonforms/vue';
import cloneDeep from 'lodash/cloneDeep';
import isEmpty from 'lodash/isEmpty';
import { defineComponent } from 'vue';
import { useNested, useQuasarControl } from '../util';
const controlRenderer = defineComponent({
name: 'ObjectRenderer',
components: {
DispatchRenderer,
},
props: {
...rendererProps<ControlElement>(),
},
setup(props: RendererProps<ControlElement>) {
const control = useQuasarControl(useJsonFormsControlWithDetail(props));
const nested = useNested('object');
return {
...control,
input: control,
nested,
};
},
computed: {
detailUiSchema(): UISchemaElement {
const uiSchemaGenerator = () => {
const uiSchema = Generate.uiSchema(this.control.schema, 'Group');
if (isEmpty(this.control.path)) {
uiSchema.type = 'VerticalLayout';
} else {
(uiSchema as GroupLayout).label = this.control.label;
}
return uiSchema;
};
let result = findUISchema(
this.control.uischemas,
this.control.schema,
this.control.uischema.scope,
this.control.path,
uiSchemaGenerator,
this.control.uischema,
this.control.rootSchema,
);
if (this.nested.level > 0) {
result = cloneDeep(result);
result.options = {
...result.options,
bare: true,
alignLeft: this.nested.level >= 4 || this.nested.parentElement === 'array',
};
}
return result;
},
},
});
export default controlRenderer;
</script>
26 changes: 19 additions & 7 deletions src/components/form/complex/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
isAllOfControl,
isAnyOfControl,
JsonFormsRendererRegistryEntry,
rankWith,
and,
Expand All @@ -8,15 +9,16 @@ import {
schemaMatches,
schemaSubPathMatches,
uiTypeIs,
isObjectControl,
} from '@jsonforms/core';

import { default as AllOfRenderer } from './AllOfRenderer.vue';
// export { default as AnyOfRenderer } from "./AnyOfRenderer.vue";
// export { default as ArrayControlRenderer } from "./ArrayControlRenderer.vue";
import { default as AnyOfRenderer } from './AnyOfRenderer.vue';
// import { default as ArrayControlRenderer } from "./ArrayControlRenderer.vue";
import { default as EnumArrayRenderer } from './EnumArrayRenderer.vue';
// export { default as ObjectRenderer } from "./ObjectRenderer.vue";
// export { default as OneOfRenderer } from "./OneOfRenderer.vue";
// export { default as OneOfTabRenderer } from "./OneOfTabRenderer.vue";
import { default as ObjectRenderer } from './ObjectRenderer.vue';
// import { default as OneOfRenderer } from "./OneOfRenderer.vue";
// import { default as OneOfTabRenderer } from "./OneOfTabRenderer.vue";

export const AllOfRendererEntry: JsonFormsRendererRegistryEntry = {
renderer: AllOfRenderer,
Expand All @@ -32,6 +34,11 @@ const hasOneOfItems = (schema: JsonSchema): boolean =>

const hasEnumItems = (schema: JsonSchema): boolean => schema.type === 'string' && schema.enum !== undefined;

export const AnyOfRendererEntry: JsonFormsRendererRegistryEntry = {
renderer: AnyOfRenderer,
tester: rankWith(3, isAnyOfControl),
};

export const EnumArrayRendererEntry: JsonFormsRendererRegistryEntry = {
renderer: EnumArrayRenderer,
tester: rankWith(
Expand All @@ -48,12 +55,17 @@ export const EnumArrayRendererEntry: JsonFormsRendererRegistryEntry = {
),
};

export const ObjectRendererEntry: JsonFormsRendererRegistryEntry = {
renderer: ObjectRenderer,
tester: rankWith(2, isObjectControl),
};

export const complexRenderers = [
AllOfRendererEntry,
// anyOfRendererEntry,
AnyOfRendererEntry,
// arrayControlRendererEntry,
EnumArrayRendererEntry,
// objectRendererEntry,
ObjectRendererEntry,
// oneOfRendererEntry,
// oneOfTabRendererEntry,
];
1 change: 1 addition & 0 deletions src/components/form/controls/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/*empty*/
1 change: 1 addition & 0 deletions src/components/form/controls/directives/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/* empty */
3 changes: 3 additions & 0 deletions src/components/form/controls/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// export * from './components';
// export * from './directives';

import {
JsonFormsRendererRegistryEntry,
rankWith,
Expand Down
85 changes: 85 additions & 0 deletions src/components/form/layouts/GroupRenderer.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<template>
<q-card v-if="layout.visible" :class="classes" :elevation="!bare ? 2 : undefined" :outlined="bare" v-bind="quasarProps('q-card')">
<q-card-section v-if="layout.label" :class="styles.group.label" v-bind="quasarProps('q-card-section')">
<div class="text-h6">{{ layout.label }}</div>
</q-card-section>

<q-card-section
v-for="(element, index) in (layout.uischema as Layout).elements"
v-bind="quasarProps(`v-card-text[${index}]`)"
:key="`${layout.path}-${index}`"
class="q-pt-none"
:class="styles.group.item"
>
<dispatch-renderer
:schema="layout.schema"
:uischema="element"
:path="layout.path"
:enabled="layout.enabled"
:renderers="layout.renderers"
:cells="layout.cells"
/>
</q-card-section>
</q-card>
</template>

<script lang="ts">
import { JsonFormsRendererRegistryEntry, Layout, rankWith, and, isLayout, uiTypeIs } from '@jsonforms/core';
import { defineComponent } from 'vue';
import { DispatchRenderer, rendererProps, useJsonFormsLayout, RendererProps } from '@jsonforms/vue';
import { useQuasarLayout } from '../util';
const layoutRenderer = defineComponent({
name: 'GroupRenderer',
components: {
DispatchRenderer,
},
props: {
...rendererProps<Layout>(),
},
setup(props: RendererProps<Layout>) {
return useQuasarLayout(useJsonFormsLayout(props));
},
computed: {
bare(): boolean {
return !!this.appliedOptions.bare;
},
alignLeft(): boolean {
return !!this.appliedOptions.alignLeft;
},
classes(): string {
const classes = ['q-my-xs', 'q-pa-none', `${this.styles.group.root}`];
if (this.bare) {
classes.push(`${this.styles.group.bare}`);
}
if (this.alignLeft) {
classes.push(`${this.styles.group.alignLeft}`);
}
return classes.join(' ');
},
},
});
export default layoutRenderer;
export const entry: JsonFormsRendererRegistryEntry = {
renderer: layoutRenderer,
tester: rankWith(2, and(isLayout, uiTypeIs('Group'))),
};
</script>

<!-- Default styles for the 'nested' feature -->
<style>
.group.group-bare {
border: 0;
box-shadow: none;
}
.group-bare > .group-label,
.group-bare > .group-item {
padding-right: 0;
}
.group-align-left > .group-label,
.group-align-left > .group-item {
padding-left: 0;
}
</style>
15 changes: 13 additions & 2 deletions src/components/form/layouts/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
import { JsonFormsRendererRegistryEntry, rankWith, uiTypeIs, isLayout } from '@jsonforms/core';
import { JsonFormsRendererRegistryEntry, rankWith, uiTypeIs, isLayout, and } from '@jsonforms/core';

import { default as GroupRenderer } from './GroupRenderer.vue';
import { default as LayoutRenderer } from './LayoutRenderer.vue';
import { default as TopLevelNavigationRenderer } from './TopLevelNavigationRenderer.vue';

export const GroupRendererEntry: JsonFormsRendererRegistryEntry = {
renderer: GroupRenderer,
tester: rankWith(2, and(isLayout, uiTypeIs('Group'))),
};

export const LayoutRendererEntry: JsonFormsRendererRegistryEntry = {
renderer: LayoutRenderer,
tester: rankWith(1, isLayout),
};

export const VerticalLayoutRendererEntry: JsonFormsRendererRegistryEntry = {
renderer: LayoutRenderer,
tester: rankWith(2, uiTypeIs('VerticalLayout')),
};
export const TopLevelNavigationRendererEntry: JsonFormsRendererRegistryEntry = {
renderer: TopLevelNavigationRenderer,
tester: rankWith(2, uiTypeIs('TopLevelNavigation')),
};

export const layoutRenderers = [LayoutRendererEntry, TopLevelNavigationRendererEntry];
export const layoutRenderers = [GroupRendererEntry, LayoutRendererEntry, VerticalLayoutRendererEntry, TopLevelNavigationRendererEntry];
2 changes: 2 additions & 0 deletions src/components/form/styles/defaultStyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ export const defaultStyles: Styles = {
root: 'group',
label: 'group-label',
item: 'group-item',
bare: 'group-bare',
alignLeft: 'group-align-left',
},
arrayList: {
root: 'array-list',
Expand Down
2 changes: 2 additions & 0 deletions src/components/form/styles/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ export interface Styles {
root?: string;
label?: string;
item?: string;
bare?: string;
alignLeft?: string;
};
arrayList: {
root?: string;
Expand Down
Loading

0 comments on commit b813c7d

Please sign in to comment.