@json-editor/json-editor 2.5.2 → 2.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/.eslintrc +5 -2
  2. package/.github/PULL_REQUEST_TEMPLATE.md +6 -6
  3. package/.github/workflows/build.yml +58 -0
  4. package/CHANGELOG.md +41 -1
  5. package/CONTRIBUTING.md +1 -1
  6. package/README.md +39 -4
  7. package/README_ADDON.md +65 -0
  8. package/config/codeceptjs_helpers.js +4 -0
  9. package/dist/jsoneditor.js +2 -2
  10. package/dist/nonmin/jsoneditor.js +3711 -3324
  11. package/dist/nonmin/jsoneditor.js.map +1 -1
  12. package/docs/cleave.html +1 -1
  13. package/docs/datetime.html +1 -1
  14. package/docs/describedby.html +1 -1
  15. package/docs/index.html +4 -2
  16. package/docs/materialize_css.html +1 -1
  17. package/docs/meta_schema.json +0 -1
  18. package/docs/radio.html +1 -1
  19. package/docs/select2.html +1 -1
  20. package/docs/selectize.html +1 -1
  21. package/docs/starrating.html +1 -1
  22. package/docs/wysiwyg.html +1 -1
  23. package/package.json +27 -26
  24. package/release-notes.md +9 -9
  25. package/src/core.js +1 -0
  26. package/src/defaults.js +182 -94
  27. package/src/editor.js +28 -9
  28. package/src/editors/array.js +20 -16
  29. package/src/editors/autocomplete.js +1 -0
  30. package/src/editors/base64.js +5 -4
  31. package/src/editors/button.js +2 -2
  32. package/src/editors/checkbox.js +3 -3
  33. package/src/editors/datetime.js +2 -2
  34. package/src/editors/info.js +1 -1
  35. package/src/editors/multiple.js +8 -2
  36. package/src/editors/multiselect.js +5 -3
  37. package/src/editors/object.js +35 -21
  38. package/src/editors/radio.js +9 -4
  39. package/src/editors/select.js +6 -6
  40. package/src/editors/signature.js +3 -2
  41. package/src/editors/starrating.js +5 -5
  42. package/src/editors/string.js +6 -4
  43. package/src/editors/table.js +24 -14
  44. package/src/editors/upload.js +4 -3
  45. package/src/editors/uuid.js +1 -1
  46. package/src/iconlibs/index.js +2 -0
  47. package/src/iconlibs/openiconic.js +28 -0
  48. package/src/schemaloader.js +112 -28
  49. package/src/theme.js +6 -3
  50. package/src/themes/bootstrap3.js +4 -4
  51. package/src/themes/bootstrap4.js +11 -3
  52. package/src/themes/html.js +1 -2
  53. package/src/themes/materialize.js +1 -1
  54. package/src/themes/spectre.js +11 -8
  55. package/src/themes/tailwind.js +1 -1
  56. package/src/validator.js +128 -16
  57. package/tests/codeceptjs/core_test.js +125 -1
  58. package/tests/codeceptjs/editors/array_test.js +13 -11
  59. package/tests/codeceptjs/editors/button_test.js +6 -1
  60. package/tests/codeceptjs/editors/issues/issue-gh-812_test.js +32 -0
  61. package/tests/codeceptjs/editors/number_test.js +1 -1
  62. package/tests/codeceptjs/editors/object_test.js +216 -100
  63. package/tests/codeceptjs/editors/programmatic-changes_test.js +3 -1
  64. package/tests/codeceptjs/editors/radio_test.js +10 -0
  65. package/tests/codeceptjs/editors/rating_test.js +10 -11
  66. package/tests/codeceptjs/editors/select_test.js +17 -15
  67. package/tests/codeceptjs/editors/stepper_test.js +13 -1
  68. package/tests/codeceptjs/editors/string_test.js +81 -80
  69. package/tests/codeceptjs/editors/table-confirm-delete_test.js +58 -56
  70. package/tests/codeceptjs/editors/tabs_test.js +12 -10
  71. package/tests/codeceptjs/editors/validation_test.js +10 -8
  72. package/tests/codeceptjs/meta-schema_test.js +13 -14
  73. package/tests/codeceptjs/schemaloader_test.js +13 -0
  74. package/tests/codeceptjs/steps_file.js +4 -3
  75. package/tests/codeceptjs/themes_test.js +31 -0
  76. package/tests/docker-compose.yml +4 -3
  77. package/tests/fixtures/validation.json +382 -1
  78. package/tests/pages/_demo.html +2 -0
  79. package/tests/pages/anyof.html +80 -0
  80. package/tests/pages/issues/issue-gh-812.html +110 -0
  81. package/tests/pages/issues/issue-gh-848.html +81 -0
  82. package/tests/pages/meta_schema.json +0 -1
  83. package/tests/pages/object-no-additional-properties.html +27 -12
  84. package/tests/pages/object-required-properties.html +43 -9
  85. package/tests/pages/object-show-opt-in.html +110 -0
  86. package/tests/pages/object-with-dependencies-array.html +56 -0
  87. package/tests/pages/oneof.html +103 -0
  88. package/tests/pages/read-only.html +19 -4
  89. package/tests/pages/stepper-manual.html +57 -0
  90. package/tests/pages/themes.html +2 -0
  91. package/tests/pages/translate-property.html +247 -0
  92. package/tests/pages/urn.html +93 -0
  93. package/tests/unit/core.spec.js +2 -0
  94. package/tests/unit/defaults.spec.js +4 -2
  95. package/tests/unit/editor.spec.js +2 -0
  96. package/tests/unit/editors/array.spec.js +86 -0
  97. package/tests/unit/editors/table.spec.js +91 -0
  98. package/tests/unit/schemaloader.spec.js +362 -3
  99. package/tests/unit/validator.spec.js +14 -2
package/src/editor.js CHANGED
@@ -11,6 +11,7 @@ export class AbstractEditor {
11
11
  this.template_engine = this.jsoneditor.template
12
12
  this.iconlib = this.jsoneditor.iconlib
13
13
  this.translate = this.jsoneditor.translate || this.defaults.translate
14
+ this.translateProperty = this.jsoneditor.translateProperty || this.defaults.translateProperty
14
15
  this.original_schema = options.schema
15
16
  this.schema = this.jsoneditor.expandSchema(this.original_schema)
16
17
  this.active = true
@@ -129,7 +130,12 @@ export class AbstractEditor {
129
130
  this.notify()
130
131
  }
131
132
 
132
- const displayMode = this.dependenciesFulfilled ? 'block' : 'none'
133
+ let displayMode = this.dependenciesFulfilled ? 'block' : 'none'
134
+
135
+ if (this.options.hidden) {
136
+ displayMode = 'none'
137
+ }
138
+
133
139
  if (wrapper.tagName === 'TD') {
134
140
  Object.keys(wrapper.childNodes).forEach(child => (wrapper.childNodes[child].style.display = displayMode))
135
141
  } else wrapper.style.display = displayMode
@@ -146,9 +152,8 @@ export class AbstractEditor {
146
152
  if (!editor || !editor.dependenciesFulfilled) {
147
153
  this.dependenciesFulfilled = false
148
154
  } else if (Array.isArray(choices)) {
149
- choices.some(choice => {
150
- if (value === choice) {
151
- this.dependenciesFulfilled = true
155
+ this.dependenciesFulfilled = choices.some(choice => {
156
+ if (JSON.stringify(value) === JSON.stringify(choice)) {
152
157
  return true
153
158
  }
154
159
  })
@@ -201,7 +206,11 @@ export class AbstractEditor {
201
206
  })
202
207
 
203
208
  /* append active/deactive checkbox if show_opt_in is true */
204
- if (this.jsoneditor.options.show_opt_in || this.options.show_opt_in) {
209
+ const globalOptIn = this.jsoneditor.options.show_opt_in
210
+ const parentOptInDefined = (typeof this.parent.options.show_opt_in !== 'undefined')
211
+ const parentOptInEnabled = (parentOptInDefined && this.parent.options.show_opt_in === true)
212
+ const parentOptInDisabled = (parentOptInDefined && this.parent.options.show_opt_in === false)
213
+ if (parentOptInEnabled || (!parentOptInDisabled && globalOptIn) || (!parentOptInDefined && globalOptIn)) {
205
214
  /* and control to type object editors if they are not required */
206
215
  if (this.parent && this.parent.schema.type === 'object' && !this.isRequired() && this.header) {
207
216
  this.header.appendChild(this.optInCheckbox)
@@ -293,11 +302,14 @@ export class AbstractEditor {
293
302
 
294
303
  onMove () {}
295
304
 
296
- getButton (text, icon, title) {
305
+ getButton (text, icon, title, args = []) {
297
306
  const btnClass = `json-editor-btn-${icon}`
298
307
  if (!this.iconlib) icon = null
299
308
  else icon = this.iconlib.getIcon(icon)
300
309
 
310
+ text = this.translate(text, args)
311
+ title = this.translate(title, args)
312
+
301
313
  if (!icon && title) {
302
314
  text = title
303
315
  title = null
@@ -308,10 +320,13 @@ export class AbstractEditor {
308
320
  return btn
309
321
  }
310
322
 
311
- setButtonText (button, text, icon, title) {
323
+ setButtonText (button, text, icon, title, args = []) {
312
324
  if (!this.iconlib) icon = null
313
325
  else icon = this.iconlib.getIcon(icon)
314
326
 
327
+ text = this.translate(text, args)
328
+ title = this.translate(title, args)
329
+
315
330
  if (!icon && title) {
316
331
  text = title
317
332
  title = null
@@ -460,10 +475,14 @@ export class AbstractEditor {
460
475
 
461
476
  getHeaderText (titleOnly) {
462
477
  if (this.header_text) return this.header_text
463
- else if (titleOnly) return this.schema.title
478
+ else if (titleOnly) return this.translateProperty(this.schema.title)
464
479
  else return this.getTitle()
465
480
  }
466
481
 
482
+ getPathDepth () {
483
+ return this.path.split('.').length
484
+ }
485
+
467
486
  cleanText (txt) {
468
487
  /* Clean out HTML tags from txt */
469
488
  const tmp = document.createElement('div')
@@ -569,7 +588,7 @@ export class AbstractEditor {
569
588
  }
570
589
 
571
590
  getTitle () {
572
- return this.schema.title || this.key
591
+ return this.translateProperty(this.schema.title || this.key)
573
592
  }
574
593
 
575
594
  enable () {
@@ -95,12 +95,16 @@ export class ArrayEditor extends AbstractEditor {
95
95
  if (!this.options.compact) {
96
96
  this.header = document.createElement('label')
97
97
  this.header.textContent = this.getTitle()
98
- this.title = this.theme.getHeader(this.header)
98
+ this.title = this.theme.getHeader(this.header, this.getPathDepth())
99
99
  this.container.appendChild(this.title)
100
+ if (this.options.infoText) {
101
+ this.infoButton = this.theme.getInfoButton(this.translateProperty(this.options.infoText))
102
+ this.container.appendChild(this.infoButton)
103
+ }
100
104
  this.title_controls = this.theme.getHeaderButtonHolder()
101
105
  this.title.appendChild(this.title_controls)
102
106
  if (this.schema.description) {
103
- this.description = this.theme.getDescription(this.schema.description)
107
+ this.description = this.theme.getDescription(this.translateProperty(this.schema.description))
104
108
  this.container.appendChild(this.description)
105
109
  }
106
110
  this.error_holder = document.createElement('div')
@@ -136,7 +140,7 @@ export class ArrayEditor extends AbstractEditor {
136
140
  }
137
141
  } else {
138
142
  /* compact mode */
139
- this.title = this.theme.getHeader('')
143
+ this.title = this.theme.getHeader('', this.getPathDepth())
140
144
  this.container.appendChild(this.title)
141
145
  this.panel = this.theme.getIndentedPanel()
142
146
  this.container.appendChild(this.panel)
@@ -162,7 +166,7 @@ export class ArrayEditor extends AbstractEditor {
162
166
  if (!this.item_title) {
163
167
  if (this.schema.items && !Array.isArray(this.schema.items)) {
164
168
  const tmp = this.jsoneditor.expandRefs(this.schema.items)
165
- this.item_title = tmp.title || this.translate('default_array_item_title')
169
+ this.item_title = this.translateProperty(tmp.title) || this.translate('default_array_item_title')
166
170
  } else {
167
171
  this.item_title = this.translate('default_array_item_title')
168
172
  }
@@ -200,7 +204,7 @@ export class ArrayEditor extends AbstractEditor {
200
204
  schema = this.jsoneditor.expandRefs(schema)
201
205
 
202
206
  this.item_info[stringified] = {
203
- title: schema.title || this.translate('default_array_item_title'),
207
+ title: this.translateProperty(schema.title) || this.translate('default_array_item_title'),
204
208
  default: schema.default,
205
209
  width: 12,
206
210
  child_editors: schema.properties || schema.items
@@ -492,14 +496,14 @@ export class ArrayEditor extends AbstractEditor {
492
496
  this.rows[i].movedown_button = this._createMoveDownButton(i, controlsHolder)
493
497
  }
494
498
 
495
- if (value) this.rows[i].setValue(value, initial)
499
+ if (typeof value !== 'undefined') this.rows[i].setValue(value, initial)
496
500
  this.refreshTabs()
497
501
 
498
502
  return this.rows[i]
499
503
  }
500
504
 
501
505
  _createDeleteButton (i, holder) {
502
- const button = this.getButton(this.getItemTitle(), 'delete', this.translate('button_delete_row_title', [this.getItemTitle()]))
506
+ const button = this.getButton(this.getItemTitle(), 'delete', 'button_delete_row_title', [this.getItemTitle()])
503
507
  button.classList.add('delete', 'json-editor-btntype-delete')
504
508
  button.setAttribute('data-i', i)
505
509
  button.addEventListener('click', e => {
@@ -540,7 +544,7 @@ export class ArrayEditor extends AbstractEditor {
540
544
  }
541
545
 
542
546
  _createCopyButton (i, holder) {
543
- const button = this.getButton(this.getItemTitle(), 'copy', `Copy ${this.getItemTitle()}`)
547
+ const button = this.getButton(this.getItemTitle(), 'copy', 'button_copy_row_title', [this.getItemTitle()])
544
548
  button.classList.add('copy', 'json-editor-btntype-copy')
545
549
  button.setAttribute('data-i', i)
546
550
  button.addEventListener('click', e => {
@@ -565,7 +569,7 @@ export class ArrayEditor extends AbstractEditor {
565
569
  }
566
570
 
567
571
  _createMoveUpButton (i, holder) {
568
- const button = this.getButton('', (this.schema.format === 'tabs-top' ? 'moveleft' : 'moveup'), this.translate('button_move_up_title'))
572
+ const button = this.getButton('', (this.schema.format === 'tabs-top' ? 'moveleft' : 'moveup'), 'button_move_up_title')
569
573
  button.classList.add('moveup', 'json-editor-btntype-move')
570
574
  button.setAttribute('data-i', i)
571
575
  button.addEventListener('click', e => {
@@ -595,7 +599,7 @@ export class ArrayEditor extends AbstractEditor {
595
599
  }
596
600
 
597
601
  _createMoveDownButton (i, holder) {
598
- const button = this.getButton('', (this.schema.format === 'tabs-top' ? 'moveright' : 'movedown'), this.translate('button_move_down_title'))
602
+ const button = this.getButton('', (this.schema.format === 'tabs-top' ? 'moveright' : 'movedown'), 'button_move_down_title')
599
603
  button.classList.add('movedown', 'json-editor-btntype-move')
600
604
  button.setAttribute('data-i', i)
601
605
  button.addEventListener('click', e => {
@@ -652,7 +656,7 @@ export class ArrayEditor extends AbstractEditor {
652
656
  }
653
657
 
654
658
  _createToggleButton () {
655
- const button = this.getButton('', 'collapse', this.translate('button_collapse'))
659
+ const button = this.getButton('', 'collapse', 'button_collapse')
656
660
  button.classList.add('json-editor-btntype-toggle')
657
661
  this.title.insertBefore(button, this.title.childNodes[0])
658
662
 
@@ -667,19 +671,19 @@ export class ArrayEditor extends AbstractEditor {
667
671
  this.collapsed = false
668
672
  this.row_holder.style.display = rowHolderDisplay
669
673
  this.controls.style.display = controlsDisplay
670
- this.setButtonText(e.currentTarget, '', 'collapse', this.translate('button_collapse'))
674
+ this.setButtonText(e.currentTarget, '', 'collapse', 'button_collapse')
671
675
  } else {
672
676
  this.collapsed = true
673
677
  this.row_holder.style.display = 'none'
674
678
  this.controls.style.display = 'none'
675
- this.setButtonText(e.currentTarget, '', 'expand', this.translate('button_expand'))
679
+ this.setButtonText(e.currentTarget, '', 'expand', 'button_expand')
676
680
  }
677
681
  })
678
682
  return button
679
683
  }
680
684
 
681
685
  _createAddRowButton () {
682
- const button = this.getButton(this.getItemTitle(), 'add', this.translate('button_add_row_title', [this.getItemTitle()]))
686
+ const button = this.getButton(this.getItemTitle(), 'add', 'button_add_row_title', [this.getItemTitle()])
683
687
  button.classList.add('json-editor-btntype-add')
684
688
  button.addEventListener('click', (e) => {
685
689
  e.preventDefault()
@@ -706,7 +710,7 @@ export class ArrayEditor extends AbstractEditor {
706
710
  }
707
711
 
708
712
  _createDeleteLastRowButton () {
709
- const button = this.getButton(this.translate('button_delete_last', [this.getItemTitle()]), 'subtract', this.translate('button_delete_last_title', [this.getItemTitle()]))
713
+ const button = this.getButton('button_delete_last', 'subtract', 'button_delete_last_title', [this.getItemTitle()])
710
714
  button.classList.add('json-editor-btntype-deletelast')
711
715
  button.addEventListener('click', (e) => {
712
716
  e.preventDefault()
@@ -740,7 +744,7 @@ export class ArrayEditor extends AbstractEditor {
740
744
  }
741
745
 
742
746
  _createRemoveAllRowsButton () {
743
- const button = this.getButton(this.translate('button_delete_all'), 'delete', this.translate('button_delete_all_title'))
747
+ const button = this.getButton('button_delete_all', 'delete', 'button_delete_all_title')
744
748
  button.classList.add('json-editor-btntype-deleteall')
745
749
  button.addEventListener('click', (e) => {
746
750
  e.preventDefault()
@@ -30,6 +30,7 @@ export class AutocompleteEditor extends StringEditor {
30
30
  /* single property options from schema "options.autocomplete" */
31
31
  options = this.expandCallbacks('autocomplete', extend({}, {
32
32
  search: (jseditor, input) => {
33
+ // eslint-disable-next-line no-console
33
34
  console.log(`No "search" callback defined for autocomplete in property "${jseditor.key}"`)
34
35
  return []
35
36
  },
@@ -31,8 +31,8 @@ export class Base64Editor extends AbstractEditor {
31
31
  }
32
32
 
33
33
  build () {
34
- this.title = this.header = this.label = this.theme.getFormInputLabel(this.getTitle(), this.isRequired())
35
- if (this.options.infoText) this.infoButton = this.theme.getInfoButton(this.options.infoText)
34
+ if (!this.options.compact) this.title = this.header = this.label = this.theme.getFormInputLabel(this.getTitle(), this.isRequired())
35
+ if (this.options.infoText) this.infoButton = this.theme.getInfoButton(this.translateProperty(this.options.infoText))
36
36
 
37
37
  /* Input that holds the base64 string */
38
38
  this.input = this.theme.getFormInputField('hidden')
@@ -90,7 +90,7 @@ export class Base64Editor extends AbstractEditor {
90
90
  })
91
91
  }
92
92
 
93
- this.preview = this.theme.getFormInputDescription(this.schema.description)
93
+ this.preview = this.theme.getFormInputDescription(this.translateProperty(this.schema.description))
94
94
  this.container.appendChild(this.preview)
95
95
 
96
96
  this.control = this.theme.getFormControl(this.label, this.uploader || this.input, this.preview, this.infoButton)
@@ -138,7 +138,8 @@ export class Base64Editor extends AbstractEditor {
138
138
 
139
139
  setValue (val) {
140
140
  if (this.value !== val) {
141
- this.value = val
141
+ if (this.schema.readOnly && this.schema.enum && !this.schema.enum.includes(val)) this.value = this.schema.enum[0]
142
+ else this.value = val
142
143
  this.input.value = this.value
143
144
  this.refreshPreview()
144
145
  this.onChange()
@@ -24,7 +24,7 @@ export class ButtonEditor extends AbstractEditor {
24
24
 
25
25
  /* Get options, either global options from "this.defaults.options.button" or */
26
26
  /* single property options from schema "options.button" */
27
- const title = this.schema.title || this.key
27
+ const title = this.translateProperty(this.schema.title) || this.key
28
28
  const options = this.expandCallbacks('button', extend({}, {
29
29
  icon: '',
30
30
  validated: false,
@@ -38,7 +38,7 @@ export class ButtonEditor extends AbstractEditor {
38
38
  this.input.addEventListener('click', options.action, false)
39
39
 
40
40
  if (this.schema.readOnly || this.schema.readonly || this.schema.template) {
41
- this.always_disabled = true
41
+ this.disable(true)
42
42
  this.input.setAttribute('readonly', 'true')
43
43
  }
44
44
 
@@ -31,8 +31,8 @@ export class CheckboxEditor extends AbstractEditor {
31
31
  this.label.htmlFor = this.formname
32
32
  }
33
33
 
34
- if (this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description)
35
- if (this.options.infoText && !this.options.compact) this.infoButton = this.theme.getInfoButton(this.options.infoText)
34
+ if (this.schema.description) this.description = this.theme.getFormInputDescription(this.translateProperty(this.schema.description))
35
+ if (this.options.infoText && !this.options.compact) this.infoButton = this.theme.getInfoButton(this.translateProperty(this.options.infoText))
36
36
  if (this.options.compact) this.container.classList.add('compact')
37
37
 
38
38
  this.input = this.theme.getCheckbox()
@@ -40,7 +40,7 @@ export class CheckboxEditor extends AbstractEditor {
40
40
  this.control = this.theme.getFormControl(this.label, this.input, this.description, this.infoButton)
41
41
 
42
42
  if (this.schema.readOnly || this.schema.readonly) {
43
- this.always_disabled = true
43
+ this.disable(true)
44
44
  this.input.disabled = true
45
45
  }
46
46
 
@@ -51,13 +51,13 @@ export class DatetimeEditor extends StringEditor {
51
51
  /* Create buttons for input group */
52
52
  const buttons = []
53
53
  if (this.options.flatpickr.showToggleButton !== false) {
54
- const toggleButton = this.getButton('', this.schema.format === 'time' ? 'time' : 'calendar', this.translate('flatpickr_toggle_button'))
54
+ const toggleButton = this.getButton('', this.schema.format === 'time' ? 'time' : 'calendar', 'flatpickr_toggle_button')
55
55
  /* Attribute for flatpicker */
56
56
  toggleButton.setAttribute('data-toggle', '')
57
57
  buttons.push(toggleButton)
58
58
  }
59
59
  if (this.options.flatpickr.showClearButton !== false) {
60
- const clearButton = this.getButton('', 'clear', this.translate('flatpickr_clear_button'))
60
+ const clearButton = this.getButton('', 'clear', 'flatpickr_clear_button')
61
61
  /* Attribute for flatpicker */
62
62
  clearButton.setAttribute('data-clear', '')
63
63
  buttons.push(clearButton)
@@ -11,7 +11,7 @@ export class InfoEditor extends ButtonEditor {
11
11
  }
12
12
 
13
13
  getTitle () {
14
- return this.schema.title
14
+ return this.translateProperty(this.schema.title)
15
15
  }
16
16
 
17
17
  getNumColumns () {
@@ -267,6 +267,8 @@ export class MultipleEditor extends AbstractEditor {
267
267
  if (fitTestResult !== null) {
268
268
  validVal.match = fitTestResult.match
269
269
  }
270
+ } else {
271
+ fitTestVal = validVal
270
272
  }
271
273
  })
272
274
  let finalI = validVal.i
@@ -311,9 +313,13 @@ export class MultipleEditor extends AbstractEditor {
311
313
  if (!editor) return
312
314
  const check = `${this.path}.${checkPart}[${i}]`
313
315
  const filterError = (newErrors, error) => {
314
- if (error.path === check.substr(0, error.path.length)) {
316
+ if (error.path.startsWith(check) || error.path === check.substr(0, error.path.length)) {
315
317
  const newError = extend({}, error)
316
- newError.path = this.path + newError.path.substr(check.length)
318
+
319
+ if (error.path.startsWith(check)) {
320
+ newError.path = this.path + newError.path.substr(check.length)
321
+ }
322
+
317
323
  newErrors.push(newError)
318
324
  }
319
325
  return newErrors
@@ -51,8 +51,8 @@ export class MultiSelectEditor extends AbstractEditor {
51
51
  build () {
52
52
  let i
53
53
  if (!this.options.compact) this.header = this.label = this.theme.getFormInputLabel(this.getTitle(), this.isRequired())
54
- if (this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description)
55
- if (this.options.infoText) this.infoButton = this.theme.getInfoButton(this.options.infoText)
54
+ if (this.schema.description) this.description = this.theme.getFormInputDescription(this.translateProperty(this.schema.description))
55
+ if (this.options.infoText) this.infoButton = this.theme.getInfoButton(this.translateProperty(this.options.infoText))
56
56
  if (this.options.compact) this.container.classList.add('compact')
57
57
 
58
58
  if ((!this.schema.format && this.option_keys.length < 8) || this.schema.format === 'checkbox') {
@@ -86,7 +86,9 @@ export class MultiSelectEditor extends AbstractEditor {
86
86
  this.control = this.theme.getFormControl(this.label, this.input, this.description, this.infoButton)
87
87
  }
88
88
 
89
- if (this.schema.readOnly || this.schema.readonly) this.disable(true)
89
+ if (this.schema.readOnly || this.schema.readonly) {
90
+ this.disable(true)
91
+ }
90
92
 
91
93
  this.container.appendChild(this.control)
92
94
 
@@ -230,7 +230,7 @@ export class ObjectEditor extends AbstractEditor {
230
230
  const containerSimple = document.createElement('div')
231
231
  /* This will be the place to (re)build tabs and panes */
232
232
  /* tabs_holder has 2 childs, [0]: ul.nav.nav-tabs and [1]: div.tab-content */
233
- const newTabsHolder = this.theme.getTopTabHolder(this.schema.title)
233
+ const newTabsHolder = this.theme.getTopTabHolder(this.translateProperty(this.schema.title))
234
234
  /* child [1] of previous, stores panes */
235
235
  const newTabPanesContainer = this.theme.getTopTabContentHolder(newTabsHolder)
236
236
 
@@ -555,7 +555,7 @@ export class ObjectEditor extends AbstractEditor {
555
555
  this.header = document.createElement('label')
556
556
  this.header.textContent = this.getTitle()
557
557
  }
558
- this.title = this.theme.getHeader(this.header)
558
+ this.title = this.theme.getHeader(this.header, this.getPathDepth())
559
559
  this.title.classList.add('je-object__title')
560
560
  this.controls = this.theme.getButtonHolder()
561
561
  this.controls.classList.add('je-object__controls')
@@ -568,21 +568,21 @@ export class ObjectEditor extends AbstractEditor {
568
568
  this.editjson_holder = this.theme.getModal()
569
569
  this.editjson_textarea = this.theme.getTextareaInput()
570
570
  this.editjson_textarea.classList.add('je-edit-json--textarea')
571
- this.editjson_save = this.getButton('Save', 'save', 'Save')
571
+ this.editjson_save = this.getButton('button_save', 'save', 'button_save')
572
572
  this.editjson_save.classList.add('json-editor-btntype-save')
573
573
  this.editjson_save.addEventListener('click', (e) => {
574
574
  e.preventDefault()
575
575
  e.stopPropagation()
576
576
  this.saveJSON()
577
577
  })
578
- this.editjson_copy = this.getButton('Copy', 'copy', 'Copy')
578
+ this.editjson_copy = this.getButton('button_copy', 'copy', 'button_copy')
579
579
  this.editjson_copy.classList.add('json-editor-btntype-copy')
580
580
  this.editjson_copy.addEventListener('click', (e) => {
581
581
  e.preventDefault()
582
582
  e.stopPropagation()
583
583
  this.copyJSON()
584
584
  })
585
- this.editjson_cancel = this.getButton('Cancel', 'cancel', 'Cancel')
585
+ this.editjson_cancel = this.getButton('button_cancel', 'cancel', 'button_cancel')
586
586
  this.editjson_cancel.classList.add('json-editor-btntype-cancel')
587
587
  this.editjson_cancel.addEventListener('click', (e) => {
588
588
  e.preventDefault()
@@ -598,7 +598,7 @@ export class ObjectEditor extends AbstractEditor {
598
598
  this.addproperty_holder = this.theme.getModal()
599
599
  this.addproperty_list = document.createElement('div')
600
600
  this.addproperty_list.classList.add('property-selector')
601
- this.addproperty_add = this.getButton('add', 'add', 'add')
601
+ this.addproperty_add = this.getButton('button_add', 'add', 'button_add')
602
602
  this.addproperty_add.classList.add('json-editor-btntype-add')
603
603
 
604
604
  this.addproperty_input = this.theme.getFormInputField('text')
@@ -637,11 +637,11 @@ export class ObjectEditor extends AbstractEditor {
637
637
  this.addproperty_holder.appendChild(spacer)
638
638
 
639
639
  /* Close properties modal if clicked outside modal */
640
- document.addEventListener('click', this.onOutsideModalClick)
640
+ document.addEventListener('click', this.onOutsideModalClick.bind(this))
641
641
 
642
642
  /* Description */
643
643
  if (this.schema.description) {
644
- this.description = this.theme.getDescription(this.schema.description)
644
+ this.description = this.theme.getDescription(this.translateProperty(this.schema.description))
645
645
  this.container.appendChild(this.description)
646
646
  }
647
647
 
@@ -657,11 +657,11 @@ export class ObjectEditor extends AbstractEditor {
657
657
  this.row_container = this.theme.getGridContainer()
658
658
 
659
659
  if (isCategoriesFormat) {
660
- this.tabs_holder = this.theme.getTopTabHolder(this.getValidId(this.schema.title))
660
+ this.tabs_holder = this.theme.getTopTabHolder(this.getValidId(this.translateProperty(this.schema.title)))
661
661
  this.tabPanesContainer = this.theme.getTopTabContentHolder(this.tabs_holder)
662
662
  this.editor_holder.appendChild(this.tabs_holder)
663
663
  } else {
664
- this.tabs_holder = this.theme.getTabHolder(this.getValidId(this.schema.title))
664
+ this.tabs_holder = this.theme.getTabHolder(this.getValidId(this.translateProperty(this.schema.title)))
665
665
  this.tabPanesContainer = this.theme.getTabContentHolder(this.tabs_holder)
666
666
  this.editor_holder.appendChild(this.row_container)
667
667
  }
@@ -711,7 +711,7 @@ export class ObjectEditor extends AbstractEditor {
711
711
 
712
712
  /* Show/Hide button */
713
713
  this.collapsed = false
714
- this.collapse_control = this.getButton('', 'collapse', this.translate('button_collapse'))
714
+ this.collapse_control = this.getButton('', 'collapse', 'button_collapse')
715
715
  this.collapse_control.classList.add('json-editor-btntype-toggle')
716
716
  this.title.insertBefore(this.collapse_control, this.title.childNodes[0])
717
717
 
@@ -721,11 +721,11 @@ export class ObjectEditor extends AbstractEditor {
721
721
  if (this.collapsed) {
722
722
  this.editor_holder.style.display = ''
723
723
  this.collapsed = false
724
- this.setButtonText(this.collapse_control, '', 'collapse', this.translate('button_collapse'))
724
+ this.setButtonText(this.collapse_control, '', 'collapse', 'button_collapse')
725
725
  } else {
726
726
  this.editor_holder.style.display = 'none'
727
727
  this.collapsed = true
728
- this.setButtonText(this.collapse_control, '', 'expand', this.translate('button_expand'))
728
+ this.setButtonText(this.collapse_control, '', 'expand', 'button_expand')
729
729
  }
730
730
  })
731
731
 
@@ -742,7 +742,7 @@ export class ObjectEditor extends AbstractEditor {
742
742
  }
743
743
 
744
744
  /* Edit JSON Button */
745
- this.editjson_control = this.getButton('JSON', 'edit', 'Edit JSON')
745
+ this.editjson_control = this.getButton('JSON', 'edit', 'button_edit_json')
746
746
  this.editjson_control.classList.add('json-editor-btntype-editjson')
747
747
  this.editjson_control.addEventListener('click', (e) => {
748
748
  e.preventDefault()
@@ -760,7 +760,7 @@ export class ObjectEditor extends AbstractEditor {
760
760
  }
761
761
 
762
762
  /* Object Properties Button */
763
- this.addproperty_button = this.getButton('Properties', 'edit_properties', this.translate('button_object_properties'))
763
+ this.addproperty_button = this.getButton('properties', 'edit_properties', 'button_object_properties')
764
764
  this.addproperty_button.classList.add('json-editor-btntype-properties')
765
765
  this.addproperty_button.addEventListener('click', (e) => {
766
766
  e.preventDefault()
@@ -793,7 +793,11 @@ export class ObjectEditor extends AbstractEditor {
793
793
 
794
794
  deactivateNonRequiredProperties () {
795
795
  /* the show_opt_in editor option is for backward compatibility */
796
- if (this.jsoneditor.options.show_opt_in || this.options.show_opt_in) {
796
+ const globalOptIn = this.jsoneditor.options.show_opt_in
797
+ const editorOptInDefined = (typeof this.options.show_opt_in !== 'undefined')
798
+ const editorOptInEnabled = (editorOptInDefined && this.options.show_opt_in === true)
799
+ const editorOptInDisabled = (editorOptInDefined && this.options.show_opt_in === false)
800
+ if (editorOptInEnabled || (!editorOptInDisabled && globalOptIn) || (!editorOptInDefined && globalOptIn)) {
797
801
  Object.entries(this.editors).forEach(([key, editor]) => {
798
802
  if (!this.isRequiredObject(editor)) {
799
803
  this.editors[key].deactivate()
@@ -968,6 +972,7 @@ export class ObjectEditor extends AbstractEditor {
968
972
  [key]: {}
969
973
  }
970
974
  case 'additionalProperties':
975
+ case 'propertyNames':
971
976
  return {
972
977
  ...acc,
973
978
  [key]: true
@@ -992,7 +997,8 @@ export class ObjectEditor extends AbstractEditor {
992
997
  this.editors[name].register()
993
998
  /* New property */
994
999
  } else {
995
- if (!this.canHaveAdditionalProperties() && (!this.schema.properties || !this.schema.properties[name])) {
1000
+ if (!this.canHaveAdditionalProperties() && (!this.schema.properties || !this.schema.properties[name]) &&
1001
+ (!this.schema.patternProperties || !(Object.keys(this.schema.patternProperties).find(i => new RegExp(i).test(name))))) {
996
1002
  return
997
1003
  }
998
1004
 
@@ -1036,9 +1042,8 @@ export class ObjectEditor extends AbstractEditor {
1036
1042
  }
1037
1043
 
1038
1044
  onOutsideModalClick (e) {
1039
- if (this.addproperty_holder &&
1040
- !this.addproperty_holder.contains(e.path[0] || e.composedPath()[0]) &&
1041
- this.adding_property) {
1045
+ const path = e.path || (e.composedPath && e.composedPath())
1046
+ if (this.addproperty_holder && !this.addproperty_holder.contains(path[0]) && this.adding_property) {
1042
1047
  e.preventDefault()
1043
1048
  e.stopPropagation()
1044
1049
  this.toggleAddProperty()
@@ -1096,6 +1101,10 @@ export class ObjectEditor extends AbstractEditor {
1096
1101
  refreshValue () {
1097
1102
  this.value = {}
1098
1103
 
1104
+ if (!this.editors) {
1105
+ return
1106
+ }
1107
+
1099
1108
  Object.keys(this.editors).forEach(i => {
1100
1109
  if (this.editors[i].isActive()) {
1101
1110
  this.value[i] = this.editors[i].getValue()
@@ -1196,9 +1205,14 @@ export class ObjectEditor extends AbstractEditor {
1196
1205
  if (typeof value[i] !== 'undefined') {
1197
1206
  this.addObjectProperty(i)
1198
1207
  editor.setValue(value[i], initial)
1208
+ editor.activate()
1199
1209
  /* Otherwise, remove value unless this is the initial set or it's required */
1200
1210
  } else if (!initial && !this.isRequiredObject(editor)) {
1201
- this.removeObjectProperty(i)
1211
+ if (this.jsoneditor.options.show_opt_in || this.options.show_opt_in) {
1212
+ editor.deactivate()
1213
+ } else {
1214
+ this.removeObjectProperty(i)
1215
+ }
1202
1216
  /* Otherwise, set the value to the default */
1203
1217
  } else {
1204
1218
  editor.setValue(editor.getDefault(), initial)
@@ -2,15 +2,14 @@ import { SelectEditor } from './select.js'
2
2
 
3
3
  export class RadioEditor extends SelectEditor {
4
4
  preBuild () {
5
- this.schema.required = true /* force editor into required mode to prevent creation of empty radio button */
6
5
  super.preBuild()
7
6
  }
8
7
 
9
8
  build () {
10
9
  this.label = ''
11
10
  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.schema.description)
13
- if (this.options.infoText) this.infoButton = this.theme.getInfoButton(this.options.infoText)
11
+ if (this.schema.description) this.description = this.theme.getFormInputDescription(this.translateProperty(this.schema.description))
12
+ if (this.options.infoText) this.infoButton = this.theme.getInfoButton(this.translateProperty(this.options.infoText))
14
13
  if (this.options.compact) this.container.classList.add('compact')
15
14
 
16
15
  this.radioContainer = document.createElement('div')
@@ -22,6 +21,12 @@ export class RadioEditor extends SelectEditor {
22
21
  this.onChange(true)
23
22
  }
24
23
 
24
+ if (!this.isRequired()) {
25
+ this.enum_display.shift()
26
+ this.enum_options.shift()
27
+ this.enum_values.shift()
28
+ }
29
+
25
30
  for (let i = 0; i < this.enum_values.length; i++) {
26
31
  /* form radio elements */
27
32
  this.input = this.theme.getFormRadio({
@@ -46,7 +51,7 @@ export class RadioEditor extends SelectEditor {
46
51
  }
47
52
 
48
53
  if (this.schema.readOnly || this.schema.readonly) {
49
- this.always_disabled = true
54
+ this.disable(true)
50
55
  for (let j = 0; j < this.radioGroup.length; j++) {
51
56
  this.radioGroup[j].disabled = true
52
57
  }