@tmagic/form 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/dist/style.css +161 -0
  2. package/dist/tmagic-form.es.js +3749 -0
  3. package/dist/tmagic-form.es.js.map +1 -0
  4. package/dist/tmagic-form.umd.js +3790 -0
  5. package/dist/tmagic-form.umd.js.map +1 -0
  6. package/dist/types/src/Form.vue.d.ts +304 -0
  7. package/dist/types/src/FormDialog.vue.d.ts +641 -0
  8. package/dist/types/src/containers/Container.vue.d.ts +69 -0
  9. package/dist/types/src/containers/Fieldset.vue.d.ts +56 -0
  10. package/dist/types/src/containers/GroupList.vue.d.ts +56 -0
  11. package/dist/types/src/containers/GroupListItem.vue.d.ts +72 -0
  12. package/dist/types/src/containers/Panel.vue.d.ts +41 -0
  13. package/dist/types/src/containers/Row.vue.d.ts +42 -0
  14. package/dist/types/src/containers/Step.vue.d.ts +45 -0
  15. package/dist/types/src/containers/Table.vue.d.ts +1401 -0
  16. package/dist/types/src/containers/Tabs.vue.d.ts +45 -0
  17. package/dist/types/src/fields/Cascader.vue.d.ts +1748 -0
  18. package/dist/types/src/fields/Checkbox.vue.d.ts +59 -0
  19. package/dist/types/src/fields/CheckboxGroup.vue.d.ts +56 -0
  20. package/dist/types/src/fields/Date.vue.d.ts +58 -0
  21. package/dist/types/src/fields/DateTime.vue.d.ts +57 -0
  22. package/dist/types/src/fields/Daterange.vue.d.ts +57 -0
  23. package/dist/types/src/fields/Display.vue.d.ts +3 -0
  24. package/dist/types/src/fields/DynamicField.vue.d.ts +76 -0
  25. package/dist/types/src/fields/Hidden.vue.d.ts +3 -0
  26. package/dist/types/src/fields/Link.vue.d.ts +1365 -0
  27. package/dist/types/src/fields/Number.vue.d.ts +59 -0
  28. package/dist/types/src/fields/RadioGroup.vue.d.ts +55 -0
  29. package/dist/types/src/fields/Select.vue.d.ts +975 -0
  30. package/dist/types/src/fields/Switch.vue.d.ts +59 -0
  31. package/dist/types/src/fields/Text.vue.d.ts +61 -0
  32. package/dist/types/src/fields/Textarea.vue.d.ts +56 -0
  33. package/dist/types/src/fields/Time.vue.d.ts +54 -0
  34. package/dist/types/src/index.d.ts +36 -0
  35. package/dist/types/src/schema.d.ts +495 -0
  36. package/dist/types/src/shims-vue.d.ts +6 -0
  37. package/dist/types/src/utils/config.d.ts +3 -0
  38. package/dist/types/src/utils/fieldProps.d.ts +21 -0
  39. package/dist/types/src/utils/form.d.ts +8 -0
  40. package/dist/types/src/utils/useAddField.d.ts +1 -0
  41. package/dist/types/src/vite-env.d.ts +1 -0
  42. package/package.json +51 -0
  43. package/src/Form.vue +188 -0
  44. package/src/FormDialog.vue +163 -0
  45. package/src/containers/Container.vue +263 -0
  46. package/src/containers/Fieldset.vue +138 -0
  47. package/src/containers/GroupList.vue +152 -0
  48. package/src/containers/GroupListItem.vue +159 -0
  49. package/src/containers/Panel.vue +106 -0
  50. package/src/containers/Row.vue +68 -0
  51. package/src/containers/Step.vue +82 -0
  52. package/src/containers/Table.vue +624 -0
  53. package/src/containers/Tabs.vue +176 -0
  54. package/src/fields/Cascader.vue +147 -0
  55. package/src/fields/Checkbox.vue +67 -0
  56. package/src/fields/CheckboxGroup.vue +44 -0
  57. package/src/fields/ColorPicker.vue +38 -0
  58. package/src/fields/Date.vue +51 -0
  59. package/src/fields/DateTime.vue +59 -0
  60. package/src/fields/Daterange.vue +75 -0
  61. package/src/fields/Display.vue +34 -0
  62. package/src/fields/DynamicField.vue +109 -0
  63. package/src/fields/Hidden.vue +29 -0
  64. package/src/fields/Link.vue +112 -0
  65. package/src/fields/Number.vue +53 -0
  66. package/src/fields/RadioGroup.vue +33 -0
  67. package/src/fields/Select.vue +393 -0
  68. package/src/fields/Switch.vue +65 -0
  69. package/src/fields/Text.vue +134 -0
  70. package/src/fields/Textarea.vue +62 -0
  71. package/src/fields/Time.vue +48 -0
  72. package/src/index.ts +127 -0
  73. package/src/schema.ts +638 -0
  74. package/src/shims-vue.d.ts +6 -0
  75. package/src/theme/date-time.scss +7 -0
  76. package/src/theme/fieldset.scss +24 -0
  77. package/src/theme/form-dialog.scss +13 -0
  78. package/src/theme/form.scss +30 -0
  79. package/src/theme/group-list.scss +23 -0
  80. package/src/theme/index.scss +9 -0
  81. package/src/theme/link.scss +3 -0
  82. package/src/theme/panel.scss +18 -0
  83. package/src/theme/select.scss +3 -0
  84. package/src/theme/table.scss +69 -0
  85. package/src/theme/tabs.scss +25 -0
  86. package/src/utils/config.ts +27 -0
  87. package/src/utils/containerProps.ts +50 -0
  88. package/src/utils/createForm.ts +25 -0
  89. package/src/utils/fieldProps.ts +39 -0
  90. package/src/utils/form.ts +266 -0
  91. package/src/utils/useAddField.ts +40 -0
  92. package/src/vite-env.d.ts +1 -0
  93. package/tsconfig.json +9 -0
  94. package/vite.config.ts +27 -0
package/src/Form.vue ADDED
@@ -0,0 +1,188 @@
1
+ <template>
2
+ <ElForm
3
+ v-if="initialized && Array.isArray(config)"
4
+ class="m-form"
5
+ ref="elForm"
6
+ :model="values"
7
+ :label-width="labelWidth"
8
+ :disabled="disabled"
9
+ :style="`height: ${height}`"
10
+ :inline="inline"
11
+ :label-position="labelPosition"
12
+ >
13
+ <MContainer
14
+ v-for="(item, index) in config"
15
+ :key="item[keyProp] ?? index"
16
+ :config="item"
17
+ :model="values"
18
+ :label-width="item.labelWidth || labelWidth"
19
+ :step-active="stepActive"
20
+ :size="size"
21
+ @change="changeHandler"
22
+ ></MContainer>
23
+ </ElForm>
24
+ </template>
25
+
26
+ <script lang="ts">
27
+ import { defineComponent, PropType, provide, reactive, ref, watchEffect } from 'vue';
28
+ import { ElForm } from 'element-plus';
29
+ import { cloneDeep } from 'lodash-es';
30
+
31
+ import MContainer from './containers/Container.vue';
32
+ import { getConfig } from './utils/config';
33
+ import { initValue } from './utils/form';
34
+ import { FormConfig, FormState, FormValue } from './schema';
35
+
36
+ export interface ValidateError {
37
+ message: string;
38
+ field: string;
39
+ }
40
+
41
+ export interface FieldErrorList {
42
+ [field: string]: ValidateError[];
43
+ }
44
+
45
+ export default defineComponent({
46
+ name: 'm-form',
47
+
48
+ components: { ElForm, MContainer },
49
+
50
+ props: {
51
+ // 表单初始化值
52
+ initValues: {
53
+ type: Object,
54
+ required: true,
55
+ default: () => ({}),
56
+ },
57
+
58
+ parentValues: {
59
+ type: Object,
60
+ default: () => ({}),
61
+ },
62
+
63
+ // 表单配置
64
+ config: {
65
+ type: Array as PropType<FormConfig>,
66
+ required: true,
67
+ default: () => [],
68
+ },
69
+
70
+ labelWidth: {
71
+ type: String,
72
+ default: () => '200px',
73
+ },
74
+
75
+ disabled: {
76
+ type: Boolean,
77
+ default: () => false,
78
+ },
79
+
80
+ height: {
81
+ type: String,
82
+ default: () => 'auto',
83
+ },
84
+
85
+ stepActive: {
86
+ type: [String, Number],
87
+ default: () => 1,
88
+ },
89
+
90
+ size: {
91
+ type: String,
92
+ default: 'small',
93
+ },
94
+
95
+ inline: {
96
+ type: Boolean,
97
+ default: false,
98
+ },
99
+
100
+ labelPosition: {
101
+ type: String,
102
+ default: 'right',
103
+ },
104
+
105
+ keyProp: {
106
+ type: String,
107
+ default: '__key',
108
+ },
109
+ },
110
+
111
+ emits: ['change', 'field-input', 'field-change'],
112
+
113
+ setup(props, { emit }) {
114
+ const elForm = ref<InstanceType<typeof ElForm>>();
115
+ const initialized = ref(false);
116
+ const values = ref<FormValue>({});
117
+ const fields = new Map<string, any>();
118
+
119
+ const requestFuc = getConfig('request') as Function;
120
+
121
+ const formState: FormState = reactive<FormState>({
122
+ keyProp: props.keyProp,
123
+ config: props.config,
124
+ initValues: props.initValues,
125
+ parentValues: props.parentValues,
126
+ values,
127
+ $emit: emit as (event: string, ...args: any[]) => void,
128
+ fields,
129
+ setField: (prop: string, field: any) => fields.set(prop, field),
130
+ getField: (prop: string) => fields.get(prop),
131
+ deleteField: (prop: string) => fields.delete(prop),
132
+ post: (options: any) => {
133
+ if (requestFuc) {
134
+ return requestFuc({
135
+ ...options,
136
+ method: 'POST',
137
+ });
138
+ }
139
+ },
140
+ });
141
+
142
+ provide('mForm', formState);
143
+
144
+ watchEffect(() => {
145
+ initialized.value = false;
146
+ initValue(formState, {
147
+ initValues: props.initValues,
148
+ config: props.config,
149
+ }).then((value) => {
150
+ values.value = value;
151
+ initialized.value = true;
152
+ });
153
+ });
154
+
155
+ return {
156
+ initialized,
157
+ values,
158
+ elForm,
159
+
160
+ formState,
161
+
162
+ changeHandler: () => {
163
+ emit('change', values.value);
164
+ },
165
+
166
+ resetForm: () => elForm.value?.resetFields(),
167
+
168
+ submitForm: async (native?: boolean): Promise<any> => {
169
+ try {
170
+ await elForm.value?.validate();
171
+ return native ? values.value : cloneDeep(values.value);
172
+ } catch (invalidFields: any) {
173
+ const error: string[] = [];
174
+
175
+ Object.entries(invalidFields).forEach(([, ValidateError]) => {
176
+ (ValidateError as ValidateError[]).forEach(({ field, message }) => {
177
+ if (field && message) error.push(`${field} -> ${message}`);
178
+ if (field && !message) error.push(`${field} -> 出现错误`);
179
+ if (!field && message) error.push(`${message}`);
180
+ });
181
+ });
182
+ throw new Error(error.join('<br>'));
183
+ }
184
+ },
185
+ };
186
+ },
187
+ });
188
+ </script>
@@ -0,0 +1,163 @@
1
+ <template>
2
+ <div>
3
+ <el-dialog
4
+ v-model="dialogVisible"
5
+ custom-class="m-form-dialog"
6
+ top="20px"
7
+ :title="title"
8
+ :appendToBody="true"
9
+ :width="width"
10
+ :fullscreen="fullscreen"
11
+ :close-on-click-modal="false"
12
+ @close="closeHandler"
13
+ >
14
+ <div class="m-dialog-body" :style="`max-height: ${bodyHeight}; overflow-y: auto; overflow-x: hidden;`">
15
+ <m-form
16
+ v-model="stepActive"
17
+ ref="form"
18
+ :config="config"
19
+ :init-values="values"
20
+ :label-width="labelWidth"
21
+ @change="changeHandler"
22
+ ></m-form>
23
+ <slot></slot>
24
+ </div>
25
+
26
+ <template #footer>
27
+ <el-row class="dialog-footer">
28
+ <el-col :span="12" style="text-align: left">
29
+ <div style="min-height: 1px">
30
+ <slot name="left"></slot>
31
+ </div>
32
+ </el-col>
33
+ <el-col :span="12">
34
+ <slot name="footer">
35
+ <el-button @click="cancel" size="small">取 消</el-button>
36
+ <el-button v-if="hasStep && stepActive > 1" type="info" size="small" @click="preStep">上一步</el-button>
37
+ <el-button v-if="hasStep && stepCount > stepActive" type="info" size="small" @click="nextStep"
38
+ >下一步</el-button
39
+ >
40
+ <el-button v-else type="primary" size="small" :loading="saveFetch" @click="save">{{
41
+ confirmText
42
+ }}</el-button>
43
+ </slot>
44
+ </el-col>
45
+ </el-row>
46
+ </template>
47
+ </el-dialog>
48
+ </div>
49
+ </template>
50
+
51
+ <script lang="ts">
52
+ import { computed, defineComponent, PropType, ref } from 'vue';
53
+
54
+ import Form from './Form.vue';
55
+ import { FormConfig, StepConfig } from './schema';
56
+
57
+ export default defineComponent({
58
+ name: 'm-form-dialog',
59
+
60
+ props: {
61
+ values: {
62
+ type: Object,
63
+ default: () => ({}),
64
+ },
65
+
66
+ width: [Number, String],
67
+
68
+ fullscreen: Boolean,
69
+
70
+ title: String,
71
+
72
+ config: {
73
+ type: Array as PropType<FormConfig>,
74
+ required: true,
75
+ default: () => [],
76
+ },
77
+
78
+ labelWidth: [Number, String],
79
+
80
+ confirmText: {
81
+ type: String,
82
+ default: '确定',
83
+ },
84
+ },
85
+
86
+ emits: ['close', 'submit', 'error', 'change'],
87
+
88
+ setup(props, { emit }) {
89
+ const form = ref<InstanceType<typeof Form>>();
90
+ const dialogVisible = ref(false);
91
+ const saveFetch = ref(false);
92
+ const stepActive = ref(1);
93
+ const bodyHeight = ref(`${document.body.clientHeight - 194}px`);
94
+
95
+ const stepCount = computed(() => {
96
+ const { length } = props.config;
97
+ for (let index = 0; index < length; index++) {
98
+ if (props.config[index].type === 'step') {
99
+ return (props.config[index] as StepConfig).items.length;
100
+ }
101
+ }
102
+ return 0;
103
+ });
104
+
105
+ const hasStep = computed(() => {
106
+ const { length } = props.config;
107
+ for (let index = 0; index < length; index++) {
108
+ if (props.config[index].type === 'step') {
109
+ return true;
110
+ }
111
+ }
112
+
113
+ return false;
114
+ });
115
+
116
+ const cancel = () => {
117
+ dialogVisible.value = false;
118
+ };
119
+
120
+ const closeHandler = () => {
121
+ stepActive.value = 1;
122
+ emit('close');
123
+ };
124
+
125
+ const save = async () => {
126
+ try {
127
+ const values = await form.value?.submitForm();
128
+ emit('submit', values);
129
+ } catch (e) {
130
+ emit('error', e);
131
+ }
132
+ };
133
+
134
+ const preStep = () => {
135
+ stepActive.value -= 1;
136
+ };
137
+
138
+ const nextStep = () => {
139
+ stepActive.value += 1;
140
+ };
141
+
142
+ const changeHandler = (value: any) => {
143
+ emit('change', value);
144
+ };
145
+
146
+ return {
147
+ form,
148
+ saveFetch,
149
+ stepActive,
150
+ dialogVisible,
151
+ bodyHeight,
152
+ stepCount,
153
+ hasStep,
154
+ cancel,
155
+ closeHandler,
156
+ save,
157
+ preStep,
158
+ nextStep,
159
+ changeHandler,
160
+ };
161
+ },
162
+ });
163
+ </script>
@@ -0,0 +1,263 @@
1
+ <template>
2
+ <div v-if="config" :style="config.tip ? 'display: flex' : ''" :class="config.className" class="m-form-container">
3
+ <m-fields-hidden
4
+ v-if="type === 'hidden'"
5
+ :model="model"
6
+ :config="config"
7
+ :name="config.name"
8
+ :disabled="disabled"
9
+ :prop="itemProp"
10
+ ></m-fields-hidden>
11
+
12
+ <component
13
+ v-else-if="items && !config.text && type && display"
14
+ :key="key(config)"
15
+ :size="size"
16
+ :is="tagName"
17
+ :model="model"
18
+ :config="config"
19
+ :name="name"
20
+ :prop="itemProp"
21
+ :step-active="stepActive"
22
+ :expand-more="expand"
23
+ :label-width="config.labelWidth || labelWidth"
24
+ @change="onChangeHandler"
25
+ ></component>
26
+
27
+ <template v-else-if="type && display">
28
+ <el-form-item
29
+ :style="config.tip ? 'flex: 1' : ''"
30
+ :prop="itemProp"
31
+ :label-width="config.text ? config.labelWidth || labelWidth : '0'"
32
+ :rules="rule"
33
+ >
34
+ <template #label><span v-html="type === 'checkbox' ? '' : config.text"></span></template>
35
+ <el-tooltip v-if="tooltip">
36
+ <component
37
+ :key="key(config)"
38
+ :size="size"
39
+ :is="tagName"
40
+ :model="model"
41
+ :config="config"
42
+ :name="name"
43
+ :disabled="disabled"
44
+ :prop="itemProp"
45
+ @change="onChangeHandler"
46
+ ></component>
47
+ <template #content>
48
+ <div v-html="tooltip"></div>
49
+ </template>
50
+ </el-tooltip>
51
+
52
+ <component
53
+ v-else
54
+ :key="key(config)"
55
+ :size="size"
56
+ :is="tagName"
57
+ :model="model"
58
+ :config="config"
59
+ :name="name"
60
+ :disabled="disabled"
61
+ :prop="itemProp"
62
+ @change="onChangeHandler"
63
+ ></component>
64
+
65
+ <div v-if="extra" v-html="extra" class="m-form-tip"></div>
66
+ </el-form-item>
67
+
68
+ <el-tooltip v-if="config.tip" placement="top" style="line-height: 40px; margin-left: 5px">
69
+ <i class="el-icon-warning"></i>
70
+ <template #content>
71
+ <div v-html="config.tip"></div>
72
+ </template>
73
+ </el-tooltip>
74
+ </template>
75
+
76
+ <template v-else-if="items && display">
77
+ <template v-if="name || name === 0 ? model[name] : model">
78
+ <m-form-container
79
+ v-for="item in items"
80
+ :key="key(item)"
81
+ :model="name || name === 0 ? model[name] : model"
82
+ :config="item"
83
+ :size="size"
84
+ :step-active="stepActive"
85
+ :expand-more="expand"
86
+ :label-width="config.labelWidth || labelWidth"
87
+ :prop="itemProp"
88
+ @change="onChangeHandler"
89
+ ></m-form-container>
90
+ </template>
91
+ </template>
92
+
93
+ <div style="text-align: center" v-if="config.expand && type !== 'fieldset'">
94
+ <el-button type="text" @click="expandHandler">{{ expand ? '收起配置' : '展开更多配置' }}</el-button>
95
+ </div>
96
+ </div>
97
+ </template>
98
+
99
+ <script lang="ts">
100
+ import { computed, defineComponent, inject, PropType, ref, resolveComponent, watchEffect } from 'vue';
101
+
102
+ import { ChildConfig, ContainerCommonConfig, FormState, FormValue } from '../schema';
103
+ import { display as displayFunction, filterFunction, getRules } from '../utils/form';
104
+
105
+ export default defineComponent({
106
+ name: 'm-form-container',
107
+
108
+ props: {
109
+ labelWidth: String,
110
+ expandMore: Boolean,
111
+
112
+ model: {
113
+ type: [Object, Array] as PropType<FormValue>,
114
+ required: true,
115
+ },
116
+
117
+ config: {
118
+ type: Object as PropType<ChildConfig>,
119
+ required: true,
120
+ },
121
+
122
+ prop: {
123
+ type: String,
124
+ default: () => '',
125
+ },
126
+
127
+ stepActive: {
128
+ type: [String, Number],
129
+ },
130
+
131
+ size: {
132
+ type: String,
133
+ default: 'small',
134
+ },
135
+ },
136
+
137
+ emits: ['change'],
138
+
139
+ setup(props, { emit }) {
140
+ const mForm = inject<FormState | undefined>('mForm');
141
+
142
+ const expand = ref(false);
143
+
144
+ const name = computed(() => props.config.name || '');
145
+
146
+ const items = computed(() => (props.config as ContainerCommonConfig).items);
147
+
148
+ const itemProp = computed(() => (name.value ? `${props.prop}${props.prop ? '.' : ''}${name.value}` : props.prop));
149
+
150
+ const tagName = computed(() => {
151
+ const component = resolveComponent(`m-${items.value ? 'form' : 'fields'}-${type.value}`);
152
+ if (typeof component !== 'string') return component;
153
+ return 'm-fields-text';
154
+ });
155
+
156
+ const disabled = computed(() => filterFunction(mForm, props.config.disabled, props));
157
+
158
+ const tooltip = computed(() => filterFunction(mForm, props.config.tooltip, props));
159
+
160
+ const extra = computed(() => filterFunction(mForm, props.config.extra, props));
161
+
162
+ const rule = computed(() => getRules(mForm, props.config.rules, props));
163
+
164
+ const type = computed((): string => {
165
+ let { type } = props.config;
166
+ if (typeof type === 'function') {
167
+ type = type(mForm, {
168
+ model: props.model,
169
+ });
170
+ }
171
+ if (type === 'form') return '';
172
+ return type?.replace(/([A-Z])/g, '-$1').toLowerCase() || (items.value ? '' : 'text');
173
+ });
174
+
175
+ const display = computed((): boolean => {
176
+ if (props.config.display === 'expand') {
177
+ return expand.value;
178
+ }
179
+
180
+ return displayFunction(mForm, props.config.display, props);
181
+ });
182
+
183
+ watchEffect(() => {
184
+ expand.value = props.expandMore;
185
+ });
186
+
187
+ const expandHandler = () => (expand.value = !expand.value);
188
+
189
+ const key = (config: any) => config[mForm?.keyProps];
190
+
191
+ const filterHandler = (filter: any, value: FormValue | number | string) => {
192
+ if (typeof filter === 'function') {
193
+ return filter(mForm, value, {
194
+ model: props.model,
195
+ values: mForm?.initValues,
196
+ formValue: mForm?.values,
197
+ prop: itemProp.value,
198
+ });
199
+ }
200
+
201
+ if (filter === 'number') {
202
+ return +value;
203
+ }
204
+
205
+ return value;
206
+ };
207
+
208
+ const changeHandler = (onChange: any, value: FormValue | number | string) => {
209
+ if (typeof onChange === 'function') {
210
+ return onChange(mForm, value, {
211
+ model: props.model,
212
+ values: mForm?.initValues,
213
+ formValue: mForm?.values,
214
+ prop: itemProp.value,
215
+ });
216
+ }
217
+ };
218
+
219
+ const trimHandler = (trim: any, value: FormValue | number | string) => {
220
+ if (typeof value === 'string' && trim) {
221
+ return value.replace(/^\s*/, '').replace(/\s*$/, '');
222
+ }
223
+ };
224
+
225
+ const onChangeHandler = async function (v: FormValue, key?: string) {
226
+ const { filter, onChange, trim, name, dynamicKey } = props.config as any;
227
+ let value: FormValue | number | string = filterHandler(filter, v);
228
+
229
+ value = (await changeHandler(onChange, value)) ?? value;
230
+ value = trimHandler(trim, value) ?? value;
231
+
232
+ // field内容下包含field-link时,model===value, 这里避免循环引用
233
+ if ((name || name === 0) && props.model !== value && (v !== value || props.model[name] !== value)) {
234
+ // eslint-disable-next-line vue/no-mutating-props
235
+ props.model[name] = value;
236
+ }
237
+ // 动态表单类型,根据value和key参数,直接修改model
238
+ if (key !== undefined && dynamicKey) {
239
+ // eslint-disable-next-line vue/no-mutating-props
240
+ props.model[key] = value;
241
+ }
242
+ emit('change', props.model);
243
+ };
244
+
245
+ return {
246
+ expand,
247
+ name,
248
+ type,
249
+ disabled,
250
+ itemProp,
251
+ items,
252
+ display,
253
+ tagName,
254
+ rule,
255
+ tooltip,
256
+ extra,
257
+ key,
258
+ onChangeHandler,
259
+ expandHandler,
260
+ };
261
+ },
262
+ });
263
+ </script>