@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,1290 @@
1
+ import { AbstractEditor } from '../editor.js'
2
+ import { extend, hasOwnProperty, trigger } from '../utilities.js'
3
+ import rules from './object.css.js'
4
+
5
+ export class ObjectEditor extends AbstractEditor {
6
+ constructor (options, defaults, depth) {
7
+ super(options, defaults)
8
+ this.currentDepth = depth
9
+ }
10
+
11
+ getDefault () {
12
+ return extend({}, this.schema.default || {})
13
+ }
14
+
15
+ getChildEditors () {
16
+ return this.editors
17
+ }
18
+
19
+ register () {
20
+ super.register()
21
+ if (this.editors) {
22
+ Object.values(this.editors).forEach(e => e.register())
23
+ }
24
+ }
25
+
26
+ unregister () {
27
+ super.unregister()
28
+ if (this.editors) {
29
+ Object.values(this.editors).forEach(e => e.unregister())
30
+ }
31
+ }
32
+
33
+ getNumColumns () {
34
+ return Math.max(Math.min(12, this.maxwidth), 3)
35
+ }
36
+
37
+ enable () {
38
+ if (!this.always_disabled) {
39
+ if (this.editjson_control) this.editjson_control.disabled = false
40
+ if (this.addproperty_button) this.addproperty_button.disabled = false
41
+
42
+ super.enable()
43
+ if (this.editors) {
44
+ Object.values(this.editors).forEach(e => {
45
+ if (e.isActive()) {
46
+ e.enable()
47
+ }
48
+ e.optInCheckbox.disabled = false
49
+ })
50
+ }
51
+ }
52
+ }
53
+
54
+ disable (alwaysDisabled) {
55
+ if (alwaysDisabled) this.always_disabled = true
56
+ if (this.editjson_control) this.editjson_control.disabled = true
57
+ if (this.addproperty_button) this.addproperty_button.disabled = true
58
+ this.hideEditJSON()
59
+
60
+ super.disable()
61
+ if (this.editors) {
62
+ Object.values(this.editors).forEach(e => {
63
+ if (e.isActive()) {
64
+ e.disable(alwaysDisabled)
65
+ }
66
+ e.optInCheckbox.disabled = true
67
+ })
68
+ }
69
+ }
70
+
71
+ layoutEditors () {
72
+ let i; let j
73
+
74
+ if (!this.row_container) return
75
+
76
+ /* Sort editors by propertyOrder */
77
+ this.property_order = Object.keys(this.editors)
78
+ this.property_order = this.property_order.sort((a, b) => {
79
+ let ordera = this.editors[a].schema.propertyOrder
80
+ let orderb = this.editors[b].schema.propertyOrder
81
+ if (typeof ordera !== 'number') ordera = 1000
82
+ if (typeof orderb !== 'number') orderb = 1000
83
+
84
+ return ordera - orderb
85
+ })
86
+
87
+ let container
88
+ const isCategoriesFormat = (this.format === 'categories')
89
+ const rows = []
90
+ let key = null
91
+ let editor = null
92
+ let row
93
+
94
+ if (this.format === 'grid-strict') {
95
+ let rowIndex = 0
96
+ row = []
97
+
98
+ this.property_order.forEach(key => {
99
+ const editor = this.editors[key]
100
+ if (editor.property_removed) {
101
+ return
102
+ }
103
+ const width = editor.options.hidden ? 0 : (editor.options.grid_columns || editor.getNumColumns())
104
+ const offset = editor.options.hidden ? 0 : (editor.options.grid_offset || 0)
105
+ const gridBreak = editor.options.hidden ? false : (editor.options.grid_break || false)
106
+ const height = editor.options.hidden ? 0 : editor.container.offsetHeight
107
+
108
+ const column = {
109
+ key,
110
+ width,
111
+ offset,
112
+ height
113
+ }
114
+
115
+ row.push(column)
116
+
117
+ rows[rowIndex] = row
118
+
119
+ if (gridBreak) {
120
+ rowIndex++
121
+ row = []
122
+ }
123
+ })
124
+
125
+ /* layout hasn't changed */
126
+ if (this.layout === JSON.stringify(rows)) return false
127
+ this.layout = JSON.stringify(rows)
128
+
129
+ /* Layout the form */
130
+ container = document.createElement('div')
131
+ for (i = 0; i < rows.length; i++) {
132
+ row = this.theme.getGridRow()
133
+ container.appendChild(row)
134
+ for (j = 0; j < rows[i].length; j++) {
135
+ key = rows[i][j].key
136
+ editor = this.editors[key]
137
+ if (editor.options.hidden) {
138
+ editor.container.style.display = 'none'
139
+ } else {
140
+ this.theme.setGridColumnSize(editor.container, rows[i][j].width, rows[i][j].offset)
141
+ }
142
+ row.appendChild(editor.container)
143
+ }
144
+ }
145
+ } else if (this.format === 'grid') {
146
+ this.property_order.forEach(key => {
147
+ const editor = this.editors[key]
148
+ if (editor.property_removed) return
149
+ let found = false
150
+ const width = editor.options.hidden ? 0 : (editor.options.grid_columns || editor.getNumColumns())
151
+ const height = editor.options.hidden ? 0 : editor.container.offsetHeight
152
+ /* See if the editor will fit in any of the existing rows first */
153
+ for (let i = 0; i < rows.length; i++) {
154
+ /* If the editor will fit in the row horizontally */
155
+ if (rows[i].width + width <= 12) {
156
+ /* If the editor is close to the other elements in height */
157
+ /* i.e. Don't put a really tall editor in an otherwise short row or vice versa */
158
+ if (!height || (rows[i].minh * 0.5 < height && rows[i].maxh * 2 > height)) {
159
+ found = i
160
+ }
161
+ }
162
+ }
163
+
164
+ /* If there isn't a spot in any of the existing rows, start a new row */
165
+ if (found === false) {
166
+ rows.push({
167
+ width: 0,
168
+ minh: 999999,
169
+ maxh: 0,
170
+ editors: []
171
+ })
172
+ found = rows.length - 1
173
+ }
174
+
175
+ rows[found].editors.push({
176
+ key,
177
+ /* editor: editor, */
178
+ width,
179
+ height
180
+ })
181
+ rows[found].width += width
182
+ rows[found].minh = Math.min(rows[found].minh, height)
183
+ rows[found].maxh = Math.max(rows[found].maxh, height)
184
+ })
185
+
186
+ /* Make almost full rows width 12 */
187
+ /* Do this by increasing all editors' sizes proprotionately */
188
+ /* Any left over space goes to the biggest editor */
189
+ /* Don't touch rows with a width of 6 or less */
190
+ for (i = 0; i < rows.length; i++) {
191
+ if (rows[i].width < 12) {
192
+ let biggest = false
193
+ let newWidth = 0
194
+ for (j = 0; j < rows[i].editors.length; j++) {
195
+ if (biggest === false) biggest = j
196
+ else if (rows[i].editors[j].width > rows[i].editors[biggest].width) biggest = j
197
+ rows[i].editors[j].width *= 12 / rows[i].width
198
+ rows[i].editors[j].width = Math.floor(rows[i].editors[j].width)
199
+ newWidth += rows[i].editors[j].width
200
+ }
201
+ if (newWidth < 12) rows[i].editors[biggest].width += 12 - newWidth
202
+ rows[i].width = 12
203
+ }
204
+ }
205
+
206
+ /* layout hasn't changed */
207
+ if (this.layout === JSON.stringify(rows)) return false
208
+ this.layout = JSON.stringify(rows)
209
+
210
+ /* Layout the form */
211
+ container = document.createElement('div')
212
+ for (i = 0; i < rows.length; i++) {
213
+ row = this.theme.getGridRow()
214
+ container.appendChild(row)
215
+ for (j = 0; j < rows[i].editors.length; j++) {
216
+ key = rows[i].editors[j].key
217
+ editor = this.editors[key]
218
+
219
+ if (editor.options.hidden) editor.container.style.display = 'none'
220
+ else this.theme.setGridColumnSize(editor.container, rows[i].editors[j].width)
221
+ row.appendChild(editor.container)
222
+ }
223
+ }
224
+ /* Normal layout */
225
+ } else {
226
+ container = document.createElement('div')
227
+
228
+ if (isCategoriesFormat) {
229
+ /* A container for properties not object nor arrays */
230
+ const containerSimple = document.createElement('div')
231
+ /* This will be the place to (re)build tabs and panes */
232
+ /* tabs_holder has 2 childs, [0]: ul.nav.nav-tabs and [1]: div.tab-content */
233
+ const newTabsHolder = this.theme.getTopTabHolder(this.translateProperty(this.schema.title))
234
+ /* child [1] of previous, stores panes */
235
+ const newTabPanesContainer = this.theme.getTopTabContentHolder(newTabsHolder)
236
+
237
+ this.property_order.forEach(key => {
238
+ const editor = this.editors[key]
239
+ if (editor.property_removed) return
240
+ const aPane = this.theme.getTabContent()
241
+ const isObjOrArray = editor.schema && (editor.schema.type === 'object' || editor.schema.type === 'array')
242
+ /* mark the pane */
243
+ aPane.isObjOrArray = isObjOrArray
244
+ const gridRow = this.theme.getGridRow()
245
+
246
+ /* this happens with added properties, they don't have a tab */
247
+ if (!editor.tab) {
248
+ /* Pass the pane which holds the editor */
249
+ if (typeof this.basicPane === 'undefined') {
250
+ /* There is no basicPane yet, so aPane will be it */
251
+ this.addRow(editor, newTabsHolder, aPane)
252
+ } else {
253
+ this.addRow(editor, newTabsHolder, this.basicPane)
254
+ }
255
+ }
256
+
257
+ aPane.id = this.getValidId(editor.tab_text.textContent)
258
+
259
+ /* For simple properties, add them on the same panel (Basic) */
260
+ if (!isObjOrArray) {
261
+ containerSimple.appendChild(gridRow)
262
+ /* There are already some panes */
263
+ if (newTabPanesContainer.childElementCount > 0) {
264
+ /* If first pane is object or array, insert before a simple pane */
265
+ if (newTabPanesContainer.firstChild.isObjOrArray) {
266
+ /* Append pane for simple properties */
267
+ aPane.appendChild(containerSimple)
268
+ newTabPanesContainer.insertBefore(aPane, newTabPanesContainer.firstChild)
269
+ /* Add "Basic" tab */
270
+ this.theme.insertBasicTopTab(editor.tab, newTabsHolder)
271
+ /* newTabs_holder.firstChild.insertBefore(editor.tab,newTabs_holder.firstChild.firstChild); */
272
+ /* Update the basicPane */
273
+ editor.basicPane = aPane
274
+ } else {
275
+ /* We already have a first "Basic" pane, just add the new property to it, so */
276
+ /* do nothing; */
277
+ }
278
+ /* There is no pane, so add the first (simple) pane */
279
+ } else {
280
+ /* Append pane for simple properties */
281
+ aPane.appendChild(containerSimple)
282
+ newTabPanesContainer.appendChild(aPane)
283
+ /* Add "Basic" tab */
284
+ /* newTabs_holder.firstChild.appendChild(editor.tab); */
285
+ this.theme.addTopTab(newTabsHolder, editor.tab)
286
+ /* Update the basicPane */
287
+ editor.basicPane = aPane
288
+ }
289
+ /* Objects and arrays earn their own panes */
290
+ } else {
291
+ aPane.appendChild(gridRow)
292
+ newTabPanesContainer.appendChild(aPane)
293
+ /* newTabs_holder.firstChild.appendChild(editor.tab); */
294
+ this.theme.addTopTab(newTabsHolder, editor.tab)
295
+ }
296
+
297
+ if (editor.options.hidden) editor.container.style.display = 'none'
298
+ else this.theme.setGridColumnSize(editor.container, 12)
299
+ /* Now, add the property editor to the row */
300
+ gridRow.appendChild(editor.container)
301
+ /* Update the rowPane (same as this.rows[x].rowPane) */
302
+ editor.rowPane = aPane
303
+ })
304
+
305
+ /* Erase old panes */
306
+ while (this.tabPanesContainer.firstChild) {
307
+ this.tabPanesContainer.removeChild(this.tabPanesContainer.firstChild)
308
+ }
309
+
310
+ /* Erase old tabs and set the new ones */
311
+ const parentTabsHolder = this.tabs_holder.parentNode
312
+ parentTabsHolder.removeChild(parentTabsHolder.firstChild)
313
+ parentTabsHolder.appendChild(newTabsHolder)
314
+
315
+ this.tabPanesContainer = newTabPanesContainer
316
+ this.tabs_holder = newTabsHolder
317
+
318
+ /* Activate the first tab */
319
+ const firstTab = this.theme.getFirstTab(this.tabs_holder)
320
+ if (firstTab) {
321
+ trigger(firstTab, 'click')
322
+ }
323
+ return
324
+ /* Normal layout */
325
+ }
326
+ this.property_order.forEach(key => {
327
+ const editor = this.editors[key]
328
+ if (editor.property_removed) return
329
+ row = this.theme.getGridRow()
330
+ container.appendChild(row)
331
+
332
+ if (editor.options.hidden) editor.container.style.display = 'none'
333
+ else this.theme.setGridColumnSize(editor.container, 12)
334
+ row.appendChild(editor.container)
335
+ })
336
+ }
337
+ /* for grid and normal layout */
338
+ while (this.row_container.firstChild) {
339
+ this.row_container.removeChild(this.row_container.firstChild)
340
+ }
341
+ this.row_container.appendChild(container)
342
+ }
343
+
344
+ getPropertySchema (key) {
345
+ /* Schema declared directly in properties */
346
+ let schema = this.schema.properties[key] || {}
347
+ schema = extend({}, schema)
348
+ let matched = !!this.schema.properties[key]
349
+
350
+ /* Any matching patternProperties should be merged in */
351
+ if (this.schema.patternProperties) {
352
+ Object.keys(this.schema.patternProperties).forEach(i => {
353
+ const regex = new RegExp(i)
354
+ if (regex.test(key)) {
355
+ schema.allOf = schema.allOf || []
356
+ schema.allOf.push(this.schema.patternProperties[i])
357
+ matched = true
358
+ }
359
+ })
360
+ }
361
+
362
+ /* Hasn't matched other rules, use additionalProperties schema */
363
+ if (!matched && this.schema.additionalProperties && typeof this.schema.additionalProperties === 'object') {
364
+ schema = extend({}, this.schema.additionalProperties)
365
+ }
366
+
367
+ return schema
368
+ }
369
+
370
+ preBuild () {
371
+ super.preBuild()
372
+
373
+ this.editors = {}
374
+ this.cached_editors = {}
375
+
376
+ this.format = this.options.layout || this.options.object_layout || this.schema.format || this.jsoneditor.options.object_layout || 'normal'
377
+
378
+ this.schema.properties = this.schema.properties || {}
379
+
380
+ this.minwidth = 0
381
+ this.maxwidth = 0
382
+
383
+ /* If the object should be rendered as a table row */
384
+ if (this.options.table_row) {
385
+ Object.entries(this.schema.properties).forEach(([key, schema]) => {
386
+ const editor = this.jsoneditor.getEditorClass(schema)
387
+ this.editors[key] = this.jsoneditor.createEditor(editor, {
388
+ jsoneditor: this.jsoneditor,
389
+ schema,
390
+ path: `${this.path}.${key}`,
391
+ parent: this,
392
+ compact: true,
393
+ required: true
394
+ }, this.currentDepth + 1)
395
+ this.editors[key].preBuild()
396
+
397
+ const width = this.editors[key].options.hidden ? 0 : (this.editors[key].options.grid_columns || this.editors[key].getNumColumns())
398
+
399
+ this.minwidth += width
400
+ this.maxwidth += width
401
+ })
402
+ this.no_link_holder = true
403
+ /* If the object should be rendered as a table */
404
+ } else if (this.options.table) {
405
+ /* TODO: table display format */
406
+ throw new Error('Not supported yet')
407
+ /* If the object should be rendered as a div */
408
+ } else {
409
+ if (!this.schema.defaultProperties) {
410
+ if (this.jsoneditor.options.display_required_only || this.options.display_required_only) {
411
+ this.schema.defaultProperties = Object.keys(this.schema.properties).filter(k => this.isRequiredObject({ key: k, schema: this.schema.properties[k] }))
412
+ } else {
413
+ this.schema.defaultProperties = Object.keys(this.schema.properties)
414
+ }
415
+ }
416
+
417
+ /* Increase the grid width to account for padding */
418
+ this.maxwidth += 1
419
+
420
+ /* Check for array (eg. meta-schema options is an object) */
421
+ if (Array.isArray(this.schema.defaultProperties)) {
422
+ this.schema.defaultProperties.forEach(key => {
423
+ this.addObjectProperty(key, true)
424
+
425
+ if (this.editors[key]) {
426
+ this.minwidth = Math.max(this.minwidth, (this.editors[key].options.grid_columns || this.editors[key].getNumColumns()))
427
+ this.maxwidth += (this.editors[key].options.grid_columns || this.editors[key].getNumColumns())
428
+ }
429
+ })
430
+ }
431
+ }
432
+
433
+ /* Sort editors by propertyOrder */
434
+ this.property_order = Object.keys(this.editors)
435
+ this.property_order = this.property_order.sort((a, b) => {
436
+ let ordera = this.editors[a].schema.propertyOrder
437
+ let orderb = this.editors[b].schema.propertyOrder
438
+ if (typeof ordera !== 'number') ordera = 1000
439
+ if (typeof orderb !== 'number') orderb = 1000
440
+
441
+ return ordera - orderb
442
+ })
443
+ }
444
+
445
+ /* "Borrow" from arrays code */
446
+ addTab (idx) {
447
+ const isObjOrArray = this.rows[idx].schema && (this.rows[idx].schema.type === 'object' || this.rows[idx].schema.type === 'array')
448
+ if (this.tabs_holder) {
449
+ this.rows[idx].tab_text = document.createElement('span')
450
+
451
+ if (!isObjOrArray) {
452
+ this.rows[idx].tab_text.textContent = (typeof this.schema.basicCategoryTitle === 'undefined') ? 'Basic' : this.schema.basicCategoryTitle
453
+ } else {
454
+ this.rows[idx].tab_text.textContent = this.rows[idx].getHeaderText()
455
+ }
456
+ this.rows[idx].tab = this.theme.getTopTab(this.rows[idx].tab_text, this.getValidId(this.rows[idx].tab_text.textContent))
457
+ this.rows[idx].tab.addEventListener('click', (e) => {
458
+ this.active_tab = this.rows[idx].tab
459
+ this.refreshTabs()
460
+ e.preventDefault()
461
+ e.stopPropagation()
462
+ })
463
+ }
464
+ }
465
+
466
+ addRow (editor, tabHolder, aPane) {
467
+ const rowsLen = this.rows.length
468
+ const isObjOrArray = editor.schema.type === 'object' || editor.schema.type === 'array'
469
+
470
+ /* Add a row */
471
+ this.rows[rowsLen] = editor
472
+ /* rowPane stores the editor corresponding pane to set the display style when refreshing Tabs */
473
+ this.rows[rowsLen].rowPane = aPane
474
+
475
+ if (!isObjOrArray) {
476
+ /* This is the first simple property to be added, */
477
+ /* add a ("Basic") tab for it and save it's row number */
478
+ if (typeof this.basicTab === 'undefined') {
479
+ this.addTab(rowsLen)
480
+ /* Store the index row of the first simple property added */
481
+ this.basicTab = rowsLen
482
+ this.basicPane = aPane
483
+ this.theme.addTopTab(tabHolder, this.rows[rowsLen].tab)
484
+ } else {
485
+ /* Any other simple property gets the same tab (and the same pane) as the first one, */
486
+ /* so, when 'click' event is fired from a row, it gets the correct ("Basic") tab */
487
+ this.rows[rowsLen].tab = this.rows[this.basicTab].tab
488
+ this.rows[rowsLen].tab_text = this.rows[this.basicTab].tab_text
489
+ this.rows[rowsLen].rowPane = this.rows[this.basicTab].rowPane
490
+ }
491
+ } else {
492
+ this.addTab(rowsLen)
493
+ this.theme.addTopTab(tabHolder, this.rows[rowsLen].tab)
494
+ }
495
+ }
496
+
497
+ /* Mark the active tab and make visible the corresponding pane, hide others */
498
+ refreshTabs (refreshHeaders) {
499
+ const basicTabPresent = typeof this.basicTab !== 'undefined'
500
+ let basicTabRefreshed = false
501
+
502
+ this.rows.forEach(row => {
503
+ /* If it's an orphan row (some property which has been deleted), return */
504
+ if (!row.tab || !row.rowPane || !row.rowPane.parentNode) return
505
+
506
+ if (basicTabPresent && row.tab === this.rows[this.basicTab].tab && basicTabRefreshed) return
507
+
508
+ if (refreshHeaders) {
509
+ row.tab_text.textContent = row.getHeaderText()
510
+ } else {
511
+ /* All rows of simple properties point to the same tab, so refresh just once */
512
+ if (basicTabPresent && row.tab === this.rows[this.basicTab].tab) basicTabRefreshed = true
513
+
514
+ if (row.tab === this.active_tab) {
515
+ this.theme.markTabActive(row)
516
+ } else {
517
+ this.theme.markTabInactive(row)
518
+ }
519
+ }
520
+ })
521
+ }
522
+
523
+ build () {
524
+ const isCategoriesFormat = (this.format === 'categories')
525
+ this.rows = []
526
+ this.active_tab = null
527
+
528
+ /* If the object should be rendered as a table row */
529
+ if (this.options.table_row) {
530
+ this.editor_holder = this.container
531
+ Object.entries(this.editors).forEach(([key, editor]) => {
532
+ const holder = this.theme.getTableCell()
533
+ this.editor_holder.appendChild(holder)
534
+
535
+ editor.setContainer(holder)
536
+ editor.build()
537
+ editor.postBuild()
538
+ editor.setOptInCheckbox(editor.header)
539
+
540
+ if (this.editors[key].options.hidden) {
541
+ holder.style.display = 'none'
542
+ }
543
+ if (this.editors[key].options.input_width) {
544
+ holder.style.width = this.editors[key].options.input_width
545
+ }
546
+ })
547
+ /* If the object should be rendered as a table */
548
+ } else if (this.options.table) {
549
+ /* TODO: table display format */
550
+ throw new Error('Not supported yet')
551
+ /* If the object should be rendered as a div */
552
+ } else {
553
+ this.header = ''
554
+ if (!this.options.compact) {
555
+ this.header = document.createElement('label')
556
+ this.header.textContent = this.getTitle()
557
+ }
558
+ this.title = this.theme.getHeader(this.header, this.getPathDepth())
559
+ this.title.classList.add('je-object__title')
560
+ if (!this.options.wb || !this.options.wb.disable_title) {
561
+ this.container.appendChild(this.title)
562
+ }
563
+
564
+ this.controls = this.theme.getButtonHolder()
565
+ this.controls.classList.add('je-object__controls')
566
+ if (!this.options.wb || !this.options.wb.disable_title) {
567
+ this.container.appendChild(this.controls)
568
+ }
569
+
570
+ this.container.classList.add('je-object__container')
571
+
572
+ /* Edit JSON modal */
573
+ this.editjson_holder = this.theme.getModal()
574
+ this.editjson_textarea = this.theme.getTextareaInput()
575
+ this.editjson_textarea.classList.add('je-edit-json--textarea')
576
+ this.editjson_save = this.getButton('button_save', 'save', 'button_save')
577
+ this.editjson_save.classList.add('json-editor-btntype-save')
578
+ this.editjson_save.addEventListener('click', (e) => {
579
+ e.preventDefault()
580
+ e.stopPropagation()
581
+ this.saveJSON()
582
+ })
583
+ this.editjson_copy = this.getButton('button_copy', 'copy', 'button_copy')
584
+ this.editjson_copy.classList.add('json-editor-btntype-copy')
585
+ this.editjson_copy.addEventListener('click', (e) => {
586
+ e.preventDefault()
587
+ e.stopPropagation()
588
+ this.copyJSON()
589
+ })
590
+ this.editjson_cancel = this.getButton('button_cancel', 'cancel', 'button_cancel')
591
+ this.editjson_cancel.classList.add('json-editor-btntype-cancel')
592
+ this.editjson_cancel.addEventListener('click', (e) => {
593
+ e.preventDefault()
594
+ e.stopPropagation()
595
+ this.hideEditJSON()
596
+ })
597
+ this.editjson_holder.appendChild(this.editjson_textarea)
598
+ this.editjson_holder.appendChild(this.editjson_save)
599
+ this.editjson_holder.appendChild(this.editjson_copy)
600
+ this.editjson_holder.appendChild(this.editjson_cancel)
601
+
602
+ /* Manage Properties modal */
603
+ this.addproperty_holder = this.theme.getModal()
604
+ this.addproperty_list = document.createElement('div')
605
+ this.addproperty_list.classList.add('property-selector')
606
+ this.addproperty_add = this.getButton('button_add', 'add', 'button_add')
607
+ this.addproperty_add.classList.add('json-editor-btntype-add')
608
+
609
+ this.addproperty_input = this.theme.getFormInputField('text')
610
+ this.addproperty_input.setAttribute('placeholder', this.translate('property_name_placeholder'))
611
+ this.addproperty_input.classList.add('property-selector-input')
612
+ this.addproperty_add.addEventListener('click', (e) => {
613
+ e.preventDefault()
614
+ e.stopPropagation()
615
+ if (this.addproperty_input.value) {
616
+ if (this.editors[this.addproperty_input.value]) {
617
+ window.alert(this.translate('duplicate_property_error'))
618
+ return
619
+ }
620
+
621
+ this.addObjectProperty(this.addproperty_input.value)
622
+ if (this.editors[this.addproperty_input.value]) {
623
+ this.editors[this.addproperty_input.value].disable()
624
+ }
625
+ this.onChange(true)
626
+ }
627
+ })
628
+ this.addproperty_input.addEventListener('input', (e) => {
629
+ e.target.previousSibling.childNodes.forEach((value) => {
630
+ if (value.innerText.includes(e.target.value)) {
631
+ value.style.display = ''
632
+ } else {
633
+ value.style.display = 'none'
634
+ }
635
+ })
636
+ })
637
+ this.addproperty_holder.appendChild(this.addproperty_list)
638
+ this.addproperty_holder.appendChild(this.addproperty_input)
639
+ this.addproperty_holder.appendChild(this.addproperty_add)
640
+ const spacer = document.createElement('div')
641
+ spacer.style.clear = 'both'
642
+ this.addproperty_holder.appendChild(spacer)
643
+
644
+ /* Close properties modal if clicked outside modal */
645
+ document.addEventListener('click', this.onOutsideModalClick.bind(this))
646
+
647
+ /* Description */
648
+ if (this.schema.description) {
649
+ this.description = this.theme.getDescription(this.translateProperty(this.schema.description))
650
+ this.container.appendChild(this.description)
651
+ }
652
+
653
+ /* Validation error placeholder area */
654
+ this.error_holder = document.createElement('div')
655
+ this.container.appendChild(this.error_holder)
656
+
657
+ /* Container for child editor area */
658
+ if (!this.options.wb || (!this.options.wb.disable_title && !this.options.wb.disable_panel)) {
659
+ this.editor_holder = this.theme.getIndentedPanel()
660
+ } else {
661
+ this.editor_holder = document.createElement('div')
662
+ }
663
+ this.container.appendChild(this.editor_holder)
664
+
665
+ /* Container for rows of child editors */
666
+ this.row_container = this.theme.getGridContainer()
667
+
668
+ if (isCategoriesFormat) {
669
+ this.tabs_holder = this.theme.getTopTabHolder(this.getValidId(this.translateProperty(this.schema.title)))
670
+ this.tabPanesContainer = this.theme.getTopTabContentHolder(this.tabs_holder)
671
+ this.editor_holder.appendChild(this.tabs_holder)
672
+ } else {
673
+ this.tabs_holder = this.theme.getTabHolder(this.getValidId(this.translateProperty(this.schema.title)))
674
+ this.tabPanesContainer = this.theme.getTabContentHolder(this.tabs_holder)
675
+ this.editor_holder.appendChild(this.row_container)
676
+ }
677
+
678
+ Object.values(this.editors).forEach(editor => {
679
+ const aPane = this.theme.getTabContent()
680
+ const holder = this.theme.getGridColumn()
681
+ const isObjOrArray = !!((editor.schema && (editor.schema.type === 'object' || editor.schema.type === 'array')))
682
+ aPane.isObjOrArray = isObjOrArray
683
+
684
+ if (isCategoriesFormat) {
685
+ if (isObjOrArray) {
686
+ const singleRowContainer = this.theme.getGridContainer()
687
+ singleRowContainer.appendChild(holder)
688
+ aPane.appendChild(singleRowContainer)
689
+ this.tabPanesContainer.appendChild(aPane)
690
+ this.row_container = singleRowContainer
691
+ } else {
692
+ if (typeof this.row_container_basic === 'undefined') {
693
+ this.row_container_basic = this.theme.getGridContainer()
694
+ aPane.appendChild(this.row_container_basic)
695
+ if (this.tabPanesContainer.childElementCount === 0) {
696
+ this.tabPanesContainer.appendChild(aPane)
697
+ } else {
698
+ this.tabPanesContainer.insertBefore(aPane, this.tabPanesContainer.childNodes[1])
699
+ }
700
+ }
701
+ this.row_container_basic.appendChild(holder)
702
+ }
703
+
704
+ this.addRow(editor, this.tabs_holder, aPane)
705
+
706
+ aPane.id = this.getValidId(editor.schema.title) /* editor.schema.path//tab_text.textContent */
707
+ } else {
708
+ this.row_container.appendChild(holder)
709
+ }
710
+
711
+ editor.setContainer(holder)
712
+ editor.build()
713
+ editor.postBuild()
714
+ editor.setOptInCheckbox(editor.header)
715
+ })
716
+
717
+ if (this.rows[0]) {
718
+ trigger(this.rows[0].tab, 'click')
719
+ }
720
+
721
+ /* Show/Hide button */
722
+ this.collapsed = false
723
+ this.collapse_control = this.getButton('', 'collapse', 'button_collapse')
724
+ this.collapse_control.classList.add('json-editor-btntype-toggle')
725
+ this.title.insertBefore(this.collapse_control, this.title.childNodes[0])
726
+
727
+ this.collapse_control.addEventListener('click', (e) => {
728
+ e.preventDefault()
729
+ e.stopPropagation()
730
+ if (this.collapsed) {
731
+ this.editor_holder.style.display = ''
732
+ this.collapsed = false
733
+ this.setButtonText(this.collapse_control, '', 'collapse', 'button_collapse')
734
+ } else {
735
+ this.editor_holder.style.display = 'none'
736
+ this.collapsed = true
737
+ this.setButtonText(this.collapse_control, '', 'expand', 'button_expand')
738
+ }
739
+ })
740
+
741
+ /* If it should start collapsed */
742
+ if (this.options.collapsed) {
743
+ trigger(this.collapse_control, 'click')
744
+ }
745
+
746
+ /* Collapse button disabled */
747
+ if (this.schema.options && typeof this.schema.options.disable_collapse !== 'undefined') {
748
+ if (this.schema.options.disable_collapse) {
749
+ this.collapse_control.style.display = 'none'
750
+ this.title.classList.add('disable_collapse')
751
+ }
752
+ } else if (this.jsoneditor.options.disable_collapse) {
753
+ this.collapse_control.style.display = 'none'
754
+ }
755
+
756
+ /* Edit JSON Button */
757
+ this.editjson_control = this.getButton('JSON', 'edit', 'button_edit_json')
758
+ this.editjson_control.classList.add('json-editor-btntype-editjson')
759
+ this.editjson_control.addEventListener('click', (e) => {
760
+ e.preventDefault()
761
+ e.stopPropagation()
762
+ this.toggleEditJSON()
763
+ })
764
+ this.controls.appendChild(this.editjson_control)
765
+ this.controls.insertBefore(this.editjson_holder, this.controls.childNodes[0])
766
+
767
+ /* Edit JSON Buttton disabled */
768
+ if (this.schema.options && typeof this.schema.options.disable_edit_json !== 'undefined') {
769
+ if (this.schema.options.disable_edit_json) this.editjson_control.style.display = 'none'
770
+ } else if (this.jsoneditor.options.disable_edit_json) {
771
+ this.editjson_control.style.display = 'none'
772
+ }
773
+
774
+ /* Object Properties Button */
775
+ this.addproperty_button = this.getButton('properties', 'edit_properties', 'button_object_properties')
776
+ this.addproperty_button.classList.add('json-editor-btntype-properties')
777
+ this.addproperty_button.addEventListener('click', (e) => {
778
+ e.preventDefault()
779
+ e.stopPropagation()
780
+ this.toggleAddProperty()
781
+ })
782
+ this.controls.appendChild(this.addproperty_button)
783
+ this.controls.insertBefore(this.addproperty_holder, this.controls.childNodes[1])
784
+
785
+ this.refreshAddProperties()
786
+
787
+ /* non required properties start deactivated */
788
+ this.deactivateNonRequiredProperties()
789
+ }
790
+
791
+ /* Fix table cell ordering */
792
+ if (this.options.table_row) {
793
+ this.editor_holder = this.container
794
+ this.property_order.forEach(key => {
795
+ this.editor_holder.appendChild(this.editors[key].container)
796
+ })
797
+ /* Layout object editors in grid if needed */
798
+ } else {
799
+ /* Initial layout */
800
+ this.layoutEditors()
801
+ /* Do it again now that we know the approximate heights of elements */
802
+ this.layoutEditors()
803
+ }
804
+ }
805
+
806
+ deactivateNonRequiredProperties () {
807
+ /* the show_opt_in editor option is for backward compatibility */
808
+ if (this.jsoneditor.options.show_opt_in || this.options.show_opt_in) {
809
+ Object.entries(this.editors).forEach(([key, editor]) => {
810
+ if (!this.isRequiredObject(editor)) {
811
+ this.editors[key].deactivate()
812
+ }
813
+ })
814
+ }
815
+ }
816
+
817
+ showEditJSON () {
818
+ if (!this.editjson_holder) return
819
+ this.hideAddProperty()
820
+
821
+ /* Position the form directly beneath the button */
822
+ /* TODO: edge detection */
823
+ this.editjson_holder.style.left = `${this.editjson_control.offsetLeft}px`
824
+ this.editjson_holder.style.top = `${this.editjson_control.offsetTop + this.editjson_control.offsetHeight}px`
825
+
826
+ /* Start the textarea with the current value */
827
+ this.editjson_textarea.value = JSON.stringify(this.getValue(), null, 2)
828
+
829
+ /* Disable the rest of the form while editing JSON */
830
+ this.disable()
831
+
832
+ this.editjson_holder.style.display = ''
833
+ this.editjson_control.disabled = false
834
+ this.editing_json = true
835
+ }
836
+
837
+ hideEditJSON () {
838
+ if (!this.editjson_holder) return
839
+ if (!this.editing_json) return
840
+
841
+ this.editjson_holder.style.display = 'none'
842
+ this.enable()
843
+ this.editing_json = false
844
+ }
845
+
846
+ copyJSON () {
847
+ if (!this.editjson_holder) return
848
+ const ta = document.createElement('textarea')
849
+ ta.value = this.editjson_textarea.value
850
+ ta.setAttribute('readonly', '')
851
+ ta.style.position = 'absolute'
852
+ ta.style.left = '-9999px'
853
+ document.body.appendChild(ta)
854
+ ta.select()
855
+ document.execCommand('copy')
856
+ document.body.removeChild(ta)
857
+ }
858
+
859
+ saveJSON () {
860
+ if (!this.editjson_holder) return
861
+
862
+ try {
863
+ const json = JSON.parse(this.editjson_textarea.value)
864
+ this.setValue(json)
865
+ this.hideEditJSON()
866
+ this.onChange(true)
867
+ } catch (e) {
868
+ window.alert('invalid JSON')
869
+ throw e
870
+ }
871
+ }
872
+
873
+ toggleEditJSON () {
874
+ if (this.editing_json) this.hideEditJSON()
875
+ else this.showEditJSON()
876
+ }
877
+
878
+ insertPropertyControlUsingPropertyOrder (property, control, container) {
879
+ let propertyOrder
880
+ if (this.schema.properties[property]) { propertyOrder = this.schema.properties[property].propertyOrder }
881
+ if (typeof propertyOrder !== 'number') propertyOrder = 1000
882
+ control.propertyOrder = propertyOrder
883
+
884
+ for (let i = 0; i < container.childNodes.length; i++) {
885
+ const child = container.childNodes[i]
886
+ if (control.propertyOrder < child.propertyOrder) {
887
+ this.addproperty_list.insertBefore(control, child)
888
+ control = null
889
+ break
890
+ }
891
+ }
892
+ if (control) {
893
+ this.addproperty_list.appendChild(control)
894
+ }
895
+ }
896
+
897
+ addPropertyCheckbox (key) {
898
+ let labelText
899
+
900
+ const checkbox = this.theme.getCheckbox()
901
+ checkbox.style.width = 'auto'
902
+
903
+ if (this.schema.properties[key] && this.schema.properties[key].title) {
904
+ labelText = this.translateProperty(this.schema.properties[key].title)
905
+ } else {
906
+ labelText = key
907
+ }
908
+
909
+ const label = this.theme.getCheckboxLabel(labelText)
910
+
911
+ const control = this.theme.getFormControl(label, checkbox)
912
+ control.style.paddingBottom = control.style.marginBottom = control.style.paddingTop = control.style.marginTop = 0
913
+ control.style.height = 'auto'
914
+ /* control.style.overflowY = 'hidden'; */
915
+
916
+ this.insertPropertyControlUsingPropertyOrder(key, control, this.addproperty_list)
917
+
918
+ checkbox.checked = key in this.editors
919
+ checkbox.addEventListener('change', () => {
920
+ if (checkbox.checked) {
921
+ this.addObjectProperty(key)
922
+ } else {
923
+ this.removeObjectProperty(key)
924
+ }
925
+ this.onChange(true)
926
+ })
927
+ this.addproperty_checkboxes[key] = checkbox
928
+
929
+ return checkbox
930
+ }
931
+
932
+ showAddProperty () {
933
+ if (!this.addproperty_holder) return
934
+ this.hideEditJSON()
935
+
936
+ /* Position the form directly beneath the button */
937
+ /* TODO: edge detection */
938
+ this.addproperty_holder.style.left = `${this.addproperty_button.offsetLeft}px`
939
+ this.addproperty_holder.style.top = `${this.addproperty_button.offsetTop + this.addproperty_button.offsetHeight}px`
940
+
941
+ /* Disable the rest of the form while editing JSON */
942
+ this.disable()
943
+
944
+ this.adding_property = true
945
+ this.addproperty_button.disabled = false
946
+ this.addproperty_holder.style.display = ''
947
+ this.refreshAddProperties()
948
+ }
949
+
950
+ hideAddProperty () {
951
+ if (!this.addproperty_holder) return
952
+ if (!this.adding_property) return
953
+
954
+ this.addproperty_holder.style.display = 'none'
955
+ this.enable()
956
+
957
+ this.adding_property = false
958
+ }
959
+
960
+ toggleAddProperty () {
961
+ if (this.adding_property) this.hideAddProperty()
962
+ else this.showAddProperty()
963
+ }
964
+
965
+ removeObjectProperty (property) {
966
+ if (this.editors[property]) {
967
+ this.editors[property].unregister()
968
+ delete this.editors[property]
969
+
970
+ this.refreshValue()
971
+ this.layoutEditors()
972
+ }
973
+ }
974
+
975
+ getSchemaOnMaxDepth (schema) {
976
+ return Object.keys(schema).reduce((acc, key) => {
977
+ switch (key) {
978
+ case '$ref':
979
+ return acc
980
+ case 'properties':
981
+ case 'items':
982
+ return {
983
+ ...acc,
984
+ [key]: {}
985
+ }
986
+ case 'additionalProperties':
987
+ case 'propertyNames':
988
+ return {
989
+ ...acc,
990
+ [key]: true
991
+ }
992
+ default:
993
+ return {
994
+ ...acc,
995
+ [key]: schema[key]
996
+ }
997
+ }
998
+ }, {})
999
+ }
1000
+
1001
+ addObjectProperty (name, prebuildOnly) {
1002
+ /* Property is already added */
1003
+ if (this.editors[name]) return
1004
+
1005
+ /* Property was added before and is cached */
1006
+ if (this.cached_editors[name]) {
1007
+ this.editors[name] = this.cached_editors[name]
1008
+ if (prebuildOnly) return
1009
+ this.editors[name].register()
1010
+ /* New property */
1011
+ } else {
1012
+ if (!this.canHaveAdditionalProperties() && (!this.schema.properties || !this.schema.properties[name])) {
1013
+ return
1014
+ }
1015
+
1016
+ const schema = this.getPropertySchema(name)
1017
+ if (typeof schema.propertyOrder !== 'number') {
1018
+ /* if the propertyOrder undefined, then set a smart default value. */
1019
+ schema.propertyOrder = Object.keys(this.editors).length + 1000
1020
+ }
1021
+
1022
+ /* Add the property */
1023
+ const editor = this.jsoneditor.getEditorClass(schema)
1024
+
1025
+ const { max_depth: maxDepth } = this.jsoneditor.options
1026
+
1027
+ this.editors[name] = this.jsoneditor.createEditor(editor, {
1028
+ jsoneditor: this.jsoneditor,
1029
+ schema: !!maxDepth && this.currentDepth >= maxDepth ? this.getSchemaOnMaxDepth(schema) : schema,
1030
+ path: `${this.path}.${name}`,
1031
+ parent: this
1032
+ }, this.currentDepth + 1)
1033
+ this.editors[name].preBuild()
1034
+
1035
+ if (!prebuildOnly) {
1036
+ const holder = this.theme.getChildEditorHolder()
1037
+ this.editor_holder.appendChild(holder)
1038
+ this.editors[name].setContainer(holder)
1039
+ this.editors[name].build()
1040
+ this.editors[name].postBuild()
1041
+ this.editors[name].setOptInCheckbox(editor.header)
1042
+ this.editors[name].activate()
1043
+ }
1044
+
1045
+ this.cached_editors[name] = this.editors[name]
1046
+ }
1047
+
1048
+ /* If we're only prebuilding the editors, don't refresh values */
1049
+ if (!prebuildOnly) {
1050
+ this.refreshValue()
1051
+ this.layoutEditors()
1052
+ }
1053
+ }
1054
+
1055
+ onOutsideModalClick (e) {
1056
+ const path = e.path || (e.composedPath && e.composedPath())
1057
+ if (this.addproperty_holder && !this.addproperty_holder.contains(path[0]) && this.adding_property) {
1058
+ e.preventDefault()
1059
+ e.stopPropagation()
1060
+ this.toggleAddProperty()
1061
+ }
1062
+ }
1063
+
1064
+ onChildEditorChange (editor) {
1065
+ this.refreshValue()
1066
+ super.onChildEditorChange(editor)
1067
+ }
1068
+
1069
+ canHaveAdditionalProperties () {
1070
+ if (typeof this.schema.additionalProperties === 'boolean') {
1071
+ return this.schema.additionalProperties
1072
+ }
1073
+ return !this.jsoneditor.options.no_additional_properties
1074
+ }
1075
+
1076
+ destroy () {
1077
+ Object.values(this.cached_editors).forEach(el => el.destroy())
1078
+ if (this.editor_holder) this.editor_holder.innerHTML = ''
1079
+ if (this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title)
1080
+ if (this.error_holder && this.error_holder.parentNode) this.error_holder.parentNode.removeChild(this.error_holder)
1081
+
1082
+ this.editors = null
1083
+ this.cached_editors = null
1084
+ if (this.editor_holder && this.editor_holder.parentNode) this.editor_holder.parentNode.removeChild(this.editor_holder)
1085
+ this.editor_holder = null
1086
+ document.removeEventListener('click', this.onOutsideModalClick)
1087
+
1088
+ super.destroy()
1089
+ }
1090
+
1091
+ getValue () {
1092
+ if (!this.dependenciesFulfilled) {
1093
+ return undefined
1094
+ }
1095
+ const result = super.getValue()
1096
+ const isEmpty = obj => typeof obj === 'undefined' || obj === '' ||
1097
+ (
1098
+ obj === Object(obj) &&
1099
+ Object.keys(obj).length === 0 &&
1100
+ obj.constructor === Object
1101
+ )
1102
+ if (result && (this.jsoneditor.options.remove_empty_properties || this.options.remove_empty_properties)) {
1103
+ Object.keys(result).forEach(key => {
1104
+ if (isEmpty(result[key])) {
1105
+ delete result[key]
1106
+ }
1107
+ })
1108
+ }
1109
+ return result
1110
+ }
1111
+
1112
+ refreshValue () {
1113
+ this.value = {}
1114
+
1115
+ if (!this.editors) {
1116
+ return
1117
+ }
1118
+
1119
+ Object.keys(this.editors).forEach(i => {
1120
+ if (this.editors[i].isActive()) {
1121
+ this.value[i] = this.editors[i].getValue()
1122
+ }
1123
+ })
1124
+
1125
+ if (this.adding_property) this.refreshAddProperties()
1126
+ }
1127
+
1128
+ refreshAddProperties () {
1129
+ if (this.options.disable_properties || (this.options.disable_properties !== false && this.jsoneditor.options.disable_properties)) {
1130
+ this.addproperty_button.style.display = 'none'
1131
+ return
1132
+ }
1133
+
1134
+ let canAdd = false; let numProps = 0; let showModal = false
1135
+
1136
+ /* Get number of editors */
1137
+ Object.keys(this.editors).forEach(i => numProps++)
1138
+
1139
+ /* Determine if we can add back removed properties */
1140
+ canAdd = this.canHaveAdditionalProperties() && !(typeof this.schema.maxProperties !== 'undefined' && numProps >= this.schema.maxProperties)
1141
+
1142
+ if (this.addproperty_checkboxes) {
1143
+ this.addproperty_list.innerHTML = ''
1144
+ }
1145
+ this.addproperty_checkboxes = {}
1146
+
1147
+ /* Check for which editors can't be removed or added back */
1148
+ Object.keys(this.cached_editors).forEach(i => {
1149
+ this.addPropertyCheckbox(i)
1150
+
1151
+ if (this.isRequiredObject(this.cached_editors[i]) && i in this.editors) {
1152
+ this.addproperty_checkboxes[i].disabled = true
1153
+ }
1154
+
1155
+ if (typeof this.schema.minProperties !== 'undefined' && numProps <= this.schema.minProperties) {
1156
+ this.addproperty_checkboxes[i].disabled = this.addproperty_checkboxes[i].checked
1157
+ if (!this.addproperty_checkboxes[i].checked) showModal = true
1158
+ } else if (!(i in this.editors)) {
1159
+ if (!canAdd && !hasOwnProperty(this.schema.properties, i)) {
1160
+ this.addproperty_checkboxes[i].disabled = true
1161
+ } else {
1162
+ this.addproperty_checkboxes[i].disabled = false
1163
+ showModal = true
1164
+ }
1165
+ } else {
1166
+ showModal = true
1167
+ }
1168
+ })
1169
+
1170
+ if (this.canHaveAdditionalProperties()) {
1171
+ showModal = true
1172
+ }
1173
+
1174
+ /* Additional addproperty checkboxes not tied to a current editor */
1175
+ Object.keys(this.schema.properties).forEach(i => {
1176
+ if (this.cached_editors[i]) return
1177
+ showModal = true
1178
+ this.addPropertyCheckbox(i)
1179
+ })
1180
+
1181
+ /* If no editors can be added or removed, hide the modal button */
1182
+ if (!showModal) {
1183
+ this.hideAddProperty()
1184
+ this.addproperty_button.style.display = 'none'
1185
+ /* If additional properties are disabled */
1186
+ } else if (!this.canHaveAdditionalProperties()) {
1187
+ this.addproperty_add.style.display = 'none'
1188
+ this.addproperty_input.style.display = 'none'
1189
+ /* If no new properties can be added */
1190
+ } else if (!canAdd) {
1191
+ this.addproperty_add.disabled = true
1192
+ /* If new properties can be added */
1193
+ } else {
1194
+ this.addproperty_add.disabled = false
1195
+ }
1196
+ }
1197
+
1198
+ isRequiredObject (editor) {
1199
+ if (!editor) {
1200
+ return
1201
+ }
1202
+ if (typeof editor.schema.required === 'boolean') return editor.schema.required
1203
+ else if (Array.isArray(this.schema.required)) return this.schema.required.includes(editor.key)
1204
+ else if (this.jsoneditor.options.required_by_default) return true
1205
+ return false
1206
+ }
1207
+
1208
+ setValue (value, initial) {
1209
+ value = value || {}
1210
+
1211
+ if (typeof value !== 'object' || Array.isArray(value)) value = {}
1212
+
1213
+ /* First, set the values for all of the defined properties */
1214
+ Object.entries(this.cached_editors).forEach(([i, editor]) => {
1215
+ /* Value explicitly set */
1216
+ if (typeof value[i] !== 'undefined') {
1217
+ this.addObjectProperty(i)
1218
+ editor.setValue(value[i], initial)
1219
+ editor.activate()
1220
+ /* Otherwise, remove value unless this is the initial set or it's required */
1221
+ } else if (!initial && !this.isRequiredObject(editor)) {
1222
+ if (this.jsoneditor.options.show_opt_in || editor.options.show_opt_in) {
1223
+ editor.deactivate()
1224
+ } else {
1225
+ if (!(editor.options.wb && editor.options.wb.show_editor)) {
1226
+ this.removeObjectProperty(i)
1227
+ }
1228
+ }
1229
+ /* Otherwise, set the value to the default */
1230
+ } else {
1231
+ editor.setValue(editor.getDefault(), initial)
1232
+ }
1233
+ })
1234
+
1235
+ Object.entries(value).forEach(([i, val]) => {
1236
+ if (!this.cached_editors[i]) {
1237
+ this.addObjectProperty(i)
1238
+ if (this.editors[i]) this.editors[i].setValue(val, initial, !!this.editors[i].template)
1239
+ }
1240
+ })
1241
+
1242
+ this.refreshValue()
1243
+ this.layoutEditors()
1244
+ this.onChange()
1245
+ }
1246
+
1247
+ showValidationErrors (errors) {
1248
+ /* Get all the errors that pertain to this editor */
1249
+ const myErrors = []
1250
+ const otherErrors = []
1251
+ errors.forEach(error => {
1252
+ if (error.path === this.path) {
1253
+ myErrors.push(error)
1254
+ } else {
1255
+ otherErrors.push(error)
1256
+ }
1257
+ })
1258
+
1259
+ /* Show errors for this editor */
1260
+ if (this.error_holder) {
1261
+ if (myErrors.length) {
1262
+ this.error_holder.innerHTML = ''
1263
+ this.error_holder.style.display = ''
1264
+ myErrors.forEach(error => {
1265
+ if (error.errorcount && error.errorcount > 1) error.message += ` (${error.errorcount} errors)`
1266
+ this.error_holder.appendChild(this.theme.getErrorMessage(error.message))
1267
+ })
1268
+ /* Hide error area */
1269
+ } else {
1270
+ this.error_holder.style.display = 'none'
1271
+ }
1272
+ }
1273
+
1274
+ /* Show error for the table row if this is inside a table */
1275
+ if (this.options.table_row) {
1276
+ if (myErrors.length) {
1277
+ this.theme.addTableRowError(this.container)
1278
+ } else {
1279
+ this.theme.removeTableRowError(this.container)
1280
+ }
1281
+ }
1282
+
1283
+ /* Show errors for child editors */
1284
+ Object.values(this.editors).forEach(editor => {
1285
+ editor.showValidationErrors(otherErrors)
1286
+ })
1287
+ }
1288
+ }
1289
+
1290
+ ObjectEditor.rules = rules