@json-editor/json-editor 2.5.1 → 2.6.0

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 (92) 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 +12 -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 +3849 -3304
  11. package/dist/nonmin/jsoneditor.js.map +1 -1
  12. package/docs/index.html +2 -0
  13. package/docs/meta_schema.json +0 -1
  14. package/package.json +27 -26
  15. package/release-notes.md +9 -0
  16. package/src/core.js +1 -0
  17. package/src/defaults.js +182 -94
  18. package/src/editor.js +32 -12
  19. package/src/editors/array.js +20 -16
  20. package/src/editors/autocomplete.js +1 -0
  21. package/src/editors/base64.js +5 -4
  22. package/src/editors/button.js +2 -2
  23. package/src/editors/checkbox.js +3 -3
  24. package/src/editors/datetime.js +2 -2
  25. package/src/editors/info.js +1 -1
  26. package/src/editors/multiple.js +8 -2
  27. package/src/editors/multiselect.js +5 -3
  28. package/src/editors/object.js +32 -17
  29. package/src/editors/radio.js +9 -4
  30. package/src/editors/select.js +6 -6
  31. package/src/editors/signature.js +3 -2
  32. package/src/editors/starrating.js +5 -5
  33. package/src/editors/stepper.js +3 -0
  34. package/src/editors/string.js +6 -4
  35. package/src/editors/table.js +24 -14
  36. package/src/editors/upload.js +4 -3
  37. package/src/editors/uuid.js +1 -1
  38. package/src/iconlibs/index.js +2 -0
  39. package/src/iconlibs/openiconic.js +28 -0
  40. package/src/schemaloader.js +112 -28
  41. package/src/theme.js +36 -5
  42. package/src/themes/bootstrap3.js +4 -4
  43. package/src/themes/bootstrap4.js +41 -5
  44. package/src/themes/html.js +1 -2
  45. package/src/themes/materialize.js +1 -1
  46. package/src/themes/spectre.js +11 -8
  47. package/src/themes/tailwind.js +1 -1
  48. package/src/validator.js +129 -17
  49. package/tests/codeceptjs/core_test.js +204 -50
  50. package/tests/codeceptjs/editors/array_test.js +13 -11
  51. package/tests/codeceptjs/editors/button_test.js +6 -1
  52. package/tests/codeceptjs/editors/issues/issue-gh-812_test.js +32 -0
  53. package/tests/codeceptjs/editors/number_test.js +1 -1
  54. package/tests/codeceptjs/editors/object_test.js +194 -98
  55. package/tests/codeceptjs/editors/programmatic-changes_test.js +3 -1
  56. package/tests/codeceptjs/editors/radio_test.js +10 -0
  57. package/tests/codeceptjs/editors/rating_test.js +10 -11
  58. package/tests/codeceptjs/editors/select_test.js +17 -15
  59. package/tests/codeceptjs/editors/stepper_test.js +13 -1
  60. package/tests/codeceptjs/editors/string_test.js +81 -80
  61. package/tests/codeceptjs/editors/table-confirm-delete_test.js +58 -56
  62. package/tests/codeceptjs/editors/tabs_test.js +12 -10
  63. package/tests/codeceptjs/editors/validation_test.js +10 -8
  64. package/tests/codeceptjs/meta-schema_test.js +13 -14
  65. package/tests/codeceptjs/schemaloader_test.js +13 -0
  66. package/tests/codeceptjs/steps_file.js +4 -3
  67. package/tests/codeceptjs/themes_test.js +31 -0
  68. package/tests/docker-compose.yml +4 -3
  69. package/tests/fixtures/validation.json +382 -1
  70. package/tests/pages/_demo.html +2 -0
  71. package/tests/pages/anyof.html +80 -0
  72. package/tests/pages/form-name.html +108 -0
  73. package/tests/pages/issues/issue-gh-812.html +110 -0
  74. package/tests/pages/issues/issue-gh-848.html +81 -0
  75. package/tests/pages/meta_schema.json +0 -1
  76. package/tests/pages/object-no-additional-properties.html +27 -12
  77. package/tests/pages/object-required-properties.html +43 -9
  78. package/tests/pages/object-show-opt-in.html +110 -0
  79. package/tests/pages/object-with-dependencies-array.html +46 -0
  80. package/tests/pages/oneof.html +103 -0
  81. package/tests/pages/read-only.html +19 -4
  82. package/tests/pages/stepper-manual.html +57 -0
  83. package/tests/pages/themes.html +2 -0
  84. package/tests/pages/translate-property.html +247 -0
  85. package/tests/pages/urn.html +93 -0
  86. package/tests/unit/core.spec.js +2 -0
  87. package/tests/unit/defaults.spec.js +4 -2
  88. package/tests/unit/editor.spec.js +2 -0
  89. package/tests/unit/editors/array.spec.js +86 -0
  90. package/tests/unit/editors/table.spec.js +91 -0
  91. package/tests/unit/schemaloader.spec.js +362 -3
  92. package/tests/unit/validator.spec.js +14 -2
@@ -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
@@ -254,13 +254,41 @@ export class AbstractTheme {
254
254
  minusBtn.textContent = '-'
255
255
  plusBtn.textContent = '+'
256
256
 
257
+ const initialize = (input, min) => {
258
+ if (min) {
259
+ input.value = Number(min)
260
+ } else {
261
+ input.value = Number(input.value)
262
+ }
263
+ input.setAttribute('initialized', '1')
264
+ }
265
+
266
+ const min = input.getAttribute('min')
267
+ const max = input.getAttribute('max')
268
+
257
269
  minusBtn.addEventListener('click', () => {
258
- input.stepDown()
270
+ if (!input.getAttribute('initialized')) {
271
+ initialize(input, min)
272
+ } else if (min) {
273
+ if (Number(input.value) > Number(min)) {
274
+ input.stepDown()
275
+ }
276
+ } else {
277
+ input.stepDown()
278
+ }
259
279
  trigger(input, 'change')
260
280
  })
261
281
 
262
282
  plusBtn.addEventListener('click', () => {
263
- input.stepUp()
283
+ if (!input.getAttribute('initialized')) {
284
+ initialize(input, min)
285
+ } else if (max) {
286
+ if (Number(input.value) < Number(max)) {
287
+ input.stepUp()
288
+ }
289
+ } else {
290
+ input.stepUp()
291
+ }
264
292
  trigger(input, 'change')
265
293
  })
266
294
 
@@ -297,10 +325,13 @@ export class AbstractTheme {
297
325
 
298
326
  }
299
327
 
300
- getFormControl (label, input, description, infoText) {
328
+ getFormControl (label, input, description, infoText, formName) {
301
329
  const el = document.createElement('div')
302
330
  el.classList.add('form-control')
303
- if (label) el.appendChild(label)
331
+ if (label) {
332
+ el.appendChild(label)
333
+ if (formName) label.setAttribute('for', formName)
334
+ }
304
335
  if ((input.type === 'checkbox' || input.type === 'radio') && label) {
305
336
  input.style.width = 'auto'
306
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
 
@@ -123,13 +123,47 @@ export class bootstrap4Theme extends AbstractTheme {
123
123
  minusBtn.textContent = '-'
124
124
  plusBtn.textContent = '+'
125
125
 
126
+ const initialize = (input, min) => {
127
+ if (min) {
128
+ input.value = Number(min)
129
+ } else {
130
+ input.value = Number(input.value)
131
+ }
132
+ input.setAttribute('initialized', '1')
133
+ }
134
+
135
+ const min = input.getAttribute('min')
136
+ const max = input.getAttribute('max')
137
+
138
+ input.addEventListener('change', () => {
139
+ if (!input.getAttribute('initialized')) {
140
+ input.setAttribute('initialized', '1')
141
+ }
142
+ })
143
+
126
144
  minusBtn.addEventListener('click', () => {
127
- input.stepDown()
145
+ if (!input.getAttribute('initialized')) {
146
+ initialize(input, min)
147
+ } else if (min) {
148
+ if (Number(input.value) > Number(min)) {
149
+ input.stepDown()
150
+ }
151
+ } else {
152
+ input.stepDown()
153
+ }
128
154
  trigger(input, 'change')
129
155
  })
130
156
 
131
157
  plusBtn.addEventListener('click', () => {
132
- input.stepUp()
158
+ if (!input.getAttribute('initialized')) {
159
+ initialize(input, min)
160
+ } else if (max) {
161
+ if (Number(input.value) < Number(max)) {
162
+ input.stepUp()
163
+ }
164
+ } else {
165
+ input.stepUp()
166
+ }
133
167
  trigger(input, 'change')
134
168
  })
135
169
 
@@ -218,6 +252,7 @@ export class bootstrap4Theme extends AbstractTheme {
218
252
  if (window.jQuery && window.jQuery().tooltip) {
219
253
  window.jQuery(button).tooltip()
220
254
  } else {
255
+ // eslint-disable-next-line no-console
221
256
  console.warn('Could not find popper jQuery plugin of Bootstrap.')
222
257
  }
223
258
  } else if (this.options.tooltip === 'css') {
@@ -370,12 +405,13 @@ export class bootstrap4Theme extends AbstractTheme {
370
405
  return el
371
406
  }
372
407
 
373
- getHeader (text) {
408
+ getHeader (text, pathDepth) {
374
409
  /* var cardHeader = document.createElement('div') */
375
410
  /* cardHeader.classList.add('card-header') */
376
411
 
377
412
  const el = document.createElement('h3')
378
413
  el.classList.add('card-title')
414
+ el.classList.add('level-' + pathDepth)
379
415
 
380
416
  if (typeof text === 'string') {
381
417
  el.textContent = text
@@ -447,7 +483,7 @@ export class bootstrap4Theme extends AbstractTheme {
447
483
  addInputError (input, text) {
448
484
  if (!input.controlgroup) return
449
485
 
450
- input.classList.add('is-invalid')
486
+ input.controlgroup.classList.add('is-invalid')
451
487
 
452
488
  if (!input.errmsg) {
453
489
  input.errmsg = document.createElement('p')
@@ -463,7 +499,7 @@ export class bootstrap4Theme extends AbstractTheme {
463
499
  removeInputError (input) {
464
500
  if (!input.errmsg) return
465
501
  input.errmsg.style.display = 'none'
466
- input.classList.remove('is-invalid')
502
+ input.controlgroup.classList.remove('is-invalid')
467
503
  }
468
504
 
469
505
  getTabHolder (propertyName) {