@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.
Files changed (283) hide show
  1. package/.env-dist +2 -0
  2. package/.eslintrc +7 -0
  3. package/.gitattributes +1 -0
  4. package/.github/PULL_REQUEST_TEMPLATE.md +9 -0
  5. package/.github/issue_template +25 -0
  6. package/.github/workflows/build.yml +58 -0
  7. package/.travis.yml +70 -0
  8. package/CHANGELOG.md +915 -0
  9. package/CONTRIBUTING.md +92 -0
  10. package/LICENSE +20 -0
  11. package/Makefile +26 -0
  12. package/README.md +1646 -0
  13. package/README_ADDON.md +573 -0
  14. package/UPGRADING.md +46 -0
  15. package/build/CssToJson.js +55 -0
  16. package/codecept.conf.js +35 -0
  17. package/config/.eslintrc +7 -0
  18. package/config/codeceptjs_helpers.js +139 -0
  19. package/config/helpers.js +10 -0
  20. package/config/karma.conf.js +93 -0
  21. package/config/readme.md +31 -0
  22. package/config/webpack.common.js +75 -0
  23. package/config/webpack.dev.js +15 -0
  24. package/config/webpack.nonmin.js +19 -0
  25. package/config/webpack.prod.js +25 -0
  26. package/dist/jsoneditor.js +14 -0
  27. package/dist/nonmin/jsoneditor.js +29097 -0
  28. package/dist/nonmin/jsoneditor.js.map +1 -0
  29. package/docs/ace_editor.html +56 -0
  30. package/docs/advanced.html +136 -0
  31. package/docs/basic.html +63 -0
  32. package/docs/basic_person.json +26 -0
  33. package/docs/choices.html +86 -0
  34. package/docs/cleave.html +132 -0
  35. package/docs/colorpicker.html +194 -0
  36. package/docs/css_integration.html +135 -0
  37. package/docs/datetime.html +305 -0
  38. package/docs/describedby.html +161 -0
  39. package/docs/enumsource.html +67 -0
  40. package/docs/images/categoriesDemo.png +0 -0
  41. package/docs/images/inheritance_tree.png +0 -0
  42. package/docs/images/jsoneditor.png +0 -0
  43. package/docs/imask.html +192 -0
  44. package/docs/index.html +579 -0
  45. package/docs/materialize_css.html +164 -0
  46. package/docs/meta_schema.json +705 -0
  47. package/docs/multiple_upload_base64.html +65 -0
  48. package/docs/person.json +73 -0
  49. package/docs/polyfills/assign.js +29 -0
  50. package/docs/radio.html +156 -0
  51. package/docs/recursive.html +170 -0
  52. package/docs/select2.html +99 -0
  53. package/docs/selectize.html +100 -0
  54. package/docs/signature.html +42 -0
  55. package/docs/starrating.html +137 -0
  56. package/docs/upload.html +131 -0
  57. package/docs/uuid.html +70 -0
  58. package/docs/wysiwyg.html +56 -0
  59. package/jasmine.json +11 -0
  60. package/json-editor-json-editor-2.5.3-wb13.tgz +0 -0
  61. package/package.json +100 -0
  62. package/release-notes.md +88 -0
  63. package/src/core.js +412 -0
  64. package/src/defaults.js +402 -0
  65. package/src/editor.js +707 -0
  66. package/src/editors/ace.js +89 -0
  67. package/src/editors/array/choices.js +103 -0
  68. package/src/editors/array/select2.js +110 -0
  69. package/src/editors/array/selectize.js +103 -0
  70. package/src/editors/array.css +9 -0
  71. package/src/editors/array.css.js +3 -0
  72. package/src/editors/array.js +818 -0
  73. package/src/editors/autocomplete.js +58 -0
  74. package/src/editors/base64.js +157 -0
  75. package/src/editors/button.js +97 -0
  76. package/src/editors/checkbox.js +95 -0
  77. package/src/editors/choices.css +3 -0
  78. package/src/editors/choices.css.js +3 -0
  79. package/src/editors/choices.js +69 -0
  80. package/src/editors/colorpicker.js +103 -0
  81. package/src/editors/datetime.js +141 -0
  82. package/src/editors/describedby.js +188 -0
  83. package/src/editors/enum.js +136 -0
  84. package/src/editors/hidden.js +127 -0
  85. package/src/editors/index.js +81 -0
  86. package/src/editors/info.js +20 -0
  87. package/src/editors/integer.js +19 -0
  88. package/src/editors/ip.js +36 -0
  89. package/src/editors/jodit.js +64 -0
  90. package/src/editors/multiple.js +409 -0
  91. package/src/editors/multiselect.js +218 -0
  92. package/src/editors/null.js +18 -0
  93. package/src/editors/number.js +51 -0
  94. package/src/editors/object.css +41 -0
  95. package/src/editors/object.css.js +3 -0
  96. package/src/editors/object.js +1290 -0
  97. package/src/editors/radio.js +111 -0
  98. package/src/editors/sceditor.js +72 -0
  99. package/src/editors/select.js +370 -0
  100. package/src/editors/select2.js +110 -0
  101. package/src/editors/selectize.js +112 -0
  102. package/src/editors/signature.js +113 -0
  103. package/src/editors/simplemde.js +100 -0
  104. package/src/editors/starrating.css +52 -0
  105. package/src/editors/starrating.css.js +3 -0
  106. package/src/editors/starrating.js +135 -0
  107. package/src/editors/stepper.js +27 -0
  108. package/src/editors/string.js +372 -0
  109. package/src/editors/table.js +516 -0
  110. package/src/editors/upload.js +321 -0
  111. package/src/editors/uuid.js +56 -0
  112. package/src/iconlib.js +24 -0
  113. package/src/iconlibs/bootstrap2.js +28 -0
  114. package/src/iconlibs/bootstrap3.js +28 -0
  115. package/src/iconlibs/fontawesome3.js +28 -0
  116. package/src/iconlibs/fontawesome4.js +28 -0
  117. package/src/iconlibs/fontawesome5.js +28 -0
  118. package/src/iconlibs/foundation2.js +24 -0
  119. package/src/iconlibs/foundation3.js +28 -0
  120. package/src/iconlibs/index.js +25 -0
  121. package/src/iconlibs/jqueryui.js +28 -0
  122. package/src/iconlibs/materialicons.js +49 -0
  123. package/src/iconlibs/openiconic.js +28 -0
  124. package/src/iconlibs/spectre.js +28 -0
  125. package/src/resolvers.js +128 -0
  126. package/src/schemaloader.js +408 -0
  127. package/src/style.css +150 -0
  128. package/src/style.css.js +3 -0
  129. package/src/templates/default.js +52 -0
  130. package/src/templates/ejs.js +13 -0
  131. package/src/templates/handlebars.js +1 -0
  132. package/src/templates/hogan.js +10 -0
  133. package/src/templates/index.js +21 -0
  134. package/src/templates/lodash.js +9 -0
  135. package/src/templates/markup.js +9 -0
  136. package/src/templates/mustache.js +9 -0
  137. package/src/templates/swig.js +1 -0
  138. package/src/templates/underscore.js +9 -0
  139. package/src/theme.js +659 -0
  140. package/src/themes/barebones.css +35 -0
  141. package/src/themes/barebones.css.js +3 -0
  142. package/src/themes/barebones.js +28 -0
  143. package/src/themes/bootstrap2.js +319 -0
  144. package/src/themes/bootstrap3.css +0 -0
  145. package/src/themes/bootstrap3.css.js +3 -0
  146. package/src/themes/bootstrap3.js +315 -0
  147. package/src/themes/bootstrap4.css +89 -0
  148. package/src/themes/bootstrap4.css.js +3 -0
  149. package/src/themes/bootstrap4.js +690 -0
  150. package/src/themes/bootstrap5.css.js +3 -0
  151. package/src/themes/foundation.js +569 -0
  152. package/src/themes/html.css +60 -0
  153. package/src/themes/html.css.js +3 -0
  154. package/src/themes/html.js +71 -0
  155. package/src/themes/index.js +28 -0
  156. package/src/themes/jqueryui.js +198 -0
  157. package/src/themes/materialize.js +426 -0
  158. package/src/themes/spectre.css +208 -0
  159. package/src/themes/spectre.css.js +3 -0
  160. package/src/themes/spectre.js +406 -0
  161. package/src/themes/tailwind.css +249 -0
  162. package/src/themes/tailwind.css.js +3 -0
  163. package/src/themes/tailwind.js +443 -0
  164. package/src/utilities.js +138 -0
  165. package/src/validator.js +877 -0
  166. package/src/validators/ip-validator.js +51 -0
  167. package/tests/Dockerfile +3 -0
  168. package/tests/README.md +48 -0
  169. package/tests/codeceptjs/codecept.json +42 -0
  170. package/tests/codeceptjs/constrains/if-then-else_test.js +143 -0
  171. package/tests/codeceptjs/core_test.js +217 -0
  172. package/tests/codeceptjs/editors/advanced_test.js +13 -0
  173. package/tests/codeceptjs/editors/array_any_of_test.js +50 -0
  174. package/tests/codeceptjs/editors/array_test.js +900 -0
  175. package/tests/codeceptjs/editors/button_test.js +35 -0
  176. package/tests/codeceptjs/editors/checkbox_test.js +21 -0
  177. package/tests/codeceptjs/editors/colorpicker_test.js +27 -0
  178. package/tests/codeceptjs/editors/datetime_test.js +33 -0
  179. package/tests/codeceptjs/editors/inheritance_test.js +11 -0
  180. package/tests/codeceptjs/editors/integer_test.js +84 -0
  181. package/tests/codeceptjs/editors/issues/issue-gh-812_test.js +32 -0
  182. package/tests/codeceptjs/editors/jodit_test.js +24 -0
  183. package/tests/codeceptjs/editors/multiselect_test.js +8 -0
  184. package/tests/codeceptjs/editors/number_test.js +82 -0
  185. package/tests/codeceptjs/editors/object_test.js +204 -0
  186. package/tests/codeceptjs/editors/option-no_default_values_test.js +42 -0
  187. package/tests/codeceptjs/editors/programmatic-changes_test.js +20 -0
  188. package/tests/codeceptjs/editors/radio_test.js +10 -0
  189. package/tests/codeceptjs/editors/rating_test.js +13 -0
  190. package/tests/codeceptjs/editors/select_test.js +22 -0
  191. package/tests/codeceptjs/editors/stepper_test.js +27 -0
  192. package/tests/codeceptjs/editors/string_test.js +118 -0
  193. package/tests/codeceptjs/editors/table-confirm-delete_test.js +67 -0
  194. package/tests/codeceptjs/editors/tabs_test.js +14 -0
  195. package/tests/codeceptjs/editors/uuid_test.js +21 -0
  196. package/tests/codeceptjs/editors/validation_test.js +14 -0
  197. package/tests/codeceptjs/meta-schema_test.js +17 -0
  198. package/tests/codeceptjs/schemaloader_test.js +13 -0
  199. package/tests/codeceptjs/steps.d.ts +13 -0
  200. package/tests/codeceptjs/steps_file.js +12 -0
  201. package/tests/codeceptjs/themes_test.js +519 -0
  202. package/tests/docker-compose.yml +34 -0
  203. package/tests/fixtures/basic_person.json +26 -0
  204. package/tests/fixtures/nested_object.json +26 -0
  205. package/tests/fixtures/person.json +55 -0
  206. package/tests/fixtures/recursive.json +8 -0
  207. package/tests/fixtures/some_types.json +32 -0
  208. package/tests/fixtures/string.json +3 -0
  209. package/tests/fixtures/validation.json +1140 -0
  210. package/tests/pages/_demo.html +475 -0
  211. package/tests/pages/advanced.html +137 -0
  212. package/tests/pages/anyof.html +80 -0
  213. package/tests/pages/array-anyof.html +142 -0
  214. package/tests/pages/array-checkboxes.html +41 -0
  215. package/tests/pages/array-choices.html +45 -0
  216. package/tests/pages/array-integers.html +37 -0
  217. package/tests/pages/array-move-events.html +61 -0
  218. package/tests/pages/array-multiselects.html +42 -0
  219. package/tests/pages/array-nested-arrays.html +40 -0
  220. package/tests/pages/array-numbers.html +37 -0
  221. package/tests/pages/array-objects.html +42 -0
  222. package/tests/pages/array-ratings.html +40 -0
  223. package/tests/pages/array-selectize.html +51 -0
  224. package/tests/pages/array-selects.html +36 -0
  225. package/tests/pages/array-strings.html +36 -0
  226. package/tests/pages/array.html +42 -0
  227. package/tests/pages/assets/pages.css +130 -0
  228. package/tests/pages/button-callbacks.html +77 -0
  229. package/tests/pages/checkbox-labels.html +114 -0
  230. package/tests/pages/colorpicker-no-3rd-party.html +43 -0
  231. package/tests/pages/colorpicker-use-vanilla-picker.html +50 -0
  232. package/tests/pages/core.html +118 -0
  233. package/tests/pages/datetime.html +76 -0
  234. package/tests/pages/form-name.html +108 -0
  235. package/tests/pages/grid-strict.html +311 -0
  236. package/tests/pages/grid.html +284 -0
  237. package/tests/pages/if-then-else-allOf.html +117 -0
  238. package/tests/pages/inheritance.html +76 -0
  239. package/tests/pages/integer.html +68 -0
  240. package/tests/pages/issues/_template.html +50 -0
  241. package/tests/pages/issues/issue-gh-812.html +110 -0
  242. package/tests/pages/issues/issue-gh-823-meta-schema.html +35 -0
  243. package/tests/pages/issues/issue-gh-848.html +81 -0
  244. package/tests/pages/meta_schema.json +705 -0
  245. package/tests/pages/number.html +89 -0
  246. package/tests/pages/object-no-additional-properties.html +65 -0
  247. package/tests/pages/object-no-duplicated-id.html +68 -0
  248. package/tests/pages/object-required-properties.html +236 -0
  249. package/tests/pages/object-with-dependencies-array.html +46 -0
  250. package/tests/pages/object-with-dependencies.html +60 -0
  251. package/tests/pages/object.html +79 -0
  252. package/tests/pages/oneof.html +103 -0
  253. package/tests/pages/option-no_default_values.html +58 -0
  254. package/tests/pages/programmatic-changes.html +120 -0
  255. package/tests/pages/read-only.html +105 -0
  256. package/tests/pages/select.html +41 -0
  257. package/tests/pages/stepper.html +59 -0
  258. package/tests/pages/string-ace-editor.html +52 -0
  259. package/tests/pages/string-cleave.html +46 -0
  260. package/tests/pages/string-custom-attributes.html +62 -0
  261. package/tests/pages/string-formats.html +52 -0
  262. package/tests/pages/string-formats2.html +57 -0
  263. package/tests/pages/string-jodit-editor.html +49 -0
  264. package/tests/pages/string-sceditor.html +62 -0
  265. package/tests/pages/table-move-events.html +56 -0
  266. package/tests/pages/table.html +46 -0
  267. package/tests/pages/tabs.html +131 -0
  268. package/tests/pages/themes.html +527 -0
  269. package/tests/pages/translate-property.html +247 -0
  270. package/tests/pages/urn.html +93 -0
  271. package/tests/pages/uuid.html +72 -0
  272. package/tests/pages/validation.html +99 -0
  273. package/tests/unit/.eslintrc +8 -0
  274. package/tests/unit/core.spec.js +309 -0
  275. package/tests/unit/defaults.spec.js +40 -0
  276. package/tests/unit/editor.spec.js +160 -0
  277. package/tests/unit/editors/array.spec.js +86 -0
  278. package/tests/unit/editors/object.spec.js +79 -0
  279. package/tests/unit/editors/table.spec.js +91 -0
  280. package/tests/unit/readme.md +35 -0
  281. package/tests/unit/schemaloader.spec.js +498 -0
  282. package/tests/unit/validator.spec.js +94 -0
  283. package/tests/unit/validators/ip-validator.spec.js +62 -0
@@ -0,0 +1,111 @@
1
+ import { SelectEditor } from './select.js'
2
+
3
+ export class RadioEditor extends SelectEditor {
4
+ preBuild () {
5
+ this.schema.required = true /* force editor into required mode to prevent creation of empty radio button */
6
+ super.preBuild()
7
+ }
8
+
9
+ build () {
10
+ this.label = ''
11
+ if (!this.options.compact) this.header = this.label = this.theme.getFormInputLabel(this.getTitle(), this.isRequired())
12
+ if (this.schema.description) this.description = this.theme.getFormInputDescription(this.translateProperty(this.schema.description))
13
+ if (this.options.infoText) this.infoButton = this.theme.getInfoButton(this.translateProperty(this.options.infoText))
14
+ if (this.options.compact) this.container.classList.add('compact')
15
+
16
+ this.radioContainer = document.createElement('div')
17
+
18
+ this.radioGroup = []
19
+
20
+ const radioInputEventhandler = e => {
21
+ this.setValue(e.currentTarget.value)
22
+ this.onChange(true)
23
+ }
24
+
25
+ for (let i = 0; i < this.enum_values.length; i++) {
26
+ /* form radio elements */
27
+ this.input = this.theme.getFormRadio({
28
+ name: this.formname,
29
+ id: `${this.formname}[${i}]`,
30
+ value: this.enum_values[i]
31
+ })
32
+
33
+ /* Set custom attributes on input element. Parameter is array of protected keys. Empty array if none. */
34
+ this.setInputAttributes(['id', 'value', 'name'])
35
+
36
+ this.input.addEventListener('change', radioInputEventhandler, false)
37
+ this.radioGroup.push(this.input)
38
+
39
+ /* form-label for radio elements */
40
+ const radioLabel = this.theme.getFormRadioLabel(this.enum_display[i])
41
+ radioLabel.htmlFor = this.input.id
42
+
43
+ const control = this.theme.getFormRadioControl(radioLabel, this.input, !!(this.options.layout === 'horizontal' || this.options.compact))
44
+
45
+ this.radioContainer.appendChild(control)
46
+ }
47
+
48
+ if (this.schema.readOnly || this.schema.readonly) {
49
+ this.disable(true)
50
+ for (let j = 0; j < this.radioGroup.length; j++) {
51
+ this.radioGroup[j].disabled = true
52
+ }
53
+ this.radioContainer.classList.add('readonly')
54
+ }
55
+
56
+ const radioContainerWrapper = this.theme.getContainer()
57
+ radioContainerWrapper.appendChild(this.radioContainer)
58
+ radioContainerWrapper.dataset.containerFor = 'radio'
59
+
60
+ this.input = radioContainerWrapper
61
+
62
+ this.control = this.theme.getFormControl(this.label, radioContainerWrapper, this.description, this.infoButton)
63
+ this.container.appendChild(this.control)
64
+
65
+ /* Any special formatting that needs to happen after the input is added to the dom */
66
+ window.requestAnimationFrame(() => {
67
+ if (this.input.parentNode) this.afterInputReady()
68
+ })
69
+ }
70
+
71
+ enable () {
72
+ if (!this.always_disabled) {
73
+ for (let i = 0; i < this.radioGroup.length; i++) {
74
+ this.radioGroup[i].disabled = false
75
+ }
76
+ this.radioContainer.classList.remove('readonly')
77
+ super.enable()
78
+ }
79
+ }
80
+
81
+ disable (alwaysDisabled) {
82
+ if (alwaysDisabled) this.always_disabled = true
83
+ for (let i = 0; i < this.radioGroup.length; i++) {
84
+ this.radioGroup[i].disabled = true
85
+ }
86
+ this.radioContainer.classList.add('readonly')
87
+ super.disable()
88
+ }
89
+
90
+ destroy () {
91
+ if (this.radioContainer.parentNode && this.radioContainer.parentNode.parentNode) this.radioContainer.parentNode.parentNode.removeChild(this.radioContainer.parentNode)
92
+ if (this.label && this.label.parentNode) this.label.parentNode.removeChild(this.label)
93
+ if (this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description)
94
+ super.destroy()
95
+ }
96
+
97
+ getNumColumns () {
98
+ return 2
99
+ }
100
+
101
+ setValue (val) {
102
+ for (let i = 0; i < this.radioGroup.length; i++) {
103
+ if (this.radioGroup[i].value === val) {
104
+ this.radioGroup[i].checked = true
105
+ this.value = val
106
+ this.onChange()
107
+ break
108
+ }
109
+ }
110
+ }
111
+ }
@@ -0,0 +1,72 @@
1
+ import { StringEditor } from './string.js'
2
+ import { extend } from '../utilities.js'
3
+
4
+ export class ScEditor extends StringEditor {
5
+ setValue (value, initial, fromTemplate) {
6
+ const res = super.setValue(value, initial, fromTemplate)
7
+ if (res !== undefined && res.changed && this.sceditor_instance) this.sceditor_instance.val(res.value)
8
+ }
9
+
10
+ build () {
11
+ this.options.format = 'textarea' /* Force format into "textarea" */
12
+ super.build()
13
+ this.input_type = this.schema.format /* Restore original format */
14
+ this.input.setAttribute('data-schemaformat', this.input_type)
15
+ }
16
+
17
+ afterInputReady () {
18
+ if (window.sceditor) {
19
+ /* Get options, either global options from "this.defaults.options.sceditor" or */
20
+ /* single property options from schema "options.sceditor" */
21
+ const options = this.expandCallbacks('sceditor', extend({}, {
22
+ format: this.input_type,
23
+ emoticonsEnabled: false,
24
+ width: '100%',
25
+ height: 300,
26
+ readOnly: this.schema.readOnly || this.schema.readonly || this.schema.template
27
+ }, this.defaults.options.sceditor || {}, this.options.sceditor || {}, {
28
+ element: this.input
29
+ }))
30
+
31
+ const instance = window.sceditor.instance(this.input)
32
+
33
+ if (instance === undefined) {
34
+ window.sceditor.create(this.input, options) /* Create doesn't return instance. */
35
+ }
36
+
37
+ this.sceditor_instance = instance || window.sceditor.instance(this.input)
38
+
39
+ /* Listen for changes */
40
+ this.sceditor_instance.blur(() => {
41
+ this.value = this.sceditor_instance.val()
42
+ this.sceditor_instance.updateOriginal()
43
+ this.is_dirty = true
44
+ this.onChange(true)
45
+ })
46
+
47
+ this.theme.afterInputReady(this.input)
48
+ } else super.afterInputReady() /* Library not loaded, so just treat this as a string */
49
+ }
50
+
51
+ getNumColumns () {
52
+ return 6
53
+ }
54
+
55
+ enable () {
56
+ if (!this.always_disabled && this.sceditor_instance) this.sceditor_instance.readOnly(false)
57
+ super.enable()
58
+ }
59
+
60
+ disable (alwaysDisabled) {
61
+ if (this.sceditor_instance) this.sceditor_instance.readOnly(true)
62
+ super.disable(alwaysDisabled)
63
+ }
64
+
65
+ destroy () {
66
+ if (this.sceditor_instance) {
67
+ this.sceditor_instance.destroy()
68
+ this.sceditor_instance = null
69
+ }
70
+ super.destroy()
71
+ }
72
+ }
@@ -0,0 +1,370 @@
1
+ import { AbstractEditor } from '../editor.js'
2
+ import { extend } from '../utilities.js'
3
+
4
+ export class SelectEditor extends AbstractEditor {
5
+ setValue (value, initial) {
6
+ /* Sanitize value before setting it */
7
+ let sanitized = this.typecast(value)
8
+
9
+ const haveToUseDefaultValue = !!this.jsoneditor.options.use_default_values || typeof this.schema.default !== 'undefined'
10
+
11
+ if (
12
+ (this.enum_options.length > 0 && !this.enum_values.includes(sanitized)) ||
13
+ (initial && !this.isRequired() && !haveToUseDefaultValue)
14
+ ) {
15
+ sanitized = this.enum_values[0]
16
+ }
17
+
18
+ if (this.value === sanitized) return
19
+
20
+ if (initial) this.is_dirty = false
21
+ else if (this.jsoneditor.options.show_errors === 'change') this.is_dirty = true
22
+
23
+ this.input.value = this.enum_options[this.enum_values.indexOf(sanitized)]
24
+
25
+ this.value = sanitized
26
+ this.onChange()
27
+ this.change()
28
+ }
29
+
30
+ register () {
31
+ super.register()
32
+ if (!this.input) return
33
+ this.input.setAttribute('name', this.formname)
34
+ }
35
+
36
+ unregister () {
37
+ super.unregister()
38
+ if (!this.input) return
39
+ this.input.removeAttribute('name')
40
+ }
41
+
42
+ getNumColumns () {
43
+ if (!this.enum_options) return 3
44
+ let longestText = this.getTitle().length
45
+ for (let i = 0; i < this.enum_options.length; i++) {
46
+ longestText = Math.max(longestText, this.enum_options[i].length + 4)
47
+ }
48
+ return Math.min(12, Math.max(Math.ceil(longestText / 7), 2))
49
+ }
50
+
51
+ typecast (value) {
52
+ if (this.schema.enum && value === undefined) return undefined
53
+ else if (this.schema.type === 'boolean') return value === 'undefined' || value === undefined ? undefined : !!value
54
+ else if (this.schema.type === 'number') return 1 * value || 0
55
+ else if (this.schema.type === 'integer') return Math.floor(value * 1 || 0)
56
+ return `${value}`
57
+ }
58
+
59
+ getValue () {
60
+ if (!this.dependenciesFulfilled) {
61
+ return undefined
62
+ }
63
+ return this.typecast(this.value)
64
+ }
65
+
66
+ preBuild () {
67
+ this.input_type = 'select'
68
+ this.enum_options = []
69
+ this.enum_values = []
70
+ this.enum_display = []
71
+ let i
72
+ let callback
73
+
74
+ /* Enum options enumerated */
75
+ if (this.schema.enum) {
76
+ const display = (this.schema.options && this.schema.options.enum_titles) || []
77
+
78
+ this.schema.enum.forEach((option, i) => {
79
+ this.enum_options[i] = `${option}`
80
+ this.enum_display[i] = `${this.translateProperty(display[i]) || option}`
81
+ this.enum_values[i] = this.typecast(option)
82
+ })
83
+
84
+ if (!this.isRequired() && !(this.schema.options && this.schema.options.show_opt_in)) {
85
+ this.enum_display.unshift(' ')
86
+ this.enum_options.unshift('undefined')
87
+ this.enum_values.unshift(undefined)
88
+ }
89
+ /* Boolean */
90
+ } else if (this.schema.type === 'boolean') {
91
+ this.enum_display = (this.schema.options && this.schema.options.enum_titles) || ['true', 'false']
92
+ this.enum_options = ['1', '']
93
+ this.enum_values = [true, false]
94
+
95
+ if (!this.isRequired()) {
96
+ this.enum_display.unshift(' ')
97
+ this.enum_options.unshift('undefined')
98
+ this.enum_values.unshift(undefined)
99
+ }
100
+ /* Dynamic Enum */
101
+ } else if (this.schema.enumSource) {
102
+ this.enumSource = []
103
+ this.enum_display = []
104
+ this.enum_options = []
105
+ this.enum_values = []
106
+
107
+ /* Shortcut declaration for using a single array */
108
+ if (!(Array.isArray(this.schema.enumSource))) {
109
+ if (this.schema.enumValue) {
110
+ this.enumSource = [
111
+ {
112
+ source: this.schema.enumSource,
113
+ value: this.schema.enumValue
114
+ }
115
+ ]
116
+ } else {
117
+ this.enumSource = [
118
+ {
119
+ source: this.schema.enumSource
120
+ }
121
+ ]
122
+ }
123
+ } else {
124
+ for (i = 0; i < this.schema.enumSource.length; i++) {
125
+ /* Shorthand for watched variable */
126
+ if (typeof this.schema.enumSource[i] === 'string') {
127
+ this.enumSource[i] = {
128
+ source: this.schema.enumSource[i]
129
+ }
130
+ /* Make a copy of the schema */
131
+ } else if (!(Array.isArray(this.schema.enumSource[i]))) {
132
+ this.enumSource[i] = extend({}, this.schema.enumSource[i])
133
+ } else {
134
+ this.enumSource[i] = this.schema.enumSource[i]
135
+ }
136
+ }
137
+ }
138
+ /* Now, enumSource is an array of sources */
139
+ /* Walk through this array and fix up the values */
140
+ for (i = 0; i < this.enumSource.length; i++) {
141
+ if (this.enumSource[i].value) {
142
+ callback = this.expandCallbacks('template', { template: this.enumSource[i].value })
143
+ if (typeof callback.template === 'function') this.enumSource[i].value = callback.template
144
+ else this.enumSource[i].value = this.jsoneditor.compileTemplate(this.enumSource[i].value, this.template_engine)
145
+ }
146
+ if (this.enumSource[i].title) {
147
+ callback = this.expandCallbacks('template', { template: this.enumSource[i].title })
148
+ if (typeof callback.template === 'function') this.enumSource[i].title = callback.template
149
+ else this.enumSource[i].title = this.jsoneditor.compileTemplate(this.enumSource[i].title, this.template_engine)
150
+ }
151
+ if (this.enumSource[i].filter && this.enumSource[i].value) {
152
+ callback = this.expandCallbacks('template', { template: this.enumSource[i].filter })
153
+ if (typeof callback.template === 'function') this.enumSource[i].filter = callback.template
154
+ else this.enumSource[i].filter = this.jsoneditor.compileTemplate(this.enumSource[i].filter, this.template_engine)
155
+ }
156
+ }
157
+ /* Other, not supported */
158
+ } else {
159
+ throw new Error("'select' editor requires the enum property to be set.")
160
+ }
161
+ }
162
+
163
+ build () {
164
+ if (!this.options.compact) this.header = this.label = this.theme.getFormInputLabel(this.getTitle(), this.isRequired())
165
+ if (this.schema.description) this.description = this.theme.getFormInputDescription(this.translateProperty(this.schema.description))
166
+ if (this.options.infoText) this.infoButton = this.theme.getInfoButton(this.translateProperty(this.options.infoText))
167
+ if (this.options.compact) this.container.classList.add('compact')
168
+
169
+ this.input = this.theme.getSelectInput(this.enum_options, false)
170
+ this.theme.setSelectOptions(this.input, this.enum_options, this.enum_display)
171
+
172
+ if (this.schema.readOnly || this.schema.readonly) {
173
+ this.disable(true)
174
+ this.input.disabled = true
175
+ }
176
+
177
+ /* Set custom attributes on input element. Parameter is array of protected keys. Empty array if none. */
178
+ this.setInputAttributes([])
179
+
180
+ this.input.addEventListener('change', (e) => {
181
+ e.preventDefault()
182
+ e.stopPropagation()
183
+ this.onInputChange()
184
+ })
185
+
186
+ this.control = this.theme.getFormControl(this.label, this.input, this.description, this.infoButton)
187
+ this.container.appendChild(this.control)
188
+
189
+ this.value = this.enum_values[0]
190
+
191
+ /* Any special formatting that needs to happen after the input is added to the dom */
192
+ window.requestAnimationFrame(() => {
193
+ if (this.input.parentNode) this.afterInputReady()
194
+ })
195
+ }
196
+
197
+ afterInputReady () {
198
+ this.theme.afterInputReady(this.input)
199
+ }
200
+
201
+ onInputChange () {
202
+ const val = this.typecast(this.input.value)
203
+
204
+ let newVal
205
+ /* Invalid option, use first option instead */
206
+ if (!this.enum_values.includes(val)) {
207
+ newVal = this.enum_values[0]
208
+ } else {
209
+ newVal = this.enum_values[this.enum_values.indexOf(val)]
210
+ }
211
+
212
+ /* If valid hasn't changed */
213
+ if (newVal === this.value) return
214
+
215
+ this.is_dirty = true
216
+
217
+ /* Store new value and propogate change event */
218
+ this.value = newVal
219
+ this.onChange(true)
220
+ }
221
+
222
+ onWatchedFieldChange () {
223
+ let vars; let j
224
+ let selectOptions = []; let selectTitles = []
225
+
226
+ /* If this editor uses a dynamic select box */
227
+ if (this.enumSource) {
228
+ vars = this.getWatchedFieldValues()
229
+
230
+ for (let i = 0; i < this.enumSource.length; i++) {
231
+ /* Constant values */
232
+ if (Array.isArray(this.enumSource[i])) {
233
+ selectOptions = selectOptions.concat(this.enumSource[i])
234
+ selectTitles = selectTitles.concat(this.enumSource[i])
235
+ } else {
236
+ let items = []
237
+ /* Static list of items */
238
+ if (Array.isArray(this.enumSource[i].source)) {
239
+ items = this.enumSource[i].source
240
+ /* A watched field */
241
+ } else {
242
+ items = vars[this.enumSource[i].source]
243
+ }
244
+
245
+ if (items) {
246
+ /* Only use a predefined part of the array */
247
+ if (this.enumSource[i].slice) {
248
+ items = Array.prototype.slice.apply(items, this.enumSource[i].slice)
249
+ }
250
+ /* Filter the items */
251
+ if (this.enumSource[i].filter) {
252
+ const newItems = []
253
+ for (j = 0; j < items.length; j++) {
254
+ if (this.enumSource[i].filter({ i: j, item: items[j], watched: vars })) newItems.push(items[j])
255
+ }
256
+ items = newItems
257
+ }
258
+
259
+ const itemTitles = []
260
+ const itemValues = []
261
+ for (j = 0; j < items.length; j++) {
262
+ const item = items[j]
263
+
264
+ /* Rendered value */
265
+ if (this.enumSource[i].value) {
266
+ itemValues[j] = this.typecast(this.enumSource[i].value({
267
+ i: j,
268
+ item
269
+ }))
270
+ /* Use value directly */
271
+ } else {
272
+ itemValues[j] = items[j]
273
+ }
274
+
275
+ /* Rendered title */
276
+ if (this.enumSource[i].title) {
277
+ itemTitles[j] = this.enumSource[i].title({
278
+ i: j,
279
+ item
280
+ })
281
+ /* Use value as the title also */
282
+ } else {
283
+ itemTitles[j] = itemValues[j]
284
+ }
285
+ }
286
+
287
+ if (this.enumSource[i].sort) {
288
+ (((itemValues, itemTitles, order) => {
289
+ itemValues.map((v, i) => ({
290
+ v,
291
+ t: itemTitles[i]
292
+ })).sort((a, b) => (a.v < b.v) ? -order : ((a.v === b.v) ? 0 : order)).forEach((v, i) => {
293
+ itemValues[i] = v.v
294
+ itemTitles[i] = v.t
295
+ })
296
+ }).bind(null, itemValues, itemTitles, this.enumSource[i].sort === 'desc' ? 1 : -1))()
297
+ }
298
+
299
+ selectOptions = selectOptions.concat(itemValues)
300
+ selectTitles = selectTitles.concat(itemTitles)
301
+ }
302
+ }
303
+ }
304
+
305
+ selectTitles = selectTitles.map(title => this.translateProperty(title))
306
+
307
+ const prevValue = this.value
308
+
309
+ this.theme.setSelectOptions(this.input, selectOptions, selectTitles)
310
+ this.enum_options = selectOptions
311
+ this.enum_display = selectTitles
312
+ this.enum_values = selectOptions
313
+
314
+ /* If the previous value is still in the new select options */
315
+ /* or if global option "enum_source_value_auto_select" is true, stick with it */
316
+ if (selectOptions.includes(prevValue) || this.jsoneditor.options.enum_source_value_auto_select !== false) {
317
+ this.input.value = prevValue
318
+ this.value = prevValue
319
+ /* Otherwise, set the value to the first select option */
320
+ } else {
321
+ this.input.value = selectOptions[0]
322
+ this.value = this.typecast(selectOptions[0] || '')
323
+ if (this.parent && !this.watchLoop) this.parent.onChildEditorChange(this)
324
+ else this.jsoneditor.onChange()
325
+ this.jsoneditor.notifyWatchers(this.path)
326
+ }
327
+ }
328
+
329
+ super.onWatchedFieldChange()
330
+ }
331
+
332
+ enable () {
333
+ if (!this.always_disabled) {
334
+ this.input.disabled = false
335
+ super.enable()
336
+ }
337
+ }
338
+
339
+ disable (alwaysDisabled) {
340
+ if (alwaysDisabled) this.always_disabled = true
341
+ this.input.disabled = true
342
+ super.disable(alwaysDisabled)
343
+ }
344
+
345
+ destroy () {
346
+ if (this.label && this.label.parentNode) this.label.parentNode.removeChild(this.label)
347
+ if (this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description)
348
+ if (this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input)
349
+
350
+ super.destroy()
351
+ }
352
+
353
+ showValidationErrors (errors) {
354
+ this.previous_error_setting = this.jsoneditor.options.show_errors
355
+
356
+ const addMessage = (messages, error) => {
357
+ if (error.path === this.path) {
358
+ messages.push(error.message)
359
+ }
360
+ return messages
361
+ }
362
+ const messages = errors.reduce(addMessage, [])
363
+
364
+ if (messages.length) {
365
+ this.theme.addInputError(this.input, `${messages.join('. ')}.`)
366
+ } else {
367
+ this.theme.removeInputError(this.input)
368
+ }
369
+ }
370
+ }
@@ -0,0 +1,110 @@
1
+ import { SelectEditor } from './select.js'
2
+ import { extend, hasOwnProperty } from '../utilities.js'
3
+
4
+ export class Select2Editor extends SelectEditor {
5
+ setValue (value, initial) {
6
+ if (this.select2_instance) {
7
+ if (initial) this.is_dirty = false
8
+ else if (this.jsoneditor.options.show_errors === 'change') this.is_dirty = true
9
+
10
+ const sanitized = this.updateValue(value) /* Sets this.value to sanitized value */
11
+
12
+ this.input.value = sanitized
13
+
14
+ if (this.select2v4) this.select2_instance.val(sanitized).trigger('change')
15
+ else this.select2_instance.select2('val', sanitized)
16
+
17
+ this.onChange(true)
18
+ } else super.setValue(value, initial)
19
+ }
20
+
21
+ afterInputReady () {
22
+ if (window.jQuery && window.jQuery.fn && window.jQuery.fn.select2 && !this.select2_instance) {
23
+ /* Get options, either global options from "this.defaults.options.select2" or */
24
+ /* single property options from schema "options.select2" */
25
+ const options = this.expandCallbacks('select2', extend({}, this.defaults.options.select2 || {}, this.options.select2 || {}))
26
+
27
+ /* New items are allowed if option "tags" is true and type is "string" */
28
+ this.newEnumAllowed = options.tags = !!options.tags && this.schema.type === 'string'
29
+
30
+ this.select2_instance = window.jQuery(this.input).select2(options)
31
+ this.select2v4 = hasOwnProperty(this.select2_instance.select2, 'amd')
32
+
33
+ /* Create change handler */
34
+ this.selectChangeHandler = () => {
35
+ const value = this.select2v4 ? this.select2_instance.val() : this.select2_instance.select2('val')
36
+ this.updateValue(value)
37
+ this.onChange(true)
38
+ }
39
+
40
+ /* Add event handler. */
41
+ /* Note: Must use the "on()" method and not addEventListener() */
42
+ this.select2_instance.on('change', this.selectChangeHandler)
43
+ this.select2_instance.on('select2-blur', this.selectChangeHandler)
44
+ }
45
+ super.afterInputReady()
46
+ }
47
+
48
+ updateValue (value) {
49
+ let sanitized = this.enum_values[0]
50
+ value = this.typecast(value || '')
51
+ if (!this.enum_values.includes(value)) {
52
+ if (this.newEnumAllowed) {
53
+ sanitized = this.addNewOption(value) ? value : sanitized
54
+ }
55
+ } else sanitized = value
56
+ this.value = sanitized
57
+ return sanitized
58
+ }
59
+
60
+ addNewOption (value) {
61
+ const sanitized = this.typecast(value); let res = false; let optionTag
62
+
63
+ if (!this.enum_values.includes(sanitized) && sanitized !== '') {
64
+ /* Add to list of valid enum values */
65
+ this.enum_options.push(`${sanitized}`)
66
+ this.enum_display.push(`${sanitized}`)
67
+ this.enum_values.push(sanitized)
68
+ /* Update Schema enum to prevent triggering error */
69
+ /* "Value must be one of the enumerated values" */
70
+ this.schema.enum.push(sanitized)
71
+
72
+ optionTag = this.input.querySelector(`option[value="${sanitized}"]`)
73
+ if (optionTag) {
74
+ /* Remove data attribute to make option tag permanent. */
75
+ optionTag.removeAttribute('data-select2-tag')
76
+ } else {
77
+ this.input.appendChild(new Option(sanitized, sanitized, false, false)).trigger('change')
78
+ }
79
+
80
+ res = true
81
+ }
82
+ return res
83
+ }
84
+
85
+ enable () {
86
+ if (!this.always_disabled) {
87
+ if (this.select2_instance) {
88
+ if (this.select2v4) this.select2_instance.prop('disabled', false)
89
+ else this.select2_instance.select2('enable', true)
90
+ }
91
+ }
92
+ super.enable()
93
+ }
94
+
95
+ disable (alwaysDisabled) {
96
+ if (this.select2_instance) {
97
+ if (this.select2v4) this.select2_instance.prop('disabled', true)
98
+ else this.select2_instance.select2('enable', false)
99
+ }
100
+ super.disable(alwaysDisabled)
101
+ }
102
+
103
+ destroy () {
104
+ if (this.select2_instance) {
105
+ this.select2_instance.select2('destroy')
106
+ this.select2_instance = null
107
+ }
108
+ super.destroy()
109
+ }
110
+ }