@wirenboard/json-editor 2.5.3-wb13
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.
- package/.env-dist +2 -0
- package/.eslintrc +7 -0
- package/.gitattributes +1 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +9 -0
- package/.github/issue_template +25 -0
- package/.github/workflows/build.yml +58 -0
- package/.travis.yml +70 -0
- package/CHANGELOG.md +915 -0
- package/CONTRIBUTING.md +92 -0
- package/LICENSE +20 -0
- package/Makefile +26 -0
- package/README.md +1646 -0
- package/README_ADDON.md +573 -0
- package/UPGRADING.md +46 -0
- package/build/CssToJson.js +55 -0
- package/codecept.conf.js +35 -0
- package/config/.eslintrc +7 -0
- package/config/codeceptjs_helpers.js +139 -0
- package/config/helpers.js +10 -0
- package/config/karma.conf.js +93 -0
- package/config/readme.md +31 -0
- package/config/webpack.common.js +75 -0
- package/config/webpack.dev.js +15 -0
- package/config/webpack.nonmin.js +19 -0
- package/config/webpack.prod.js +25 -0
- package/dist/jsoneditor.js +14 -0
- package/dist/nonmin/jsoneditor.js +29097 -0
- package/dist/nonmin/jsoneditor.js.map +1 -0
- package/docs/ace_editor.html +56 -0
- package/docs/advanced.html +136 -0
- package/docs/basic.html +63 -0
- package/docs/basic_person.json +26 -0
- package/docs/choices.html +86 -0
- package/docs/cleave.html +132 -0
- package/docs/colorpicker.html +194 -0
- package/docs/css_integration.html +135 -0
- package/docs/datetime.html +305 -0
- package/docs/describedby.html +161 -0
- package/docs/enumsource.html +67 -0
- package/docs/images/categoriesDemo.png +0 -0
- package/docs/images/inheritance_tree.png +0 -0
- package/docs/images/jsoneditor.png +0 -0
- package/docs/imask.html +192 -0
- package/docs/index.html +579 -0
- package/docs/materialize_css.html +164 -0
- package/docs/meta_schema.json +705 -0
- package/docs/multiple_upload_base64.html +65 -0
- package/docs/person.json +73 -0
- package/docs/polyfills/assign.js +29 -0
- package/docs/radio.html +156 -0
- package/docs/recursive.html +170 -0
- package/docs/select2.html +99 -0
- package/docs/selectize.html +100 -0
- package/docs/signature.html +42 -0
- package/docs/starrating.html +137 -0
- package/docs/upload.html +131 -0
- package/docs/uuid.html +70 -0
- package/docs/wysiwyg.html +56 -0
- package/jasmine.json +11 -0
- package/json-editor-json-editor-2.5.3-wb13.tgz +0 -0
- package/package.json +100 -0
- package/release-notes.md +88 -0
- package/src/core.js +412 -0
- package/src/defaults.js +402 -0
- package/src/editor.js +707 -0
- package/src/editors/ace.js +89 -0
- package/src/editors/array/choices.js +103 -0
- package/src/editors/array/select2.js +110 -0
- package/src/editors/array/selectize.js +103 -0
- package/src/editors/array.css +9 -0
- package/src/editors/array.css.js +3 -0
- package/src/editors/array.js +818 -0
- package/src/editors/autocomplete.js +58 -0
- package/src/editors/base64.js +157 -0
- package/src/editors/button.js +97 -0
- package/src/editors/checkbox.js +95 -0
- package/src/editors/choices.css +3 -0
- package/src/editors/choices.css.js +3 -0
- package/src/editors/choices.js +69 -0
- package/src/editors/colorpicker.js +103 -0
- package/src/editors/datetime.js +141 -0
- package/src/editors/describedby.js +188 -0
- package/src/editors/enum.js +136 -0
- package/src/editors/hidden.js +127 -0
- package/src/editors/index.js +81 -0
- package/src/editors/info.js +20 -0
- package/src/editors/integer.js +19 -0
- package/src/editors/ip.js +36 -0
- package/src/editors/jodit.js +64 -0
- package/src/editors/multiple.js +409 -0
- package/src/editors/multiselect.js +218 -0
- package/src/editors/null.js +18 -0
- package/src/editors/number.js +51 -0
- package/src/editors/object.css +41 -0
- package/src/editors/object.css.js +3 -0
- package/src/editors/object.js +1290 -0
- package/src/editors/radio.js +111 -0
- package/src/editors/sceditor.js +72 -0
- package/src/editors/select.js +370 -0
- package/src/editors/select2.js +110 -0
- package/src/editors/selectize.js +112 -0
- package/src/editors/signature.js +113 -0
- package/src/editors/simplemde.js +100 -0
- package/src/editors/starrating.css +52 -0
- package/src/editors/starrating.css.js +3 -0
- package/src/editors/starrating.js +135 -0
- package/src/editors/stepper.js +27 -0
- package/src/editors/string.js +372 -0
- package/src/editors/table.js +516 -0
- package/src/editors/upload.js +321 -0
- package/src/editors/uuid.js +56 -0
- package/src/iconlib.js +24 -0
- package/src/iconlibs/bootstrap2.js +28 -0
- package/src/iconlibs/bootstrap3.js +28 -0
- package/src/iconlibs/fontawesome3.js +28 -0
- package/src/iconlibs/fontawesome4.js +28 -0
- package/src/iconlibs/fontawesome5.js +28 -0
- package/src/iconlibs/foundation2.js +24 -0
- package/src/iconlibs/foundation3.js +28 -0
- package/src/iconlibs/index.js +25 -0
- package/src/iconlibs/jqueryui.js +28 -0
- package/src/iconlibs/materialicons.js +49 -0
- package/src/iconlibs/openiconic.js +28 -0
- package/src/iconlibs/spectre.js +28 -0
- package/src/resolvers.js +128 -0
- package/src/schemaloader.js +408 -0
- package/src/style.css +150 -0
- package/src/style.css.js +3 -0
- package/src/templates/default.js +52 -0
- package/src/templates/ejs.js +13 -0
- package/src/templates/handlebars.js +1 -0
- package/src/templates/hogan.js +10 -0
- package/src/templates/index.js +21 -0
- package/src/templates/lodash.js +9 -0
- package/src/templates/markup.js +9 -0
- package/src/templates/mustache.js +9 -0
- package/src/templates/swig.js +1 -0
- package/src/templates/underscore.js +9 -0
- package/src/theme.js +659 -0
- package/src/themes/barebones.css +35 -0
- package/src/themes/barebones.css.js +3 -0
- package/src/themes/barebones.js +28 -0
- package/src/themes/bootstrap2.js +319 -0
- package/src/themes/bootstrap3.css +0 -0
- package/src/themes/bootstrap3.css.js +3 -0
- package/src/themes/bootstrap3.js +315 -0
- package/src/themes/bootstrap4.css +89 -0
- package/src/themes/bootstrap4.css.js +3 -0
- package/src/themes/bootstrap4.js +690 -0
- package/src/themes/bootstrap5.css.js +3 -0
- package/src/themes/foundation.js +569 -0
- package/src/themes/html.css +60 -0
- package/src/themes/html.css.js +3 -0
- package/src/themes/html.js +71 -0
- package/src/themes/index.js +28 -0
- package/src/themes/jqueryui.js +198 -0
- package/src/themes/materialize.js +426 -0
- package/src/themes/spectre.css +208 -0
- package/src/themes/spectre.css.js +3 -0
- package/src/themes/spectre.js +406 -0
- package/src/themes/tailwind.css +249 -0
- package/src/themes/tailwind.css.js +3 -0
- package/src/themes/tailwind.js +443 -0
- package/src/utilities.js +138 -0
- package/src/validator.js +877 -0
- package/src/validators/ip-validator.js +51 -0
- package/tests/Dockerfile +3 -0
- package/tests/README.md +48 -0
- package/tests/codeceptjs/codecept.json +42 -0
- package/tests/codeceptjs/constrains/if-then-else_test.js +143 -0
- package/tests/codeceptjs/core_test.js +217 -0
- package/tests/codeceptjs/editors/advanced_test.js +13 -0
- package/tests/codeceptjs/editors/array_any_of_test.js +50 -0
- package/tests/codeceptjs/editors/array_test.js +900 -0
- package/tests/codeceptjs/editors/button_test.js +35 -0
- package/tests/codeceptjs/editors/checkbox_test.js +21 -0
- package/tests/codeceptjs/editors/colorpicker_test.js +27 -0
- package/tests/codeceptjs/editors/datetime_test.js +33 -0
- package/tests/codeceptjs/editors/inheritance_test.js +11 -0
- package/tests/codeceptjs/editors/integer_test.js +84 -0
- package/tests/codeceptjs/editors/issues/issue-gh-812_test.js +32 -0
- package/tests/codeceptjs/editors/jodit_test.js +24 -0
- package/tests/codeceptjs/editors/multiselect_test.js +8 -0
- package/tests/codeceptjs/editors/number_test.js +82 -0
- package/tests/codeceptjs/editors/object_test.js +204 -0
- package/tests/codeceptjs/editors/option-no_default_values_test.js +42 -0
- package/tests/codeceptjs/editors/programmatic-changes_test.js +20 -0
- package/tests/codeceptjs/editors/radio_test.js +10 -0
- package/tests/codeceptjs/editors/rating_test.js +13 -0
- package/tests/codeceptjs/editors/select_test.js +22 -0
- package/tests/codeceptjs/editors/stepper_test.js +27 -0
- package/tests/codeceptjs/editors/string_test.js +118 -0
- package/tests/codeceptjs/editors/table-confirm-delete_test.js +67 -0
- package/tests/codeceptjs/editors/tabs_test.js +14 -0
- package/tests/codeceptjs/editors/uuid_test.js +21 -0
- package/tests/codeceptjs/editors/validation_test.js +14 -0
- package/tests/codeceptjs/meta-schema_test.js +17 -0
- package/tests/codeceptjs/schemaloader_test.js +13 -0
- package/tests/codeceptjs/steps.d.ts +13 -0
- package/tests/codeceptjs/steps_file.js +12 -0
- package/tests/codeceptjs/themes_test.js +519 -0
- package/tests/docker-compose.yml +34 -0
- package/tests/fixtures/basic_person.json +26 -0
- package/tests/fixtures/nested_object.json +26 -0
- package/tests/fixtures/person.json +55 -0
- package/tests/fixtures/recursive.json +8 -0
- package/tests/fixtures/some_types.json +32 -0
- package/tests/fixtures/string.json +3 -0
- package/tests/fixtures/validation.json +1140 -0
- package/tests/pages/_demo.html +475 -0
- package/tests/pages/advanced.html +137 -0
- package/tests/pages/anyof.html +80 -0
- package/tests/pages/array-anyof.html +142 -0
- package/tests/pages/array-checkboxes.html +41 -0
- package/tests/pages/array-choices.html +45 -0
- package/tests/pages/array-integers.html +37 -0
- package/tests/pages/array-move-events.html +61 -0
- package/tests/pages/array-multiselects.html +42 -0
- package/tests/pages/array-nested-arrays.html +40 -0
- package/tests/pages/array-numbers.html +37 -0
- package/tests/pages/array-objects.html +42 -0
- package/tests/pages/array-ratings.html +40 -0
- package/tests/pages/array-selectize.html +51 -0
- package/tests/pages/array-selects.html +36 -0
- package/tests/pages/array-strings.html +36 -0
- package/tests/pages/array.html +42 -0
- package/tests/pages/assets/pages.css +130 -0
- package/tests/pages/button-callbacks.html +77 -0
- package/tests/pages/checkbox-labels.html +114 -0
- package/tests/pages/colorpicker-no-3rd-party.html +43 -0
- package/tests/pages/colorpicker-use-vanilla-picker.html +50 -0
- package/tests/pages/core.html +118 -0
- package/tests/pages/datetime.html +76 -0
- package/tests/pages/form-name.html +108 -0
- package/tests/pages/grid-strict.html +311 -0
- package/tests/pages/grid.html +284 -0
- package/tests/pages/if-then-else-allOf.html +117 -0
- package/tests/pages/inheritance.html +76 -0
- package/tests/pages/integer.html +68 -0
- package/tests/pages/issues/_template.html +50 -0
- package/tests/pages/issues/issue-gh-812.html +110 -0
- package/tests/pages/issues/issue-gh-823-meta-schema.html +35 -0
- package/tests/pages/issues/issue-gh-848.html +81 -0
- package/tests/pages/meta_schema.json +705 -0
- package/tests/pages/number.html +89 -0
- package/tests/pages/object-no-additional-properties.html +65 -0
- package/tests/pages/object-no-duplicated-id.html +68 -0
- package/tests/pages/object-required-properties.html +236 -0
- package/tests/pages/object-with-dependencies-array.html +46 -0
- package/tests/pages/object-with-dependencies.html +60 -0
- package/tests/pages/object.html +79 -0
- package/tests/pages/oneof.html +103 -0
- package/tests/pages/option-no_default_values.html +58 -0
- package/tests/pages/programmatic-changes.html +120 -0
- package/tests/pages/read-only.html +105 -0
- package/tests/pages/select.html +41 -0
- package/tests/pages/stepper.html +59 -0
- package/tests/pages/string-ace-editor.html +52 -0
- package/tests/pages/string-cleave.html +46 -0
- package/tests/pages/string-custom-attributes.html +62 -0
- package/tests/pages/string-formats.html +52 -0
- package/tests/pages/string-formats2.html +57 -0
- package/tests/pages/string-jodit-editor.html +49 -0
- package/tests/pages/string-sceditor.html +62 -0
- package/tests/pages/table-move-events.html +56 -0
- package/tests/pages/table.html +46 -0
- package/tests/pages/tabs.html +131 -0
- package/tests/pages/themes.html +527 -0
- package/tests/pages/translate-property.html +247 -0
- package/tests/pages/urn.html +93 -0
- package/tests/pages/uuid.html +72 -0
- package/tests/pages/validation.html +99 -0
- package/tests/unit/.eslintrc +8 -0
- package/tests/unit/core.spec.js +309 -0
- package/tests/unit/defaults.spec.js +40 -0
- package/tests/unit/editor.spec.js +160 -0
- package/tests/unit/editors/array.spec.js +86 -0
- package/tests/unit/editors/object.spec.js +79 -0
- package/tests/unit/editors/table.spec.js +91 -0
- package/tests/unit/readme.md +35 -0
- package/tests/unit/schemaloader.spec.js +498 -0
- package/tests/unit/validator.spec.js +94 -0
- package/tests/unit/validators/ip-validator.spec.js +62 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/*
|
|
2
|
+
|
|
3
|
+
Edtended handling of date, time and datetime-local type fields.
|
|
4
|
+
|
|
5
|
+
Works with both string and integer data types. (default only support string type)
|
|
6
|
+
|
|
7
|
+
Has optional support for using flatpickr datepicker.
|
|
8
|
+
All flatpickr options is supported with a few minor differences.
|
|
9
|
+
- "enableTime" and "noCalendar" are set automatically, based on the data type.
|
|
10
|
+
- Extra config option "errorDateFormat". If this is set, it will replace the format displayed in error messages.
|
|
11
|
+
- It is not possible to use "inline" and "wrap" options together.
|
|
12
|
+
- When using the "wrap" option, "toggle" and "clear" buttons are automatically added to markup. 2 extra boolean options ("showToggleButton" and "showClearButton") are available to control which buttons to display. Note: not all frameworks supports this. (Works in: Bootstrap and Foundation)
|
|
13
|
+
- When using the "inline" option, an extra boolean option ("inlineHideInput") is available to hide the original input field.
|
|
14
|
+
- If "mode" is set to either "multiple" or "range", only string data type is supported. Also the result from these is returned as a string not an array.
|
|
15
|
+
|
|
16
|
+
ToDo:
|
|
17
|
+
- Improve Handling of flatpicker "multiple" and "range" modes. (Currently the values are just added as string values, but the optimal scenario would be to save those as array if possible)
|
|
18
|
+
|
|
19
|
+
*/
|
|
20
|
+
import { StringEditor } from './string.js'
|
|
21
|
+
|
|
22
|
+
export class DatetimeEditor extends StringEditor {
|
|
23
|
+
build () {
|
|
24
|
+
super.build()
|
|
25
|
+
if (!this.input) {
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (this.schema.max && typeof this.schema.max === 'string') {
|
|
30
|
+
this.input.setAttribute('max', this.schema.max)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (this.schema.min && typeof this.schema.max === 'string') {
|
|
34
|
+
this.input.setAttribute('min', this.schema.min)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (window.flatpickr && typeof this.options.flatpickr === 'object') {
|
|
38
|
+
/* Make sure that flatpickr settings matches the input type */
|
|
39
|
+
this.options.flatpickr.enableTime = this.schema.format !== 'date'
|
|
40
|
+
this.options.flatpickr.noCalendar = this.schema.format === 'time'
|
|
41
|
+
|
|
42
|
+
/* Curently only string can contain range or multiple values */
|
|
43
|
+
if (this.schema.type === 'integer') this.options.flatpickr.mode = 'single'
|
|
44
|
+
|
|
45
|
+
/* Attribute for flatpicker */
|
|
46
|
+
this.input.setAttribute('data-input', '')
|
|
47
|
+
|
|
48
|
+
let { input } = this
|
|
49
|
+
|
|
50
|
+
if (this.options.flatpickr.wrap === true) {
|
|
51
|
+
/* Create buttons for input group */
|
|
52
|
+
const buttons = []
|
|
53
|
+
if (this.options.flatpickr.showToggleButton !== false) {
|
|
54
|
+
const toggleButton = this.getButton('', this.schema.format === 'time' ? 'time' : 'calendar', 'flatpickr_toggle_button')
|
|
55
|
+
/* Attribute for flatpicker */
|
|
56
|
+
toggleButton.setAttribute('data-toggle', '')
|
|
57
|
+
buttons.push(toggleButton)
|
|
58
|
+
}
|
|
59
|
+
if (this.options.flatpickr.showClearButton !== false) {
|
|
60
|
+
const clearButton = this.getButton('', 'clear', 'flatpickr_clear_button')
|
|
61
|
+
/* Attribute for flatpicker */
|
|
62
|
+
clearButton.setAttribute('data-clear', '')
|
|
63
|
+
buttons.push(clearButton)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* Save position of input field */
|
|
67
|
+
const { parentNode } = this.input; const { nextSibling } = this.input
|
|
68
|
+
|
|
69
|
+
const buttonContainer = this.theme.getInputGroup(this.input, buttons)
|
|
70
|
+
if (buttonContainer !== undefined) {
|
|
71
|
+
/* Make sure "inline" option is turned off */
|
|
72
|
+
this.options.flatpickr.inline = false
|
|
73
|
+
|
|
74
|
+
/* Insert container at same position as input field */
|
|
75
|
+
parentNode.insertBefore(buttonContainer, nextSibling)
|
|
76
|
+
|
|
77
|
+
input = buttonContainer
|
|
78
|
+
} else {
|
|
79
|
+
this.options.flatpickr.wrap = false
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
this.flatpickr = window.flatpickr(input, this.options.flatpickr)
|
|
84
|
+
|
|
85
|
+
if (this.options.flatpickr.inline === true && this.options.flatpickr.inlineHideInput === true) {
|
|
86
|
+
this.input.setAttribute('type', 'hidden')
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
getValue () {
|
|
92
|
+
if (!this.dependenciesFulfilled) {
|
|
93
|
+
return undefined
|
|
94
|
+
}
|
|
95
|
+
if (this.schema.type === 'string') {
|
|
96
|
+
return this.value
|
|
97
|
+
}
|
|
98
|
+
if (this.value === '' || this.value === undefined) {
|
|
99
|
+
return undefined
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const value = this.schema.format === 'time' ? `1970-01-01 ${this.value}` : this.value
|
|
103
|
+
return parseInt(new Date(value).getTime() / 1000)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
setValue (value, initial, fromTemplate) {
|
|
107
|
+
if (this.schema.type === 'string') {
|
|
108
|
+
super.setValue(value, initial, fromTemplate)
|
|
109
|
+
if (this.flatpickr) this.flatpickr.setDate(value)
|
|
110
|
+
} else if (value > 0) {
|
|
111
|
+
const dateObj = new Date(value * 1000)
|
|
112
|
+
const year = dateObj.getFullYear()
|
|
113
|
+
const month = this.zeroPad(dateObj.getMonth() + 1)
|
|
114
|
+
const day = this.zeroPad(dateObj.getDate())
|
|
115
|
+
const hour = this.zeroPad(dateObj.getHours())
|
|
116
|
+
const min = this.zeroPad(dateObj.getMinutes())
|
|
117
|
+
const sec = this.zeroPad(dateObj.getSeconds())
|
|
118
|
+
const date = [year, month, day].join('-')
|
|
119
|
+
const time = [hour, min, sec].join(':')
|
|
120
|
+
let dateValue = `${date}T${time}`
|
|
121
|
+
|
|
122
|
+
if (this.schema.format === 'date') dateValue = date
|
|
123
|
+
else if (this.schema.format === 'time') dateValue = time
|
|
124
|
+
|
|
125
|
+
this.input.value = dateValue
|
|
126
|
+
this.refreshValue()
|
|
127
|
+
if (this.flatpickr) this.flatpickr.setDate(dateValue)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
destroy () {
|
|
132
|
+
if (this.flatpickr) this.flatpickr.destroy()
|
|
133
|
+
this.flatpickr = null
|
|
134
|
+
super.destroy()
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* helper function */
|
|
138
|
+
zeroPad (value) {
|
|
139
|
+
return (`0${value}`).slice(-2)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/* hyper-link describeBy Editor */
|
|
2
|
+
import { AbstractEditor } from '../editor.js'
|
|
3
|
+
import { extend } from '../utilities.js'
|
|
4
|
+
|
|
5
|
+
export class DescribedByEditor extends AbstractEditor {
|
|
6
|
+
register () {
|
|
7
|
+
if (this.editors) {
|
|
8
|
+
for (let i = 0; i < this.editors.length; i++) {
|
|
9
|
+
if (!this.editors[i]) continue
|
|
10
|
+
this.editors[i].unregister()
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (this.editors[this.currentEditor]) this.editors[this.currentEditor].register()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
super.register()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
unregister () {
|
|
20
|
+
super.unregister()
|
|
21
|
+
|
|
22
|
+
if (this.editors) {
|
|
23
|
+
for (let i = 0; i < this.editors.length; i++) {
|
|
24
|
+
if (!this.editors[i]) continue
|
|
25
|
+
this.editors[i].unregister()
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
getNumColumns () {
|
|
31
|
+
if (!this.editors[this.currentEditor]) return 4
|
|
32
|
+
return Math.max(this.editors[this.currentEditor].getNumColumns(), 4)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
enable () {
|
|
36
|
+
if (this.editors) {
|
|
37
|
+
for (let i = 0; i < this.editors.length; i++) {
|
|
38
|
+
if (!this.editors[i]) continue
|
|
39
|
+
this.editors[i].enable()
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
super.enable()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
disable () {
|
|
47
|
+
if (this.editors) {
|
|
48
|
+
for (let i = 0; i < this.editors.length; i++) {
|
|
49
|
+
if (!this.editors[i]) continue
|
|
50
|
+
this.editors[i].disable()
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
super.disable()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
switchEditor () {
|
|
58
|
+
const vars = this.getWatchedFieldValues()
|
|
59
|
+
|
|
60
|
+
if (!vars) return
|
|
61
|
+
|
|
62
|
+
/* var ref = this.template.fillFromObject(vars); */
|
|
63
|
+
/* var ref = this.template(vars); */
|
|
64
|
+
const ref = document.location.origin + document.location.pathname + this.template(vars)
|
|
65
|
+
|
|
66
|
+
if (!this.editors[this.refs[ref]]) {
|
|
67
|
+
this.buildChildEditor(ref)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
this.currentEditor = this.refs[ref]
|
|
71
|
+
|
|
72
|
+
this.register()
|
|
73
|
+
|
|
74
|
+
this.editors.forEach((editor, ref) => {
|
|
75
|
+
if (!editor) return
|
|
76
|
+
if (this.currentEditor === ref) {
|
|
77
|
+
editor.container.style.display = ''
|
|
78
|
+
} else {
|
|
79
|
+
editor.container.style.display = 'none'
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
this.refreshValue()
|
|
84
|
+
this.onChange(true)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
buildChildEditor (ref) {
|
|
88
|
+
this.refs[ref] = this.editors.length
|
|
89
|
+
|
|
90
|
+
const holder = this.theme.getChildEditorHolder()
|
|
91
|
+
this.editor_holder.appendChild(holder)
|
|
92
|
+
|
|
93
|
+
const schema = extend({}, this.schema, this.jsoneditor.refs[ref])
|
|
94
|
+
|
|
95
|
+
const editorClass = this.jsoneditor.getEditorClass(schema, this.jsoneditor)
|
|
96
|
+
|
|
97
|
+
const editor = this.jsoneditor.createEditor(editorClass, {
|
|
98
|
+
jsoneditor: this.jsoneditor,
|
|
99
|
+
schema,
|
|
100
|
+
container: holder,
|
|
101
|
+
path: this.path,
|
|
102
|
+
parent: this,
|
|
103
|
+
required: true
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
this.editors.push(editor)
|
|
108
|
+
|
|
109
|
+
editor.preBuild()
|
|
110
|
+
editor.build()
|
|
111
|
+
editor.postBuild()
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
preBuild () {
|
|
115
|
+
this.refs = {}
|
|
116
|
+
this.editors = []
|
|
117
|
+
this.currentEditor = ''
|
|
118
|
+
let i
|
|
119
|
+
for (i = 0; i < this.schema.links.length; i++) {
|
|
120
|
+
if (this.schema.links[i].rel.toLowerCase() === 'describedby') {
|
|
121
|
+
/* this.template = new UriTemplate(this.schema.links[i].href); */
|
|
122
|
+
this.template = this.jsoneditor.compileTemplate(this.schema.links[i].href, this.template_engine)
|
|
123
|
+
break
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* this.template.fill(function(varName) {
|
|
128
|
+
this.schema.watch = this.schema.watch || {};
|
|
129
|
+
this.schema.watch[varName] = varName;
|
|
130
|
+
return '';
|
|
131
|
+
}); */
|
|
132
|
+
|
|
133
|
+
this.schema.links = this.schema.links.slice(0, i).concat(this.schema.links.slice(i + 1))
|
|
134
|
+
if (this.schema.links.length === 0) delete this.schema.links
|
|
135
|
+
this.baseSchema = extend({}, this.schema)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
build () {
|
|
139
|
+
this.editor_holder = document.createElement('div')
|
|
140
|
+
this.container.appendChild(this.editor_holder)
|
|
141
|
+
this.switchEditor()
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
onWatchedFieldChange () {
|
|
145
|
+
this.switchEditor()
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
onChildEditorChange (editor) {
|
|
149
|
+
if (this.editors[this.currentEditor]) {
|
|
150
|
+
this.refreshValue()
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
super.onChildEditorChange(editor)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
refreshValue () {
|
|
157
|
+
if (this.editors[this.currentEditor]) {
|
|
158
|
+
this.value = this.editors[this.currentEditor].getValue()
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
setValue (val, initial) {
|
|
163
|
+
if (this.editors[this.currentEditor]) {
|
|
164
|
+
this.editors[this.currentEditor].setValue(val, initial)
|
|
165
|
+
this.refreshValue()
|
|
166
|
+
this.onChange()
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
destroy () {
|
|
171
|
+
this.editors.forEach(editor => {
|
|
172
|
+
if (editor) editor.destroy()
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
if (this.editor_holder && this.editor_holder.parentNode) {
|
|
176
|
+
this.editor_holder.parentNode.removeChild(this.editor_holder)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
super.destroy()
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
showValidationErrors (errors) {
|
|
183
|
+
this.editors.forEach(editor => {
|
|
184
|
+
if (!editor) return
|
|
185
|
+
editor.showValidationErrors(errors)
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/* Enum Editor (used for objects and arrays with enumerated values) */
|
|
2
|
+
import { AbstractEditor } from '../editor.js'
|
|
3
|
+
|
|
4
|
+
export class EnumEditor extends AbstractEditor {
|
|
5
|
+
getNumColumns () {
|
|
6
|
+
return 4
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
build () {
|
|
10
|
+
this.title = this.header = this.label = this.theme.getFormInputLabel(this.getTitle(), this.isRequired())
|
|
11
|
+
this.container.appendChild(this.title)
|
|
12
|
+
|
|
13
|
+
this.options.enum_titles = this.options.enum_titles || []
|
|
14
|
+
|
|
15
|
+
this.enum = this.schema.enum
|
|
16
|
+
this.selected = 0
|
|
17
|
+
this.select_options = []
|
|
18
|
+
this.html_values = []
|
|
19
|
+
|
|
20
|
+
for (let i = 0; i < this.enum.length; i++) {
|
|
21
|
+
this.select_options[i] = this.options.enum_titles[i] || `Value ${i + 1}`
|
|
22
|
+
this.html_values[i] = this.getHTML(this.enum[i])
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* Switcher */
|
|
26
|
+
this.switcher = this.theme.getSwitcher(this.select_options)
|
|
27
|
+
this.container.appendChild(this.switcher)
|
|
28
|
+
|
|
29
|
+
/* Display area */
|
|
30
|
+
this.display_area = this.theme.getIndentedPanel()
|
|
31
|
+
this.container.appendChild(this.display_area)
|
|
32
|
+
|
|
33
|
+
if (this.options.hide_display) this.display_area.style.display = 'none'
|
|
34
|
+
|
|
35
|
+
this.switcher.addEventListener('change', e => {
|
|
36
|
+
this.selected = this.select_options.indexOf(e.currentTarget.value)
|
|
37
|
+
this.value = this.enum[this.selected]
|
|
38
|
+
this.refreshValue()
|
|
39
|
+
this.onChange(true)
|
|
40
|
+
})
|
|
41
|
+
this.value = this.enum[0]
|
|
42
|
+
this.refreshValue()
|
|
43
|
+
|
|
44
|
+
if (this.enum.length === 1) this.switcher.style.display = 'none'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
refreshValue () {
|
|
48
|
+
this.selected = -1
|
|
49
|
+
const stringified = JSON.stringify(this.value)
|
|
50
|
+
this.enum.forEach((el, i) => {
|
|
51
|
+
if (stringified === JSON.stringify(el)) {
|
|
52
|
+
this.selected = i
|
|
53
|
+
return false
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
if (this.selected < 0) {
|
|
58
|
+
this.setValue(this.enum[0])
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
this.switcher.value = this.select_options[this.selected]
|
|
63
|
+
this.display_area.innerHTML = this.html_values[this.selected]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
enable () {
|
|
67
|
+
if (!this.always_disabled) {
|
|
68
|
+
this.switcher.disabled = false
|
|
69
|
+
super.enable()
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
disable (alwaysDisabled) {
|
|
74
|
+
if (alwaysDisabled) this.always_disabled = true
|
|
75
|
+
this.switcher.disabled = true
|
|
76
|
+
super.disable()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
getHTML (el) {
|
|
80
|
+
const each = (obj, callback) => {
|
|
81
|
+
if (Array.isArray(obj) || (typeof obj.length === 'number' && obj.length > 0 && (obj.length - 1) in obj)) {
|
|
82
|
+
Array.from(obj).forEach((e, i) => callback(i, e))
|
|
83
|
+
} else {
|
|
84
|
+
Object.entries(obj).forEach(([key, value]) => callback(key, value))
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (el === null) {
|
|
89
|
+
return '<em>null</em>'
|
|
90
|
+
/* Array or Object */
|
|
91
|
+
} else if (typeof el === 'object') {
|
|
92
|
+
/* TODO: use theme */
|
|
93
|
+
let ret = ''
|
|
94
|
+
const callback = (i, child) => {
|
|
95
|
+
let html = this.getHTML(child)
|
|
96
|
+
/* Add the keys to object children */
|
|
97
|
+
if (!(Array.isArray(el))) {
|
|
98
|
+
/* TODO: use theme */
|
|
99
|
+
html = `<div><em>${i}</em>: ${html}</div>`
|
|
100
|
+
}
|
|
101
|
+
/* TODO: use theme */
|
|
102
|
+
ret += `<li>${html}</li>`
|
|
103
|
+
}
|
|
104
|
+
each(el, callback)
|
|
105
|
+
|
|
106
|
+
if (Array.isArray(el)) ret = `<ol>${ret}</ol>`
|
|
107
|
+
else ret = `<ul style='margin-top:0;margin-bottom:0;padding-top:0;padding-bottom:0;'>${ret}</ul>`
|
|
108
|
+
|
|
109
|
+
return ret
|
|
110
|
+
/* Boolean */
|
|
111
|
+
} else if (typeof el === 'boolean') {
|
|
112
|
+
return el ? 'true' : 'false'
|
|
113
|
+
/* String */
|
|
114
|
+
} else if (typeof el === 'string') {
|
|
115
|
+
return el.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
116
|
+
/* Number */
|
|
117
|
+
}
|
|
118
|
+
return el
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
setValue (val) {
|
|
122
|
+
if (this.value !== val) {
|
|
123
|
+
this.value = val
|
|
124
|
+
this.refreshValue()
|
|
125
|
+
this.onChange()
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
destroy () {
|
|
130
|
+
if (this.display_area && this.display_area.parentNode) this.display_area.parentNode.removeChild(this.display_area)
|
|
131
|
+
if (this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title)
|
|
132
|
+
if (this.switcher && this.switcher.parentNode) this.switcher.parentNode.removeChild(this.switcher)
|
|
133
|
+
|
|
134
|
+
super.destroy()
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Created by Mehmet Baker on 12.04.2017
|
|
3
|
+
*/
|
|
4
|
+
import { AbstractEditor } from '../editor.js'
|
|
5
|
+
|
|
6
|
+
export class HiddenEditor extends AbstractEditor {
|
|
7
|
+
register () {
|
|
8
|
+
super.register()
|
|
9
|
+
if (!this.input) return
|
|
10
|
+
this.input.setAttribute('name', this.formname)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
unregister () {
|
|
14
|
+
super.unregister()
|
|
15
|
+
if (!this.input) return
|
|
16
|
+
this.input.removeAttribute('name')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
setValue (value, initial, fromTemplate) {
|
|
20
|
+
if (this.template && !fromTemplate) {
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (value === null || typeof value === 'undefined') value = ''
|
|
25
|
+
else if (typeof value === 'object') value = JSON.stringify(value)
|
|
26
|
+
else if (typeof value !== 'string') value = `${value}`
|
|
27
|
+
|
|
28
|
+
if (value === this.serialized) return
|
|
29
|
+
|
|
30
|
+
/* Sanitize value before setting it */
|
|
31
|
+
const sanitized = this.sanitize(value)
|
|
32
|
+
|
|
33
|
+
if (this.input.value === sanitized) {
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
this.input.value = sanitized
|
|
38
|
+
|
|
39
|
+
const changed = fromTemplate || this.getValue() !== value
|
|
40
|
+
|
|
41
|
+
this.refreshValue()
|
|
42
|
+
|
|
43
|
+
if (initial) this.is_dirty = false
|
|
44
|
+
else if (this.jsoneditor.options.show_errors === 'change') this.is_dirty = true
|
|
45
|
+
|
|
46
|
+
if (this.adjust_height) this.adjust_height(this.input)
|
|
47
|
+
|
|
48
|
+
/* Bubble this setValue to parents if the value changed */
|
|
49
|
+
this.onChange(changed)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
getNumColumns () {
|
|
53
|
+
return 2
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
enable () {
|
|
57
|
+
super.enable()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
disable () {
|
|
61
|
+
super.disable()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
refreshValue () {
|
|
65
|
+
this.value = this.input.value
|
|
66
|
+
if (typeof this.value !== 'string') this.value = ''
|
|
67
|
+
this.serialized = this.value
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
destroy () {
|
|
71
|
+
this.template = null
|
|
72
|
+
if (this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input)
|
|
73
|
+
if (this.label && this.label.parentNode) this.label.parentNode.removeChild(this.label)
|
|
74
|
+
if (this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description)
|
|
75
|
+
|
|
76
|
+
super.destroy()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* This is overridden in derivative editors
|
|
81
|
+
*/
|
|
82
|
+
sanitize (value) {
|
|
83
|
+
return value
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Re-calculates the value if needed
|
|
88
|
+
*/
|
|
89
|
+
onWatchedFieldChange () {
|
|
90
|
+
let vars
|
|
91
|
+
|
|
92
|
+
/* If this editor needs to be rendered by a macro template */
|
|
93
|
+
if (this.template) {
|
|
94
|
+
vars = this.getWatchedFieldValues()
|
|
95
|
+
this.setValue(this.template(vars), false, true)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
super.onWatchedFieldChange()
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
build () {
|
|
102
|
+
this.format = this.schema.format
|
|
103
|
+
if (!this.format && this.options.default_format) {
|
|
104
|
+
this.format = this.options.default_format
|
|
105
|
+
}
|
|
106
|
+
if (this.options.format) {
|
|
107
|
+
this.format = this.options.format
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
this.input_type = 'hidden'
|
|
111
|
+
this.input = this.theme.getFormInputField(this.input_type)
|
|
112
|
+
|
|
113
|
+
if (this.format) this.input.setAttribute('data-schemaformat', this.format)
|
|
114
|
+
|
|
115
|
+
this.container.appendChild(this.input)
|
|
116
|
+
|
|
117
|
+
/* Compile and store the template */
|
|
118
|
+
if (this.schema.template) {
|
|
119
|
+
const callback = this.expandCallbacks('template', { template: this.schema.template })
|
|
120
|
+
if (typeof callback.template === 'function') this.template = callback.template
|
|
121
|
+
else this.template = this.jsoneditor.compileTemplate(this.schema.template, this.template_engine)
|
|
122
|
+
this.refreshValue()
|
|
123
|
+
} else {
|
|
124
|
+
this.refreshValue()
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/* Internal helper function called only here so we won't export as part of class */
|
|
2
|
+
/* Previously the assignment to the JSONEditor.defaults.editors was done in each of the editor */
|
|
3
|
+
/* files but doing it this way removes each of the editors' dependency on JSONEditor */
|
|
4
|
+
|
|
5
|
+
import { AceEditor as ace } from './ace.js'
|
|
6
|
+
import { ArrayEditor as array } from './array.js'
|
|
7
|
+
import { ArrayChoicesEditor as arrayChoices } from './array/choices.js'
|
|
8
|
+
import { ArraySelect2Editor as arraySelect2 } from './array/select2.js'
|
|
9
|
+
import { ArraySelectizeEditor as arraySelectize } from './array/selectize.js'
|
|
10
|
+
import { AutocompleteEditor as autocomplete } from './autocomplete.js'
|
|
11
|
+
import { Base64Editor as base64 } from './base64.js'
|
|
12
|
+
import { ButtonEditor as button } from './button.js'
|
|
13
|
+
import { CheckboxEditor as checkbox } from './checkbox.js'
|
|
14
|
+
import { ChoicesEditor as choices } from './choices.js'
|
|
15
|
+
import { DatetimeEditor as datetime } from './datetime.js'
|
|
16
|
+
import { DescribedByEditor as describedBy } from './describedby.js'
|
|
17
|
+
import { EnumEditor } from './enum.js'
|
|
18
|
+
import { HiddenEditor as hidden } from './hidden.js'
|
|
19
|
+
import { InfoEditor as info } from './info.js'
|
|
20
|
+
import { IntegerEditor as integer } from './integer.js'
|
|
21
|
+
import { IpEditor as ip } from './ip.js'
|
|
22
|
+
import { JoditEditor as jodit } from './jodit.js'
|
|
23
|
+
import { MultipleEditor as multiple } from './multiple.js'
|
|
24
|
+
import { MultiSelectEditor as multiselect } from './multiselect.js'
|
|
25
|
+
import { NullEditor } from './null.js'
|
|
26
|
+
import { NumberEditor as number } from './number.js'
|
|
27
|
+
import { ObjectEditor as object } from './object.js'
|
|
28
|
+
import { RadioEditor as radio } from './radio.js'
|
|
29
|
+
import { ScEditor as sceditor } from './sceditor.js'
|
|
30
|
+
import { SelectEditor as select } from './select.js'
|
|
31
|
+
import { Select2Editor as select2 } from './select2.js'
|
|
32
|
+
import { SelectizeEditor as selectize } from './selectize.js'
|
|
33
|
+
import { SignatureEditor as signature } from './signature.js'
|
|
34
|
+
import { SimplemdeEditor as simplemde } from './simplemde.js'
|
|
35
|
+
import { StarratingEditor as starrating } from './starrating.js'
|
|
36
|
+
import { StepperEditor as stepper } from './stepper.js'
|
|
37
|
+
import { StringEditor as string } from './string.js'
|
|
38
|
+
import { TableEditor as table } from './table.js'
|
|
39
|
+
import { UploadEditor as upload } from './upload.js'
|
|
40
|
+
import { UuidEditor as uuid } from './uuid.js'
|
|
41
|
+
import { ColorEditor as colorpicker } from './colorpicker.js'
|
|
42
|
+
|
|
43
|
+
export const editors = {
|
|
44
|
+
ace,
|
|
45
|
+
array,
|
|
46
|
+
arrayChoices,
|
|
47
|
+
arraySelect2,
|
|
48
|
+
arraySelectize,
|
|
49
|
+
autocomplete,
|
|
50
|
+
base64,
|
|
51
|
+
button,
|
|
52
|
+
checkbox,
|
|
53
|
+
choices,
|
|
54
|
+
datetime,
|
|
55
|
+
describedBy,
|
|
56
|
+
enum: EnumEditor,
|
|
57
|
+
hidden,
|
|
58
|
+
info,
|
|
59
|
+
integer,
|
|
60
|
+
ip,
|
|
61
|
+
jodit,
|
|
62
|
+
multiple,
|
|
63
|
+
multiselect,
|
|
64
|
+
null: NullEditor,
|
|
65
|
+
number,
|
|
66
|
+
object,
|
|
67
|
+
radio,
|
|
68
|
+
sceditor,
|
|
69
|
+
select,
|
|
70
|
+
select2,
|
|
71
|
+
selectize,
|
|
72
|
+
signature,
|
|
73
|
+
simplemde,
|
|
74
|
+
starrating,
|
|
75
|
+
stepper,
|
|
76
|
+
string,
|
|
77
|
+
table,
|
|
78
|
+
upload,
|
|
79
|
+
uuid,
|
|
80
|
+
colorpicker
|
|
81
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/* Non-Active editor for displaying text blocks in form */
|
|
2
|
+
import { ButtonEditor } from './button.js'
|
|
3
|
+
|
|
4
|
+
export class InfoEditor extends ButtonEditor {
|
|
5
|
+
build () {
|
|
6
|
+
this.options.compact = false
|
|
7
|
+
this.header = this.label = this.theme.getFormInputLabel(this.getTitle())
|
|
8
|
+
this.description = this.theme.getDescription(this.schema.description || '')
|
|
9
|
+
this.control = this.theme.getFormControl(this.label, this.description, null)
|
|
10
|
+
this.container.appendChild(this.control)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
getTitle () {
|
|
14
|
+
return this.translateProperty(this.schema.title)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
getNumColumns () {
|
|
18
|
+
return 12
|
|
19
|
+
}
|
|
20
|
+
}
|