@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
@@ -9,7 +9,7 @@ export class SelectEditor extends AbstractEditor {
9
9
  const haveToUseDefaultValue = !!this.jsoneditor.options.use_default_values || typeof this.schema.default !== 'undefined'
10
10
 
11
11
  if (
12
- !this.enum_values.includes(sanitized) ||
12
+ (this.enum_options.length > 0 && !this.enum_values.includes(sanitized)) ||
13
13
  (initial && !this.isRequired() && !haveToUseDefaultValue)
14
14
  ) {
15
15
  sanitized = this.enum_values[0]
@@ -77,7 +77,7 @@ export class SelectEditor extends AbstractEditor {
77
77
 
78
78
  this.schema.enum.forEach((option, i) => {
79
79
  this.enum_options[i] = `${option}`
80
- this.enum_display[i] = `${display[i] || option}`
80
+ this.enum_display[i] = `${this.translateProperty(display[i]) || option}`
81
81
  this.enum_values[i] = this.typecast(option)
82
82
  })
83
83
 
@@ -162,15 +162,15 @@ export class SelectEditor extends AbstractEditor {
162
162
 
163
163
  build () {
164
164
  if (!this.options.compact) this.header = this.label = this.theme.getFormInputLabel(this.getTitle(), this.isRequired())
165
- if (this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description)
166
- if (this.options.infoText) this.infoButton = this.theme.getInfoButton(this.options.infoText)
165
+ if (this.schema.description) this.description = this.theme.getFormInputDescription(this.translateProperty(this.schema.description))
166
+ if (this.options.infoText) this.infoButton = this.theme.getInfoButton(this.translateProperty(this.options.infoText))
167
167
  if (this.options.compact) this.container.classList.add('compact')
168
168
 
169
169
  this.input = this.theme.getSelectInput(this.enum_options, false)
170
170
  this.theme.setSelectOptions(this.input, this.enum_options, this.enum_display)
171
171
 
172
172
  if (this.schema.readOnly || this.schema.readonly) {
173
- this.always_disabled = true
173
+ this.disable(true)
174
174
  this.input.disabled = true
175
175
  }
176
176
 
@@ -330,8 +330,8 @@ export class SelectEditor extends AbstractEditor {
330
330
  enable () {
331
331
  if (!this.always_disabled) {
332
332
  this.input.disabled = false
333
+ super.enable()
333
334
  }
334
- super.enable()
335
335
  }
336
336
 
337
337
  disable (alwaysDisabled) {
@@ -6,7 +6,7 @@ import { StringEditor } from './string.js'
6
6
  export class SignatureEditor extends StringEditor {
7
7
  build () {
8
8
  if (!this.options.compact) this.header = this.label = this.theme.getFormInputLabel(this.getTitle(), this.isRequired())
9
- if (this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description)
9
+ if (this.schema.description) this.description = this.theme.getFormInputDescription(this.translateProperty(this.schema.description))
10
10
  const formname = this.formname.replace(/\W/g, '')
11
11
 
12
12
  if (typeof SignaturePad === 'function') {
@@ -53,7 +53,8 @@ export class SignatureEditor extends StringEditor {
53
53
  if (this.options.compact) this.container.setAttribute('class', `${this.container.getAttribute('class')} compact`)
54
54
 
55
55
  if (this.schema.readOnly || this.schema.readonly) {
56
- this.always_disabled = true
56
+ this.disable(true)
57
+
57
58
  Array.from(this.inputs).forEach(input => {
58
59
  canvas.setAttribute('readOnly', 'readOnly')
59
60
  input.disabled = true
@@ -4,8 +4,8 @@ import rules from './starrating.css.js'
4
4
  export class StarratingEditor extends StringEditor {
5
5
  build () {
6
6
  if (!this.options.compact) this.header = this.label = this.theme.getFormInputLabel(this.getTitle(), this.isRequired())
7
- if (this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description)
8
- if (this.options.infoText) this.infoButton = this.theme.getInfoButton(this.options.infoText)
7
+ if (this.schema.description) this.description = this.theme.getFormInputDescription(this.translateProperty(this.schema.description))
8
+ if (this.options.infoText) this.infoButton = this.theme.getInfoButton(this.translateProperty(this.options.infoText))
9
9
  if (this.options.compact) this.container.classList.add('compact')
10
10
 
11
11
  this.ratingContainer = document.createElement('div')
@@ -59,7 +59,7 @@ export class StarratingEditor extends StringEditor {
59
59
  }
60
60
 
61
61
  if (this.schema.readOnly || this.schema.readonly) {
62
- this.always_disabled = true
62
+ this.disable(true)
63
63
  for (let j = 0; j < this.radioGroup.length; j++) {
64
64
  this.radioGroup[j].disabled = true
65
65
  }
@@ -83,7 +83,7 @@ export class StarratingEditor extends StringEditor {
83
83
  this.radioGroup[i].disabled = false
84
84
  }
85
85
  this.ratingContainer.classList.remove('readonly')
86
- super.enable()
86
+ this.disabled = false
87
87
  }
88
88
  }
89
89
 
@@ -93,7 +93,7 @@ export class StarratingEditor extends StringEditor {
93
93
  this.radioGroup[i].disabled = true
94
94
  }
95
95
  this.ratingContainer.classList.add('readonly')
96
- super.disable()
96
+ this.disabled = true
97
97
  }
98
98
 
99
99
  destroy () {
@@ -6,12 +6,14 @@ export class StringEditor extends AbstractEditor {
6
6
  super.register()
7
7
  if (!this.input) return
8
8
  this.input.setAttribute('name', this.formname)
9
+ this.input.setAttribute('aria-label', this.formname)
9
10
  }
10
11
 
11
12
  unregister () {
12
13
  super.unregister()
13
14
  if (!this.input) return
14
15
  this.input.removeAttribute('name')
16
+ this.input.removeAttribute('aria-label')
15
17
  }
16
18
 
17
19
  setValue (value, initial, fromTemplate) {
@@ -70,8 +72,8 @@ export class StringEditor extends AbstractEditor {
70
72
 
71
73
  build () {
72
74
  if (!this.options.compact) this.header = this.label = this.theme.getFormInputLabel(this.getTitle(), this.isRequired())
73
- if (this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description)
74
- if (this.options.infoText) this.infoButton = this.theme.getInfoButton(this.options.infoText)
75
+ if (this.schema.description) this.description = this.theme.getFormInputDescription(this.translateProperty(this.schema.description))
76
+ if (this.options.infoText) this.infoButton = this.theme.getInfoButton(this.translateProperty(this.options.infoText))
75
77
 
76
78
  this.format = this.schema.format
77
79
  if (!this.format && this.schema.media && this.schema.media.type) {
@@ -127,7 +129,7 @@ export class StringEditor extends AbstractEditor {
127
129
  } else if (this.options.input_width) this.input.style.width = this.options.input_width
128
130
 
129
131
  if (this.schema.readOnly || this.schema.readonly || this.schema.template) {
130
- this.always_disabled = true
132
+ this.disable(true)
131
133
  this.input.setAttribute('readonly', 'true')
132
134
  }
133
135
 
@@ -201,7 +203,7 @@ export class StringEditor extends AbstractEditor {
201
203
  input = this.theme.getRangeControl(this.input, this.theme.getRangeOutput(this.input, this.schema.default || Math.max(this.schema.minimum || 0, 0)))
202
204
  }
203
205
 
204
- this.control = this.theme.getFormControl(this.label, input, this.description, this.infoButton)
206
+ this.control = this.theme.getFormControl(this.label, input, this.description, this.infoButton, this.formname)
205
207
  this.container.appendChild(this.control)
206
208
 
207
209
  /* Any special formatting that needs to happen after the input is added to the dom */
@@ -31,6 +31,7 @@ export class TableEditor extends ArrayEditor {
31
31
  this.item_default = itemSchema.default || null
32
32
  this.item_has_child_editors = itemSchema.properties || itemSchema.items
33
33
  this.width = 12
34
+ this.array_controls_top = this.options.array_controls_top || this.jsoneditor.options.array_controls_top
34
35
  super.preBuild()
35
36
  }
36
37
 
@@ -52,12 +53,16 @@ export class TableEditor extends ArrayEditor {
52
53
  if (!this.options.compact) {
53
54
  this.header = document.createElement('label')
54
55
  this.header.textContent = this.getTitle()
55
- this.title = this.theme.getHeader(this.header)
56
+ this.title = this.theme.getHeader(this.header, this.getPathDepth())
56
57
  this.container.appendChild(this.title)
58
+ if (this.options.infoText) {
59
+ this.infoButton = this.theme.getInfoButton(this.translateProperty(this.options.infoText))
60
+ this.container.appendChild(this.infoButton)
61
+ }
57
62
  this.title_controls = this.theme.getHeaderButtonHolder()
58
63
  this.title.appendChild(this.title_controls)
59
64
  if (this.schema.description) {
60
- this.description = this.theme.getDescription(this.schema.description)
65
+ this.description = this.theme.getDescription(this.translateProperty(this.schema.description))
61
66
  this.container.appendChild(this.description)
62
67
  }
63
68
  this.panel = this.theme.getIndentedPanel()
@@ -71,7 +76,11 @@ export class TableEditor extends ArrayEditor {
71
76
 
72
77
  this.panel.appendChild(this.table)
73
78
  this.controls = this.theme.getButtonHolder()
74
- this.panel.appendChild(this.controls)
79
+ if (this.array_controls_top) {
80
+ this.title.appendChild(this.controls)
81
+ } else {
82
+ this.panel.appendChild(this.controls)
83
+ }
75
84
 
76
85
  if (this.item_has_child_editors) {
77
86
  const ce = tmp.getChildEditors()
@@ -90,6 +99,7 @@ export class TableEditor extends ArrayEditor {
90
99
 
91
100
  /* Row Controls column */
92
101
  this.controls_header_cell = this.theme.getTableHeaderCell(' ')
102
+ this.controls_header_cell.setAttribute('aria-hidden', 'true')
93
103
  this.header_row.appendChild(this.controls_header_cell)
94
104
 
95
105
  /* Add controls */
@@ -307,11 +317,11 @@ export class TableEditor extends ArrayEditor {
307
317
  this.rows[i].movedown_button = this._createMoveDownButton(i, controlsHolder)
308
318
  }
309
319
 
310
- if (value) this.rows[i].setValue(value)
320
+ if (typeof value !== 'undefined') this.rows[i].setValue(value)
311
321
  }
312
322
 
313
323
  _createDeleteButton (i, holder) {
314
- const button = this.getButton('', 'delete', this.translate('button_delete_row_title_short'))
324
+ const button = this.getButton('', 'delete', 'button_delete_row_title_short')
315
325
  button.classList.add('delete', 'json-editor-btntype-delete')
316
326
  button.setAttribute('data-i', i)
317
327
  button.addEventListener('click', e => {
@@ -336,7 +346,7 @@ export class TableEditor extends ArrayEditor {
336
346
  }
337
347
 
338
348
  _createCopyButton (i, holder) {
339
- const button = this.getButton('', 'copy', this.translate('button_copy_row_title_short'))
349
+ const button = this.getButton('', 'copy', 'button_copy_row_title_short')
340
350
  button.classList.add('copy', 'json-editor-btntype-copy')
341
351
  button.setAttribute('data-i', i)
342
352
  button.addEventListener('click', e => {
@@ -356,7 +366,7 @@ export class TableEditor extends ArrayEditor {
356
366
  }
357
367
 
358
368
  _createMoveUpButton (i, holder) {
359
- const button = this.getButton('', 'moveup', this.translate('button_move_up_title'))
369
+ const button = this.getButton('', 'moveup', 'button_move_up_title')
360
370
  button.classList.add('moveup', 'json-editor-btntype-move')
361
371
  button.setAttribute('data-i', i)
362
372
  button.addEventListener('click', e => {
@@ -377,7 +387,7 @@ export class TableEditor extends ArrayEditor {
377
387
  }
378
388
 
379
389
  _createMoveDownButton (i, holder) {
380
- const button = this.getButton('', 'movedown', this.translate('button_move_down_title'))
390
+ const button = this.getButton('', 'movedown', 'button_move_down_title')
381
391
  button.classList.add('movedown', 'json-editor-btntype-move')
382
392
  button.setAttribute('data-i', i)
383
393
  button.addEventListener('click', e => {
@@ -409,10 +419,10 @@ export class TableEditor extends ArrayEditor {
409
419
  this.setVisibility(this.panel, this.collapsed)
410
420
  if (this.collapsed) {
411
421
  this.collapsed = false
412
- this.setButtonText(e.currentTarget, '', 'collapse', this.translate('button_collapse'))
422
+ this.setButtonText(e.currentTarget, '', 'collapse', 'button_collapse')
413
423
  } else {
414
424
  this.collapsed = true
415
- this.setButtonText(e.currentTarget, '', 'expand', this.translate('button_expand'))
425
+ this.setButtonText(e.currentTarget, '', 'expand', 'button_expand')
416
426
  }
417
427
  })
418
428
 
@@ -436,13 +446,13 @@ export class TableEditor extends ArrayEditor {
436
446
  }
437
447
 
438
448
  _createToggleButton () {
439
- const button = this.getButton('', 'collapse', this.translate('button_collapse'))
449
+ const button = this.getButton('', 'collapse', 'button_collapse')
440
450
  button.classList.add('json-editor-btntype-toggle')
441
451
  return button
442
452
  }
443
453
 
444
454
  _createAddRowButton () {
445
- const button = this.getButton(this.getItemTitle(), 'add', this.translate('button_add_row_title', [this.getItemTitle()]))
455
+ const button = this.getButton(this.getItemTitle(), 'add', 'button_add_row_title', [this.getItemTitle()])
446
456
  button.classList.add('json-editor-btntype-add')
447
457
  button.addEventListener('click', (e) => {
448
458
  e.preventDefault()
@@ -459,7 +469,7 @@ export class TableEditor extends ArrayEditor {
459
469
  }
460
470
 
461
471
  _createDeleteLastRowButton () {
462
- const button = this.getButton(this.translate('button_delete_last', [this.getItemTitle()]), 'subtract', this.translate('button_delete_last_title', [this.getItemTitle()]))
472
+ const button = this.getButton('button_delete_last', 'subtract', 'button_delete_last_title', [this.getItemTitle()])
463
473
  button.classList.add('json-editor-btntype-deletelast')
464
474
  button.addEventListener('click', (e) => {
465
475
  e.preventDefault()
@@ -480,7 +490,7 @@ export class TableEditor extends ArrayEditor {
480
490
  }
481
491
 
482
492
  _createRemoveAllRowsButton () {
483
- const button = this.getButton(this.translate('button_delete_all'), 'delete', this.translate('button_delete_all_title'))
493
+ const button = this.getButton('button_delete_all', 'delete', 'button_delete_all_title')
484
494
  button.classList.add('json-editor-btntype-deleteall')
485
495
  button.addEventListener('click', (e) => {
486
496
  e.preventDefault()
@@ -8,8 +8,8 @@ export class UploadEditor extends AbstractEditor {
8
8
 
9
9
  build () {
10
10
  if (!this.options.compact) this.header = this.label = this.theme.getFormInputLabel(this.getTitle(), this.isRequired())
11
- if (this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description)
12
- 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))
13
13
 
14
14
  /* Editor options */
15
15
  this.options = this.expandCallbacks('upload', extend({}, {
@@ -189,6 +189,7 @@ export class UploadEditor extends AbstractEditor {
189
189
  this.preview.appendChild(img)
190
190
  }
191
191
  img.onerror = error => {
192
+ // eslint-disable-next-line no-console
192
193
  console.error('upload error', error, error.currentTarget)
193
194
  }
194
195
  img.src = this.container.querySelector('a').href
@@ -216,7 +217,7 @@ export class UploadEditor extends AbstractEditor {
216
217
  file.formattedSize = `${parseFloat((file.size / (1024 ** i)).toFixed(2))} ${['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'][i]}`
217
218
  } else file.formattedSize = '0 Bytes'
218
219
 
219
- const uploadButton = this.getButton('Upload', 'upload', 'Upload')
220
+ const uploadButton = this.getButton('button_upload', 'upload', 'button_upload')
220
221
  uploadButton.addEventListener('click', (event) => {
221
222
  event.preventDefault()
222
223
 
@@ -20,7 +20,7 @@ export class UuidEditor extends StringEditor {
20
20
  build () {
21
21
  super.build()
22
22
  /* Set field to readonly */
23
- this.always_disabled = true
23
+ this.disable(true)
24
24
  this.input.setAttribute('readonly', 'true')
25
25
  }
26
26
 
@@ -7,6 +7,7 @@ import { fontawesome5Iconlib } from './fontawesome5.js'
7
7
  // import { foundation3Iconlib } from './foundation3.js'
8
8
  import { jqueryuiIconlib } from './jqueryui.js'
9
9
  // import { materialiconsIconlib } from './materialicons.js'
10
+ import { openiconicIconlib } from './openiconic.js'
10
11
  import { spectreIconlib } from './spectre.js'
11
12
 
12
13
  export const iconlibs = {
@@ -19,5 +20,6 @@ export const iconlibs = {
19
20
  // foundation3: foundation3Iconlib,
20
21
  jqueryui: jqueryuiIconlib,
21
22
  // materialicons: materialiconsIconlib,
23
+ openiconic: openiconicIconlib,
22
24
  spectre: spectreIconlib
23
25
  }
@@ -0,0 +1,28 @@
1
+ import { AbstractIconLib } from '../iconlib.js'
2
+
3
+ const iconPrefix = 'oi oi-'
4
+ const mapping = {
5
+ collapse: 'collapse-down',
6
+ expand: 'expand-right',
7
+ delete: 'trash',
8
+ edit: 'pencil',
9
+ add: 'plus',
10
+ subtract: 'minus',
11
+ cancel: 'ban',
12
+ save: 'file',
13
+ moveup: 'arrow-thick-top',
14
+ moveright: 'arrow-thick-right',
15
+ movedown: 'arrow-thick-bottom',
16
+ moveleft: 'arrow-thick-left',
17
+ copy: 'clipboard',
18
+ clear: 'circle-x',
19
+ time: 'clock',
20
+ calendar: 'calendar',
21
+ edit_properties: 'list'
22
+ }
23
+
24
+ export class openiconicIconlib extends AbstractIconLib {
25
+ constructor () {
26
+ super(iconPrefix, mapping)
27
+ }
28
+ }
@@ -98,6 +98,7 @@ export class SchemaLoader {
98
98
  : ''
99
99
  const ref = this._getRef(fetchUrl, refObj)
100
100
  if (!this.refs[ref]) { /* if reference not found */
101
+ // eslint-disable-next-line no-console
101
102
  console.warn(`reference:'${ref}' not found!`)
102
103
  } else if (recurseAllOf && hasOwnProperty(this.refs[ref], 'allOf')) {
103
104
  const allOf = this.refs[ref].allOf
@@ -135,7 +136,7 @@ export class SchemaLoader {
135
136
 
136
137
  _expandSubSchema (subschema) {
137
138
  /* Array of types */
138
- if (Array.isArray(subschema)) return subschema.map(m => typeof value === 'object' ? this.expandSchema(m) : m)
139
+ if (Array.isArray(subschema)) return subschema.map(m => typeof m === 'object' ? this.expandSchema(m) : m)
139
140
 
140
141
  /* Schema */
141
142
  return this.expandSchema(subschema)
@@ -177,10 +178,19 @@ export class SchemaLoader {
177
178
  mergeRefs(this._getExternalRefs(value, fetchUrl))
178
179
  }
179
180
  })
181
+
182
+ if (schema.id && typeof schema.id === 'string' && schema.id.substr(0, 4) === 'urn:') {
183
+ this.refs[schema.id] = schema
184
+ } else if (schema.$id && typeof schema.$id === 'string' && schema.$id.substr(0, 4) === 'urn:') {
185
+ this.refs[schema.$id] = schema
186
+ }
187
+
180
188
  return refs
181
189
  }
182
190
 
183
191
  _getFileBase (location) {
192
+ if (!location) return '/'
193
+
184
194
  const { ajaxBase } = this.options
185
195
 
186
196
  return typeof ajaxBase === 'undefined' ? this._getFileBaseFromFileLocation(location) : ajaxBase
@@ -192,64 +202,138 @@ export class SchemaLoader {
192
202
  return `${pathItems.join('/')}/`
193
203
  }
194
204
 
195
- _isLocalUrl (url, fileBase) {
196
- return fileBase !== url.substr(0, fileBase.length) &&
197
- url.substr(0, 4) !== 'http' &&
205
+ _joinUrl (url, fileBase) {
206
+ var fetchUrl = url
207
+
208
+ if (url.substr(0, 7) !== 'http://' &&
209
+ url.substr(0, 8) !== 'https://' &&
198
210
  url.substr(0, 5) !== 'blob:' &&
199
211
  url.substr(0, 5) !== 'data:' &&
212
+ url.substr(0, 1) !== '#' &&
200
213
  url.substr(0, 1) !== '/'
214
+ ) {
215
+ fetchUrl = fileBase + url
216
+ }
217
+
218
+ // strip #fragment from URI, so json pointers resolve correctly #928
219
+ if (fetchUrl.indexOf('#') > 0) fetchUrl = fetchUrl.substr(0, fetchUrl.indexOf('#'))
220
+
221
+ return fetchUrl
222
+ }
223
+
224
+ _isUniformResourceName (uri) {
225
+ return uri.substr(0, 4) === 'urn:'
201
226
  }
202
227
 
203
228
  _loadExternalRefs (schema, callback, fetchUrl, fileBase) {
204
229
  const refs = this._getExternalRefs(schema, fetchUrl)
205
- let done = 0; let waiting = 0; let callbackFired = false
230
+ let done = false; let waiting = 0
206
231
 
207
- Object.keys(refs).forEach(url => {
208
- if (this.refs[url]) return
209
- if (!this.options.ajax) throw new Error(`Must set ajax option to true to load external ref ${url}`)
210
- this.refs[url] = 'loading'
211
- waiting++
232
+ Object.keys(refs).forEach(uri => {
233
+ if (this.refs[uri]) return
212
234
 
213
- const fetchUrl = this._isLocalUrl(url, fileBase) ? fileBase + url : url
235
+ if (this._isUniformResourceName(uri)) {
236
+ this.refs[uri] = 'loading'
237
+ waiting++
238
+
239
+ const urnResolver = this.options.urn_resolver
240
+ let urn = uri
241
+ if (typeof urnResolver !== 'function') {
242
+ // eslint-disable-next-line no-console
243
+ console.log(`No "urn_resolver" callback defined to resolve "${urn}"`)
244
+ throw new Error(`Must set urn_resolver option to a callback to resolve ${urn}`)
245
+ }
246
+ // theoretically a URN can contain a JSON pointer
247
+ if (urn.indexOf('#') > 0) urn = urn.substr(0, urn.indexOf('#'))
248
+ let response
249
+ try {
250
+ response = urnResolver(urn, responseText => {
251
+ try {
252
+ schema = JSON.parse(responseText)
253
+ } catch (e) {
254
+ // eslint-disable-next-line no-console
255
+ console.log(e)
256
+ throw new Error(`Failed to parse external ref ${urn}`)
257
+ }
258
+ if (!(typeof schema === 'boolean' || typeof schema === 'object') || schema === null || Array.isArray(schema)) {
259
+ throw new Error(`External ref does not contain a valid schema - ${urn}`)
260
+ }
261
+ this.refs[uri] = schema
262
+ this._getDefinitions(schema, `${urn}#/definitions/`)
263
+ this._loadExternalRefs(schema, () => {
264
+ waiting--
265
+ if (done && !waiting) {
266
+ callback()
267
+ }
268
+ }, uri, '/')
269
+ })
270
+ } catch (e) {
271
+ // eslint-disable-next-line no-console
272
+ console.log(e)
273
+ throw new Error(`Failed to parse external ref ${urn}`)
274
+ }
275
+
276
+ if (typeof response !== 'boolean') {
277
+ throw new Error(`External ref does not contain a valid schema - ${urn}`)
278
+ } else if (response !== true) {
279
+ throw new Error(`External ref did not resolve - ${urn}`)
280
+ }
281
+
282
+ return
283
+ }
284
+
285
+ if (!this.options.ajax) throw new Error(`Must set ajax option to true to load external ref ${uri}`)
286
+ this.refs[uri] = 'loading'
287
+ waiting++
288
+ let url = this._joinUrl(uri, fileBase)
214
289
 
215
290
  const r = new XMLHttpRequest()
216
291
  r.overrideMimeType('application/json')
217
- r.open('GET', fetchUrl, true)
292
+ r.open('GET', url, true)
218
293
  if (this.options.ajaxCredentials) r.withCredentials = this.options.ajaxCredentials
219
294
  r.onreadystatechange = () => {
220
295
  if (r.readyState !== 4) return
221
296
  /* Request succeeded */
222
297
  if (r.status === 200) {
223
- let response
298
+ let schema
224
299
  try {
225
- response = JSON.parse(r.responseText)
300
+ schema = JSON.parse(r.responseText)
226
301
  } catch (e) {
227
- window.console.log(e)
228
- throw new Error(`Failed to parse external ref ${fetchUrl}`)
302
+ // eslint-disable-next-line no-console
303
+ console.log(e)
304
+ throw new Error(`Failed to parse external ref ${url}`)
229
305
  }
230
- if (!(typeof response === 'boolean' || typeof response === 'object') || response === null || Array.isArray(response)) {
231
- throw new Error(`External ref does not contain a valid schema - ${fetchUrl}`)
306
+ if (!(typeof schema === 'boolean' || typeof schema === 'object') || schema === null || Array.isArray(schema)) {
307
+ throw new Error(`External ref does not contain a valid schema - ${url}`)
308
+ }
309
+
310
+ this.refs[uri] = schema
311
+ const fileBase = this._getFileBaseFromFileLocation(url)
312
+
313
+ // add leading slash
314
+ if (url !== uri) {
315
+ const pathItems = url.split('/')
316
+ url = (uri.substr(0, 1) === '/' ? '/' : '') + pathItems.pop()
232
317
  }
233
318
 
234
- this.refs[url] = response
235
- const fileBase = this._getFileBaseFromFileLocation(fetchUrl)
236
- this._getDefinitions(response, `${fetchUrl}#/definitions/`)
237
- this._loadExternalRefs(response, () => {
238
- done++
239
- if (done >= waiting && !callbackFired) {
240
- callbackFired = true
319
+ this._getDefinitions(schema, `${url}#/definitions/`)
320
+ this._loadExternalRefs(schema, () => {
321
+ waiting--
322
+ if (done && !waiting) {
241
323
  callback()
242
324
  }
243
- }, fetchUrl, fileBase)
325
+ }, url, fileBase)
244
326
  } else {
245
327
  /* Request failed */
246
- window.console.log(r)
247
- throw new Error(`Failed to fetch ref via ajax- ${url}`)
328
+ // eslint-disable-next-line no-console
329
+ console.log(r)
330
+ throw new Error(`Failed to fetch ref via ajax - ${uri}`)
248
331
  }
249
332
  }
250
333
  r.send()
251
334
  })
252
335
 
336
+ done = true
253
337
  if (!waiting) {
254
338
  callback()
255
339
  }
package/src/theme.js CHANGED
@@ -105,7 +105,7 @@ export class AbstractTheme {
105
105
  return el
106
106
  }
107
107
 
108
- getHeader (text) {
108
+ getHeader (text, pathDepth) {
109
109
  const el = document.createElement('h3')
110
110
  if (typeof text === 'string') {
111
111
  el.textContent = text
@@ -325,10 +325,13 @@ export class AbstractTheme {
325
325
 
326
326
  }
327
327
 
328
- getFormControl (label, input, description, infoText) {
328
+ getFormControl (label, input, description, infoText, formName) {
329
329
  const el = document.createElement('div')
330
330
  el.classList.add('form-control')
331
- if (label) el.appendChild(label)
331
+ if (label) {
332
+ el.appendChild(label)
333
+ if (formName) label.setAttribute('for', formName)
334
+ }
332
335
  if ((input.type === 'checkbox' || input.type === 'radio') && label) {
333
336
  input.style.width = 'auto'
334
337
  label.insertBefore(input, label.firstChild)
@@ -50,7 +50,7 @@ export class bootstrap3Theme extends AbstractTheme {
50
50
  return el
51
51
  }
52
52
 
53
- getFormControl (label, input, description) {
53
+ getFormControl (label, input, description, infoText) {
54
54
  const group = document.createElement('div')
55
55
 
56
56
  if (label && (input.type === 'checkbox' || input.type === 'radio')) {
@@ -62,6 +62,7 @@ export class bootstrap3Theme extends AbstractTheme {
62
62
  if (label) {
63
63
  label.classList.add('control-label')
64
64
  group.appendChild(label)
65
+ if (infoText) label.appendChild(infoText)
65
66
  }
66
67
  group.appendChild(input)
67
68
  }
@@ -94,7 +95,6 @@ export class bootstrap3Theme extends AbstractTheme {
94
95
  tooltip.style.padding = '.5rem 1rem'
95
96
  tooltip.style['border-radius'] = '.25rem'
96
97
  tooltip.style.width = '25rem'
97
- tooltip.style.transform = 'translateX(-27rem) translateY(-.5rem)'
98
98
  tooltip.style.position = 'absolute'
99
99
  tooltip.innerText = text
100
100
  icon.onmouseover = () => {
@@ -172,14 +172,14 @@ export class bootstrap3Theme extends AbstractTheme {
172
172
  getTabHolder (propertyName) {
173
173
  const pName = (typeof propertyName === 'undefined') ? '' : propertyName
174
174
  const el = document.createElement('div')
175
- el.innerHTML = `<ul class='col-md-2 nav nav-pills nav-stacked' id='${pName}' role='tablist'></ul><div class='col-md-10 tab-content well well-small' id='${pName}'></div>`
175
+ el.innerHTML = `<ul class='col-md-2 nav nav-pills nav-stacked' id='${pName}' role='tablist'></ul><div class='col-md-10 tab-content active well well-small' id='${pName}'></div>`
176
176
  return el
177
177
  }
178
178
 
179
179
  getTopTabHolder (propertyName) {
180
180
  const pName = (typeof propertyName === 'undefined') ? '' : propertyName
181
181
  const el = document.createElement('div')
182
- el.innerHTML = `<ul class='nav nav-tabs' id='${pName}' role='tablist'></ul><div class='tab-content well well-small' id='${pName}'></div>`
182
+ el.innerHTML = `<ul class='nav nav-tabs' id='${pName}' role='tablist'></ul><div class='tab-content active well well-small' id='${pName}'></div>`
183
183
  return el
184
184
  }
185
185