1 | <script>
|
2 | import Vue from 'vue';
|
3 | import clone from 'clone';
|
4 | import { VForm, VContainer } from 'vuetify/lib/components';
|
5 | import types from './types';
|
6 | import propsResolver from './propsResolver';
|
7 | import { buildComponentsTree, renderComponentsTree } from './utils';
|
8 |
|
9 | function getFields(node) {
|
10 | return node.fields ? node.fields.map((field) => getFields(field)) : node;
|
11 | }
|
12 |
|
13 | export default {
|
14 | name: 'VuetifySchemaForm',
|
15 | props: {
|
16 | value: {
|
17 | type: Object,
|
18 | default: () => ({}),
|
19 | },
|
20 | fields: {
|
21 | type: [Object, Array],
|
22 | required: true,
|
23 | },
|
24 | rootNode: {
|
25 | type: Object,
|
26 | default: null,
|
27 | },
|
28 | defaultType: {
|
29 | type: String,
|
30 | default: null,
|
31 | },
|
32 | globalProps: {
|
33 | type: Object,
|
34 | default: null,
|
35 | },
|
36 | globalClasses: {
|
37 | type: Object,
|
38 | default: null,
|
39 | },
|
40 | context: {
|
41 | type: Object,
|
42 | default: () => ({}),
|
43 | },
|
44 | skeletonLoading: {
|
45 | type: Boolean,
|
46 | default: false,
|
47 | },
|
48 | noGutters: {
|
49 | type: Boolean,
|
50 | default: false,
|
51 | },
|
52 | nestedKeys: {
|
53 | type: Boolean,
|
54 | default: false,
|
55 | },
|
56 | submitOnEnter: {
|
57 | type: Boolean,
|
58 | default: false,
|
59 | },
|
60 | },
|
61 | data() {
|
62 | return {
|
63 | clone: {},
|
64 | validateionFailedEvent: new Event('validation-failed'),
|
65 | };
|
66 | },
|
67 | computed: {
|
68 | root() {
|
69 | const rootNode = this.rootNode || (Vue.$schemaForm && Vue.$schemaForm.rootNode) || { type: 'row' };
|
70 | return !this.fields.length ? this.fields : { ...rootNode, fields: this.fields };
|
71 | },
|
72 | objectFields() {
|
73 | return getFields(this.root)
|
74 | .flat(Number.POSITIVE_INFINITY)
|
75 | .filter((field) => field.value !== undefined);
|
76 | },
|
77 | },
|
78 | watch: {
|
79 | value: {
|
80 | handler(val) {
|
81 | this.initialize(val);
|
82 | },
|
83 | immediate: true,
|
84 | },
|
85 | objectFields() {
|
86 | this.initialize(this.value);
|
87 | },
|
88 | },
|
89 | mounted() {
|
90 | if (this.submitOnEnter) {
|
91 | window.addEventListener('keyup', (event) => {
|
92 | if (event.keyCode === 13) {
|
93 | this.submit();
|
94 | event.preventDefault();
|
95 | event.stopPropagation();
|
96 | }
|
97 | });
|
98 | }
|
99 | },
|
100 | methods: {
|
101 | submit() {
|
102 | if (!this.$refs.editForm.validate()) {
|
103 |
|
104 |
|
105 |
|
106 |
|
107 | return;
|
108 | }
|
109 |
|
110 | this.$refs.editForm.$el.querySelectorAll('input')
|
111 | .forEach((input) => {
|
112 | if (input.blur) {
|
113 | input.blur();
|
114 | }
|
115 | });
|
116 |
|
117 | this.$nextTick(() => {
|
118 | const data = this.nestedKeys ? this.processNestedKeys(this.clone) : this.clone;
|
119 | this.$emit('submit', data);
|
120 | });
|
121 | },
|
122 | reset() {
|
123 | if (this.$refs.editForm) {
|
124 | this.initialize({});
|
125 | this.$refs.editForm.resetValidation();
|
126 | }
|
127 | },
|
128 | initialize(initial) {
|
129 | this.clone = {
|
130 | ...Object.assign({}, ...this.objectFields.map((field) => ({ [field.value]: null }))),
|
131 | ...clone(initial),
|
132 | };
|
133 | },
|
134 | processNestedKeys(data) {
|
135 | const cloned = clone(data);
|
136 | Object.keys(cloned).forEach((key) => {
|
137 | if (!key.includes('__')) {
|
138 | return;
|
139 | }
|
140 | const [parentKey, childKey] = key.split('__');
|
141 | if (!(parentKey in cloned)) {
|
142 | cloned[parentKey] = {};
|
143 | }
|
144 | cloned[parentKey][childKey] = cloned[key];
|
145 | delete cloned[key];
|
146 | });
|
147 |
|
148 | return cloned;
|
149 | },
|
150 | },
|
151 | render(h) {
|
152 | const params = Vue.$schemaForm || {};
|
153 | const options = {
|
154 | types,
|
155 | propsResolver,
|
156 | ...params,
|
157 | defaultType: this.defaultType || params.defaultType || 'text',
|
158 | globalProps: this.globalProps || params.globalProps || { dense: true },
|
159 | globalClasses: this.globalClasses || params.globalClasses || {},
|
160 | $vuetify: this.$vuetify,
|
161 | };
|
162 |
|
163 | const tree = buildComponentsTree(this.root, options);
|
164 | const renderedTree = renderComponentsTree(h, tree, this.clone,
|
165 | (item) => this.$emit('input', item), {
|
166 | context: this.context,
|
167 | scopedSlots: this.$scopedSlots,
|
168 | skeletonLoading: this.skeletonLoading,
|
169 | ...options,
|
170 | });
|
171 |
|
172 | return h(VForm,
|
173 | {
|
174 | props: { lazyValidation: true },
|
175 | on: {
|
176 | submit: (event) => {
|
177 | if (!this.submitOnEnter) {
|
178 | this.submit();
|
179 | }
|
180 | event.preventDefault();
|
181 | event.stopPropagation();
|
182 | },
|
183 | },
|
184 | ref: 'editForm',
|
185 | },
|
186 | [
|
187 | h(VContainer, {
|
188 | class: {
|
189 | 'px-0': true,
|
190 | 'py-1': true,
|
191 | },
|
192 | props: {
|
193 |
|
194 | fluid: true,
|
195 | },
|
196 | }, [renderedTree]),
|
197 | ]);
|
198 | },
|
199 | };
|
200 | </script>
|
201 | <style>
|
202 | .vsf-skeleton-loading .v-skeleton-loader__bone {
|
203 | height: 70%;
|
204 | opacity: 0.6;
|
205 | }
|
206 | </style>
|