Skip to content

Commit af9125b

Browse files
authored
vue-vanilla: add categorization renderers (#2270)
* add categorization renderer to Vue Vanilla * Implemented also the stepper variant * dev: add tests for categorization vue vanilla renderer * add useJsonFormsCategorization to vue core package to get categorization composition props
1 parent 1d73eb7 commit af9125b

File tree

9 files changed

+373
-1
lines changed

9 files changed

+373
-1
lines changed
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<template>
2+
<div :class="styles.categorization.root">
3+
<div :class="styles.categorization.category">
4+
<template
5+
v-for="(category, index) in categories"
6+
:key="`category-${index}`"
7+
>
8+
<div v-if="category.value.visible" @click="selected = index">
9+
<button
10+
:class="[selected === index ? styles.categorization.selected : '']"
11+
:disabled="!category.value.enabled"
12+
>
13+
<label>{{ category.value.label }}</label>
14+
</button>
15+
</div>
16+
</template>
17+
</div>
18+
19+
<div :class="styles.categorization.panel">
20+
<DispatchRenderer
21+
v-if="categories[selected]"
22+
:schema="layout.schema"
23+
:uischema="categories[selected].value.uischema"
24+
:path="layout.path"
25+
:enabled="layout.enabled"
26+
:renderers="layout.renderers"
27+
:cells="layout.cells"
28+
/>
29+
</div>
30+
</div>
31+
</template>
32+
33+
<script lang="ts">
34+
import { defineComponent } from 'vue';
35+
import type { JsonFormsRendererRegistryEntry, Layout } from '@jsonforms/core';
36+
import {
37+
and,
38+
categorizationHasCategory,
39+
isCategorization,
40+
rankWith,
41+
} from '@jsonforms/core';
42+
import {
43+
DispatchRenderer,
44+
rendererProps,
45+
useJsonFormsCategorization,
46+
type RendererProps,
47+
} from '@jsonforms/vue';
48+
import { useVanillaLayout } from '../util';
49+
50+
const layoutRenderer = defineComponent({
51+
name: 'CategorizationRenderer',
52+
components: {
53+
DispatchRenderer,
54+
},
55+
props: {
56+
...rendererProps<Layout>(),
57+
},
58+
setup(props: RendererProps<Layout>) {
59+
return useVanillaLayout(useJsonFormsCategorization(props));
60+
},
61+
data() {
62+
return {
63+
selected: 0,
64+
};
65+
},
66+
});
67+
68+
export default layoutRenderer;
69+
export const entry: JsonFormsRendererRegistryEntry = {
70+
renderer: layoutRenderer,
71+
tester: rankWith(2, and(isCategorization, categorizationHasCategory)),
72+
};
73+
</script>
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<template>
2+
<div :class="styles.categorization.root">
3+
<div :class="styles.categorization.stepper">
4+
<template
5+
v-for="(category, index) in visibleCategories"
6+
:key="`tab-${index}`"
7+
>
8+
<div v-if="category.value.visible" @click="selected = index">
9+
<button
10+
:class="[selected === index ? styles.categorization.selected : '']"
11+
:disabled="!category.value.enabled"
12+
>
13+
<span :class="styles.categorization.stepperBadge">{{
14+
index + 1
15+
}}</span>
16+
17+
<label>{{ category.value.label }}</label>
18+
</button>
19+
</div>
20+
21+
<hr
22+
v-if="index !== visibleCategories.length - 1"
23+
:class="styles.categorization.stepperLine"
24+
/>
25+
</template>
26+
</div>
27+
28+
<div :class="styles.categorization.panel">
29+
<DispatchRenderer
30+
v-if="visibleCategories[selected]"
31+
:schema="layout.schema"
32+
:uischema="visibleCategories[selected].value.uischema"
33+
:path="layout.path"
34+
:enabled="layout.enabled"
35+
:renderers="layout.renderers"
36+
:cells="layout.cells"
37+
/>
38+
</div>
39+
40+
<footer
41+
v-if="appliedOptions?.showNavButtons"
42+
:class="styles.categorization.stepperFooter"
43+
>
44+
<div
45+
v-if="selected > 0"
46+
:class="styles.categorization.stepperButtonBack"
47+
@click="selected = selected - 1"
48+
>
49+
<button :disabled="!visibleCategories[selected - 1].value.enabled">
50+
{{ 'back' }}
51+
</button>
52+
</div>
53+
54+
<div
55+
v-if="selected + 1 < visibleCategories.length"
56+
:class="styles.categorization.stepperButtonNext"
57+
@click="selected = selected + 1"
58+
>
59+
<button :disabled="!visibleCategories[selected + 1].value.enabled">
60+
{{ 'next' }}
61+
</button>
62+
</div>
63+
</footer>
64+
</div>
65+
</template>
66+
67+
<script lang="ts">
68+
import { defineComponent } from 'vue';
69+
import type { JsonFormsRendererRegistryEntry, Layout } from '@jsonforms/core';
70+
import {
71+
and,
72+
categorizationHasCategory,
73+
isCategorization,
74+
optionIs,
75+
rankWith,
76+
} from '@jsonforms/core';
77+
import {
78+
DispatchRenderer,
79+
rendererProps,
80+
useJsonFormsCategorization,
81+
type RendererProps,
82+
} from '@jsonforms/vue';
83+
import { useVanillaLayout } from '../util';
84+
85+
const layoutRenderer = defineComponent({
86+
name: 'CategorizationStepperRenderer',
87+
components: {
88+
DispatchRenderer,
89+
},
90+
props: {
91+
...rendererProps<Layout>(),
92+
},
93+
setup(props: RendererProps<Layout>) {
94+
return useVanillaLayout(useJsonFormsCategorization(props));
95+
},
96+
data() {
97+
return {
98+
selected: 0,
99+
};
100+
},
101+
computed: {
102+
visibleCategories() {
103+
return this.categories.filter((category) => category.value.visible);
104+
},
105+
},
106+
});
107+
108+
export default layoutRenderer;
109+
export const entry: JsonFormsRendererRegistryEntry = {
110+
renderer: layoutRenderer,
111+
tester: rankWith(
112+
3,
113+
and(
114+
isCategorization,
115+
categorizationHasCategory,
116+
optionIs('variant', 'stepper')
117+
)
118+
),
119+
};
120+
</script>
Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
export { default as LayoutRenderer } from './LayoutRenderer.vue';
22
export { default as GroupRenderer } from './GroupRenderer.vue';
3+
export { default as CategorizationRenderer } from '../layouts/CategorizationRenderer.vue';
4+
export { default as CategorizationStepperRenderer } from '../layouts/CategorizationStepperRenderer.vue';
35

46
import { entry as layoutRendererEntry } from './LayoutRenderer.vue';
57
import { entry as groupRendererEntry } from './GroupRenderer.vue';
8+
import { entry as categorizationEntry } from '../layouts/CategorizationRenderer.vue';
9+
import { entry as categorizationStepperEntry } from '../layouts/CategorizationStepperRenderer.vue';
610

7-
export const layoutRenderers = [layoutRendererEntry, groupRendererEntry];
11+
export const layoutRenderers = [
12+
layoutRendererEntry,
13+
groupRendererEntry,
14+
categorizationEntry,
15+
categorizationStepperEntry,
16+
];

packages/vue-vanilla/src/styles/defaultStyles.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,16 @@ export const defaultStyles: Styles = {
5757
oneOf: {
5858
root: 'one-of',
5959
},
60+
categorization: {
61+
root: 'categorization',
62+
category: 'categorization-category',
63+
selected: 'categorization-selected',
64+
panel: 'categorization-panel',
65+
stepper: 'categorization-stepper',
66+
stepperBadge: 'categorization-stepper-badge',
67+
stepperLine: 'categorization-stepper-line',
68+
stepperFooter: 'categorization-stepper-footer',
69+
stepperButtonBack: 'categorization-stepper-button-back',
70+
stepperButtonNext: 'categorization-stepper-button-next',
71+
},
6072
};

packages/vue-vanilla/src/styles/styles.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const createEmptyStyles = (): Styles => ({
1212
label: {},
1313
dialog: {},
1414
oneOf: {},
15+
categorization: {},
1516
});
1617

1718
export interface Styles {
@@ -71,6 +72,18 @@ export interface Styles {
7172
oneOf: {
7273
root?: string;
7374
};
75+
categorization: {
76+
root?: string;
77+
category?: string;
78+
selected?: string;
79+
panel?: string;
80+
stepper?: string;
81+
stepperBadge?: string;
82+
stepperLine?: string;
83+
stepperFooter?: string;
84+
stepperButtonBack?: string;
85+
stepperButtonNext?: string;
86+
};
7487
}
7588

7689
export const useStyles = (element?: UISchemaElement) => {
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { expect } from 'chai';
2+
import { mountJsonForms } from '../util';
3+
4+
const schema = {
5+
type: 'string',
6+
};
7+
const uischema = {
8+
type: 'Categorization',
9+
elements: [
10+
{
11+
type: 'Category',
12+
label: 'A',
13+
elements: [
14+
{
15+
type: 'Control',
16+
scope: '#',
17+
},
18+
],
19+
},
20+
{
21+
type: 'Category',
22+
label: 'B',
23+
elements: [],
24+
},
25+
],
26+
};
27+
28+
describe('CategorizationRenderer.vue', () => {
29+
it('renders categorization', () => {
30+
const wrapper = mountJsonForms('', schema, uischema);
31+
expect(wrapper.find('.categorization').exists()).to.be.true;
32+
});
33+
34+
it('renders 2 category items', async () => {
35+
const wrapper = mountJsonForms('', schema, uischema);
36+
const inputs = wrapper.findAll('.categorization-category > *');
37+
expect(inputs.length).to.equal(2);
38+
});
39+
40+
it('renders 1 panel item', async () => {
41+
const wrapper = mountJsonForms('', schema, uischema);
42+
const inputs = wrapper.findAll('.categorization-panel > *');
43+
expect(inputs.length).to.equal(1);
44+
});
45+
});
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { expect } from 'chai';
2+
import { mountJsonForms } from '../util';
3+
4+
const schema = {
5+
type: 'string',
6+
};
7+
const uischema = {
8+
type: 'Categorization',
9+
elements: [
10+
{
11+
type: 'Category',
12+
label: 'A',
13+
elements: [
14+
{
15+
type: 'Control',
16+
scope: '#',
17+
},
18+
],
19+
},
20+
{
21+
type: 'Category',
22+
label: 'B',
23+
elements: [],
24+
},
25+
],
26+
options: {
27+
variant: 'stepper',
28+
},
29+
};
30+
31+
const uischemaNav = {
32+
...uischema,
33+
options: {
34+
variant: 'stepper',
35+
showNavButtons: true,
36+
},
37+
};
38+
39+
describe('CategorizationStepperRenderer.vue', () => {
40+
it('renders categorization as stepper', () => {
41+
const wrapper = mountJsonForms('', schema, uischema);
42+
expect(wrapper.find('.categorization-stepper').exists()).to.be.true;
43+
});
44+
45+
it('renders 2 stepper items and one line', () => {
46+
const wrapper = mountJsonForms('', schema, uischema);
47+
const inputs = wrapper.findAll('.categorization-stepper > div');
48+
expect(inputs.length).to.equal(2);
49+
const lines = wrapper.findAll('.categorization-stepper > hr');
50+
expect(lines.length).to.equal(1);
51+
});
52+
53+
it('renders a next button at stepper nav bar', () => {
54+
const wrapper = mountJsonForms('', schema, uischemaNav);
55+
expect(
56+
wrapper
57+
.find(
58+
'.categorization footer.categorization-stepper-footer > div.categorization-stepper-button-next'
59+
)
60+
.exists()
61+
).to.be.true;
62+
});
63+
});

packages/vue-vanilla/vanilla.css

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,17 @@
7777
.array-list-item-content.expanded {
7878
display: block;
7979
}
80+
81+
.categorization .categorization-category,
82+
.categorization .categorization-stepper {
83+
display: flex;
84+
}
85+
.categorization .categorization-stepper-line {
86+
flex-grow: 1;
87+
height: 1px;
88+
border-width: 0 0 1px 0;
89+
}
90+
.categorization .categorization-stepper-footer {
91+
display: flex;
92+
justify-content: flex-end;
93+
}

0 commit comments

Comments
 (0)