@json-editor/json-editor 2.5.4 → 2.7.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 (91) hide show
  1. package/.github/workflows/build.yml +12 -3
  2. package/CHANGELOG.md +51 -14
  3. package/README.md +159 -20
  4. package/dist/jsoneditor.js +2 -2
  5. package/dist/nonmin/jsoneditor.js +5483 -3610
  6. package/dist/nonmin/jsoneditor.js.map +1 -1
  7. package/docs/cleave.html +1 -1
  8. package/docs/datetime.html +1 -1
  9. package/docs/describedby.html +1 -1
  10. package/docs/form-submission.html +76 -0
  11. package/docs/index.html +2 -2
  12. package/docs/materialize_css.html +1 -1
  13. package/docs/meta_schema.json +0 -1
  14. package/docs/radio.html +1 -1
  15. package/docs/select2.html +1 -1
  16. package/docs/selectize.html +1 -1
  17. package/docs/starrating.html +1 -1
  18. package/docs/wysiwyg.html +1 -1
  19. package/package.json +28 -26
  20. package/release-notes.md +5 -3
  21. package/src/core.js +36 -37
  22. package/src/defaults.js +9 -2
  23. package/src/editor.js +6 -2
  24. package/src/editors/array.js +12 -1
  25. package/src/editors/autocomplete.js +4 -3
  26. package/src/editors/button.js +1 -1
  27. package/src/editors/multiple.js +2 -0
  28. package/src/editors/multiselect.js +14 -5
  29. package/src/editors/object.js +10 -6
  30. package/src/editors/radio.js +6 -1
  31. package/src/editors/string.js +7 -1
  32. package/src/editors/table.js +21 -2
  33. package/src/editors/upload.js +1 -1
  34. package/src/editors/uuid.js +2 -12
  35. package/src/iconlib.js +1 -1
  36. package/src/schemaloader.js +232 -109
  37. package/src/style.css +3 -0
  38. package/src/style.css.js +1 -1
  39. package/src/theme.js +5 -4
  40. package/src/themes/bootstrap3.js +3 -2
  41. package/src/themes/bootstrap4.js +7 -0
  42. package/src/themes/spectre.js +2 -1
  43. package/src/utilities.js +18 -0
  44. package/src/validator.js +65 -47
  45. package/tests/codeceptjs/codecept.json +1 -1
  46. package/tests/codeceptjs/core_test.js +98 -0
  47. package/tests/codeceptjs/editors/advanced_test.js +1 -1
  48. package/tests/codeceptjs/editors/array_test.js +74 -0
  49. package/tests/codeceptjs/editors/autocomplete_test.js +16 -0
  50. package/tests/codeceptjs/editors/button_test.js +11 -4
  51. package/tests/codeceptjs/editors/integer_test.js +7 -2
  52. package/tests/codeceptjs/editors/jodit_test.js +3 -3
  53. package/tests/codeceptjs/editors/object_test.js +84 -13
  54. package/tests/codeceptjs/editors/range_test.js +12 -0
  55. package/tests/codeceptjs/editors/stepper_test.js +12 -0
  56. package/tests/codeceptjs/editors/uuid_test.js +31 -4
  57. package/tests/codeceptjs/editors/validation_test.js +1 -1
  58. package/tests/docker-compose.yml +1 -1
  59. package/tests/fixtures/definitions.json +22 -0
  60. package/tests/fixtures/properties.json +20 -0
  61. package/tests/fixtures/validation.json +207 -0
  62. package/tests/pages/array-checkboxes-infotext.html +52 -0
  63. package/tests/pages/array-move-events.html +4 -2
  64. package/tests/pages/array-unique-items-sort.html +78 -0
  65. package/tests/pages/autocomplete.html +69 -0
  66. package/tests/pages/button-icons.html +38 -0
  67. package/tests/pages/core.html +4 -2
  68. package/tests/pages/error-messages.html +47 -0
  69. package/tests/pages/grid-strict.html +6 -10
  70. package/tests/pages/grid.html +0 -4
  71. package/tests/pages/issues/issue-gh-812.html +4 -2
  72. package/tests/pages/meta_schema.json +14 -1
  73. package/tests/pages/object-required-properties.html +37 -14
  74. package/tests/pages/object-show-opt-in.html +110 -0
  75. package/tests/pages/object-with-dependencies-array.html +29 -19
  76. package/tests/pages/range.html +60 -0
  77. package/tests/pages/ready.html +43 -0
  78. package/tests/pages/references.html +162 -0
  79. package/tests/pages/stepper-manual.html +57 -0
  80. package/tests/pages/string-simplemde-editor.html +81 -0
  81. package/tests/pages/table-move-events.html +4 -1
  82. package/tests/pages/urn.html +11 -8
  83. package/tests/pages/uuid.html +89 -50
  84. package/tests/pages/validation-messages.json +705 -0
  85. package/tests/unit/core.spec.js +79 -66
  86. package/tests/unit/editor.spec.js +20 -8
  87. package/tests/unit/editors/array.spec.js +3 -2
  88. package/tests/unit/editors/object.spec.js +3 -1
  89. package/tests/unit/editors/table.spec.js +4 -2
  90. package/tests/unit/schemaloader.spec.js +77 -105
  91. package/tests/unit/validator.spec.js +10 -0
@@ -2,7 +2,6 @@ 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
 
@@ -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({
@@ -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) {
@@ -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 */
@@ -211,6 +213,10 @@ export class StringEditor extends AbstractEditor {
211
213
  /* it will generate an error trying to append it to the missing parentNode */
212
214
  if (this.input.parentNode) this.afterInputReady()
213
215
  if (this.adjust_height) this.adjust_height(this.input)
216
+ if (this.format === 'range') {
217
+ const output = this.control.querySelector('output')
218
+ output.value = this.input.value
219
+ }
214
220
  })
215
221
 
216
222
  /* Compile and store the template */
@@ -1,5 +1,5 @@
1
1
  import { ArrayEditor } from './array.js'
2
- import { extend, trigger } from '../utilities.js'
2
+ import { extend, generateUUID, trigger } from '../utilities.js'
3
3
 
4
4
  export class TableEditor extends ArrayEditor {
5
5
  register () {
@@ -99,6 +99,7 @@ export class TableEditor extends ArrayEditor {
99
99
 
100
100
  /* Row Controls column */
101
101
  this.controls_header_cell = this.theme.getTableHeaderCell(' ')
102
+ this.controls_header_cell.setAttribute('aria-hidden', 'true')
102
103
  this.header_row.appendChild(this.controls_header_cell)
103
104
 
104
105
  /* Add controls */
@@ -346,6 +347,7 @@ export class TableEditor extends ArrayEditor {
346
347
 
347
348
  _createCopyButton (i, holder) {
348
349
  const button = this.getButton('', 'copy', 'button_copy_row_title_short')
350
+ const schema = this.schema
349
351
  button.classList.add('copy', 'json-editor-btntype-copy')
350
352
  button.setAttribute('data-i', i)
351
353
  button.addEventListener('click', e => {
@@ -354,8 +356,25 @@ export class TableEditor extends ArrayEditor {
354
356
  const j = e.currentTarget.getAttribute('data-i') * 1
355
357
  const value = this.getValue()
356
358
 
357
- value.splice(j + 1, 0, value[j])
359
+ let newValue = value[j]
360
+
361
+ /* On copy, recreate uuid if needed. */
362
+ if (schema.items.type === 'string' && schema.items.format === 'uuid') {
363
+ newValue = generateUUID()
364
+ } else if (schema.items.type === 'object' && schema.items.properties) {
365
+ value.forEach((row, i) => {
366
+ if (j === i) {
367
+ for (const key of Object.keys(row)) {
368
+ if (schema.items.properties && schema.items.properties[key] && schema.items.properties[key].format === 'uuid') {
369
+ newValue = Object.assign({}, value[j])
370
+ newValue[key] = generateUUID()
371
+ }
372
+ }
373
+ }
374
+ })
375
+ }
358
376
 
377
+ value.splice(j + 1, 0, newValue)
359
378
  this.setValue(value)
360
379
  this.onChange(true)
361
380
  this.jsoneditor.trigger('copyRow', this.rows[j + 1])
@@ -257,7 +257,7 @@ export class UploadEditor extends AbstractEditor {
257
257
 
258
258
  if (this.options.auto_upload) {
259
259
  uploadButton.dispatchEvent(new window.MouseEvent('click'))
260
- this.preview.removeChild(uploadButton)
260
+ uploadButton.parentNode.removeChild(uploadButton)
261
261
  }
262
262
  }
263
263
 
@@ -1,3 +1,4 @@
1
+ import { generateUUID } from '../utilities.js'
1
2
  import { StringEditor } from './string.js'
2
3
 
3
4
  export class UuidEditor extends StringEditor {
@@ -36,18 +37,7 @@ export class UuidEditor extends StringEditor {
36
37
  }
37
38
 
38
39
  getUuid () {
39
- /* https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript */
40
- let d = new Date().getTime()
41
-
42
- if (typeof performance !== 'undefined' && typeof performance.now === 'function') {
43
- d += performance.now() /* use high-precision timer if available */
44
- }
45
-
46
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
47
- const r = (d + Math.random() * 16) % 16 | 0
48
- d = Math.floor(d / 16)
49
- return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16)
50
- })
40
+ return generateUUID()
51
41
  }
52
42
 
53
43
  testUuid (value) {
package/src/iconlib.js CHANGED
@@ -8,7 +8,7 @@ export class AbstractIconLib {
8
8
  }
9
9
 
10
10
  getIconClass (key) {
11
- return this.mapping[key] ? this.icon_prefix + this.mapping[key] : null
11
+ return this.mapping[key] ? this.icon_prefix + this.mapping[key] : this.icon_prefix + key
12
12
  }
13
13
 
14
14
  getIcon (key) {
@@ -1,11 +1,52 @@
1
1
  import { extend, hasOwnProperty } from './utilities.js'
2
2
 
3
+ /**
4
+ * Handles loading Schema and tracking references.
5
+ */
3
6
  export class SchemaLoader {
4
7
  constructor (options) {
8
+ /**
9
+ * @prop {object}
10
+ * Options of the schema. @see readme.
11
+ */
5
12
  this.options = options || {}
13
+
14
+ /**
15
+ * @prop {object}
16
+ * The orginial schema to load
17
+ */
18
+ this.schema = {}
19
+
20
+ /**
21
+ * @prop {object}
22
+ * Storage of External ref. Exemple :
23
+ * refs = {
24
+ * "fully/realized/path/to/schema.json": { ... }
25
+ * "mylocalschema.json": { ... }
26
+ * }
27
+ */
6
28
  this.refs = this.options.refs || {}
29
+
30
+ /**
31
+ * @prop {object}
32
+ * Mapping between the schema and ref. Exemple:
33
+ * refs_with_info = {
34
+ * "#/counter/1": "fully/realized/path/to/schema.json"
35
+ * "#/counter/2": "mylocalschema.json"
36
+ * }
37
+ */
7
38
  this.refs_with_info = {}
39
+
40
+ /**
41
+ * @prop {string}
42
+ * String to eewrite external ref with.
43
+ */
8
44
  this.refs_prefix = '#/counter/'
45
+
46
+ /**
47
+ * @prop {int}
48
+ * Counter of ref
49
+ */
9
50
  this.refs_counter = 1
10
51
 
11
52
  this._subSchema1 = {
@@ -80,23 +121,54 @@ export class SchemaLoader {
80
121
  }
81
122
  }
82
123
 
83
- load (schema, callback, fetchUrl, location) {
84
- this._loadExternalRefs(schema, () => {
85
- this._getDefinitions(schema, `${fetchUrl}#/definitions/`)
86
- callback(this.expandRefs(schema))
87
- }, fetchUrl, this._getFileBase(location))
124
+ /**
125
+ * Fully loads and expands JSON schema for a provided schema object and URL.
126
+ *
127
+ * The callback receives a expanded JSON Schema object with references
128
+ * replaced with loaded schemas.
129
+ *
130
+ * @param {object} schema - A JSON Schema.
131
+ * @param {string} fetchUrl - Base path from which to store the definitions.
132
+ * Typically the URI of the schema.
133
+ * @param {*} location - The base URL from which to load relative paths.
134
+ * @returns {object} A JSON Schema with references expanded.
135
+ */
136
+ async load (schema, fetchUrl, location) {
137
+ this.schema = schema
138
+ await this._asyncloadExternalRefs(schema, fetchUrl, this._getFileBase(location), true)
139
+ return this.expandRefs(schema)
88
140
  }
89
141
 
142
+ /**
143
+ * Recursively expands loaded references in a provided schema.
144
+ *
145
+ * @param {object} schema - A JSON Schema with references already loaded.
146
+ * @param {boolean} recurseAllOf - Set true to recurse allOf properties.
147
+ * @returns {object} A JSON Schema with references expanded.
148
+ */
90
149
  expandRefs (schema, recurseAllOf) {
91
150
  const _schema = extend({}, schema)
92
- if (!_schema.$ref) return _schema
93
151
 
94
- const refObj = this.refs_with_info[_schema.$ref]
152
+ if (!_schema.$ref) return _schema
153
+ // This split the ref to get the Json point if it exists
154
+ // exemple #/counter/1#/definition/address +
155
+ // [1] -> /counter/1
156
+ // [2] -> /definition/address
157
+ const refWithPointerSplit = _schema.$ref.split('#')
158
+ // If local ref
159
+ if (refWithPointerSplit.length === 2 && !this.refs_with_info[_schema.$ref]) {
160
+ const sub = this.expandRecursivePointer(this.schema, refWithPointerSplit[1])
161
+ return this.extendSchemas(_schema, this.expandSchema(sub))
162
+ }
163
+ const refObj = (refWithPointerSplit.length > 2)
164
+ ? this.refs_with_info['#' + refWithPointerSplit[1]]
165
+ : this.refs_with_info[_schema.$ref]
95
166
  delete _schema.$ref
96
167
  const fetchUrl = refObj.$ref.startsWith('#')
97
168
  ? refObj.fetchUrl
98
169
  : ''
99
170
  const ref = this._getRef(fetchUrl, refObj)
171
+
100
172
  if (!this.refs[ref]) { /* if reference not found */
101
173
  // eslint-disable-next-line no-console
102
174
  console.warn(`reference:'${ref}' not found!`)
@@ -106,11 +178,41 @@ export class SchemaLoader {
106
178
  allOf[key] = this.expandRefs(allOf[key], true)
107
179
  })
108
180
  }
181
+ if (refWithPointerSplit.length > 2) {
182
+ return this.extendSchemas(_schema, this.expandSchema(this.expandRecursivePointer(this.refs[ref], refWithPointerSplit[2])))
183
+ } else {
184
+ return this.extendSchemas(_schema, this.expandSchema(this.refs[ref]))
185
+ }
186
+ }
109
187
 
110
- return this.extendSchemas(_schema, this.expandSchema(this.refs[ref]))
188
+ /**
189
+ * Returns a subschema based on a JSON Pointer path.
190
+ * @param {object} schema - Schema too into
191
+ * @param {string} pointer - path to look for
192
+ * @param {object} original_schema - the Original schema
193
+ * @returns the subschema pointed to by the path
194
+ */
195
+ expandRecursivePointer (schema, pointer) {
196
+ let subschema = schema
197
+ pointer.split('/').slice(1).forEach(i => {
198
+ if (subschema[i]) {
199
+ subschema = subschema[i]
200
+ }
201
+ })
202
+ // If the result is a pointer, let's go for another turn
203
+ if (subschema.$refs && subschema.$refs.startsWith('#')) {
204
+ return this.expandRecursivePointer(schema, subschema.$refs)
205
+ }
206
+ return subschema
111
207
  }
112
208
 
113
- expandSchema (schema, fileBase) {
209
+ /**
210
+ * Expands a JSON schema and its references.
211
+ *
212
+ * @param {object} schema - A JSON Schema with references already loaded.
213
+ * @returns {object} A JSON Schema with references expanded.
214
+ */
215
+ expandSchema (schema) {
114
216
  Object.entries(this._subSchema1).forEach(([key, func]) => {
115
217
  if (schema[key]) {
116
218
  func.call(this, schema)
@@ -136,34 +238,59 @@ export class SchemaLoader {
136
238
 
137
239
  _expandSubSchema (subschema) {
138
240
  /* Array of types */
139
- if (Array.isArray(subschema)) return subschema.map(m => typeof value === 'object' ? this.expandSchema(m) : m)
241
+ if (Array.isArray(subschema)) return subschema.map(m => typeof m === 'object' ? this.expandSchema(m) : m)
140
242
 
141
243
  /* Schema */
142
244
  return this.expandSchema(subschema)
143
245
  }
144
246
 
145
- _getDefinitions (schema, path) {
146
- if (schema.definitions) {
147
- Object.keys(schema.definitions).forEach(i => {
148
- this.refs[path + i] = schema.definitions[i]
149
- if (schema.definitions[i].definitions) {
150
- this._getDefinitions(schema.definitions[i], `${path + i}/definitions/`)
151
- }
152
- })
153
- }
247
+ /**
248
+ * Rewrite the passed schema's JSON pointers to prepend with the current reference's path, so that it will be converted to a reference "counter".
249
+ *
250
+ * @example
251
+ * In file "../otherreferencedfile.json", referenced from "schema.json":
252
+ *
253
+ * "$ref": "#/definitions/myschema" => "$ref": "../path/to/my/referenced/schemafile.json#/definitions/myschema"
254
+ * ...which will then be parsed later in loadExternalReferences() to "$ref": "#/counter/1#/definitions/myschema"
255
+ *
256
+ * @param {object} schema - A JSON Schema with the definitions key present.
257
+ * @param {string} path - Base path from which to store the definitions in refs. (exemple ../path/to/my/referenced/schemafile.json)
258
+ * Typically the URI of the schema.
259
+ */
260
+ _manageRecursivePointer (schema, path) {
261
+ Object.keys(schema).forEach(i => {
262
+ if (schema[i].$ref && schema[i].$ref.indexOf('#') === 0) {
263
+ schema[i].$ref = path + schema[i].$ref
264
+ }
265
+ })
154
266
  }
155
267
 
156
- _getExternalRefs (schema, fetchUrl) {
268
+ /**
269
+ * Recursively parse a (sub)schema to populate loader reference info.
270
+ *
271
+ * @param {object} schema - A JSON Schema
272
+ * @param {string} fetchUrl - Base path from which to store the definitions.
273
+ * @param {boolean} firstIteration - Is it the first time we load this function? Help making difference between external ref vs internal pointer
274
+ * @returns {array} Refs in the format of uri => true if external.
275
+ */
276
+ _getExternalRefs (schema, fetchUrl, firstIteration = false) {
277
+ if (!firstIteration) this._manageRecursivePointer(schema, fetchUrl)
157
278
  const refs = {}
158
279
  const mergeRefs = newrefs => Object.keys(newrefs).forEach(i => { refs[i] = true })
159
-
160
- if (schema.$ref && typeof schema.$ref !== 'object') {
280
+ if (schema.$ref && typeof schema.$ref !== 'object' && !(schema.$ref.indexOf('#') === 0 && firstIteration)) {
281
+ let refBase = schema.$ref
282
+ let pointer = ''
283
+ // Strip any JSON pointers found for external refs.
284
+ if (refBase.indexOf('#') > 0) refBase = refBase.substr(0, refBase.indexOf('#'))
285
+ if (refBase !== schema.$ref) pointer = schema.$ref.substr(schema.$ref.indexOf('#'))
286
+ // We use a fragment idenfier to track json pointer in top of our pointer
161
287
  const refCounter = this.refs_prefix + this.refs_counter++
288
+ const refPointer = refCounter + pointer
162
289
  if (schema.$ref.substr(0, 1) !== '#' && !this.refs[schema.$ref]) {
163
- refs[schema.$ref] = true
290
+ refs[refBase] = true
164
291
  }
165
- this.refs_with_info[refCounter] = { fetchUrl, $ref: schema.$ref }
166
- schema.$ref = refCounter
292
+ this.refs_with_info[refCounter] = { fetchUrl, $ref: refBase }
293
+ schema.$ref = refPointer
167
294
  }
168
295
 
169
296
  Object.values(schema).forEach(value => {
@@ -171,11 +298,14 @@ export class SchemaLoader {
171
298
  if (Array.isArray(value)) {
172
299
  Object.values(value).forEach(e => {
173
300
  if (e && typeof e === 'object') {
174
- mergeRefs(this._getExternalRefs(e, fetchUrl))
301
+ mergeRefs(this._getExternalRefs(e, fetchUrl, firstIteration))
175
302
  }
176
303
  })
177
304
  } else {
178
- mergeRefs(this._getExternalRefs(value, fetchUrl))
305
+ // Merge Ref if it's not a Pointer
306
+ if (!value.$ref || !(typeof value.$ref === 'string' && value.$ref.startsWith('#'))) {
307
+ mergeRefs(this._getExternalRefs(value, fetchUrl, firstIteration))
308
+ }
179
309
  }
180
310
  })
181
311
 
@@ -184,7 +314,6 @@ export class SchemaLoader {
184
314
  } else if (schema.$id && typeof schema.$id === 'string' && schema.$id.substr(0, 4) === 'urn:') {
185
315
  this.refs[schema.$id] = schema
186
316
  }
187
-
188
317
  return refs
189
318
  }
190
319
 
@@ -225,17 +354,31 @@ export class SchemaLoader {
225
354
  return uri.substr(0, 4) === 'urn:'
226
355
  }
227
356
 
228
- _loadExternalRefs (schema, callback, fetchUrl, fileBase) {
229
- const refs = this._getExternalRefs(schema, fetchUrl)
230
- let done = false; let waiting = 0
231
-
232
- Object.keys(refs).forEach(uri => {
233
- if (this.refs[uri]) return
234
-
357
+ /**
358
+ * Loads external references via AJAX.
359
+ *
360
+ * Will fail if this.options.ajax is not set to true.
361
+ *
362
+ * @param {object} schema - JSON Schema with external references.
363
+ * @param {string} fetchUrl - Base path from which to store the definitions.
364
+ * Typically the URI of the schema.
365
+ * @param {string} fileBase - The base URL from which to load relative paths.
366
+ * Typically the URI of the schema minus filename, with trailing slash.
367
+ * @param {boolean} firstIteration - Is it the first time we load this function? Help making difference between external ref vs internal pointer
368
+ *
369
+ * @return {boolean}
370
+ * @throws Error
371
+ */
372
+ async _asyncloadExternalRefs (schema, fetchUrl, fileBase, firstIteration = false) {
373
+ const refs = this._getExternalRefs(schema, fetchUrl, firstIteration)
374
+ let waiting = 0
375
+ // Loop into all schema references
376
+ for (const uri of Object.keys(refs)) {
377
+ if (typeof uri === 'undefined') continue
378
+ if (this.refs[uri]) continue
235
379
  if (this._isUniformResourceName(uri)) {
236
380
  this.refs[uri] = 'loading'
237
381
  waiting++
238
-
239
382
  const urnResolver = this.options.urn_resolver
240
383
  let urn = uri
241
384
  if (typeof urnResolver !== 'function') {
@@ -247,95 +390,75 @@ export class SchemaLoader {
247
390
  if (urn.indexOf('#') > 0) urn = urn.substr(0, urn.indexOf('#'))
248
391
  let response
249
392
  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
- })
393
+ let externalSchema
394
+ response = await urnResolver(urn)
395
+ try {
396
+ externalSchema = JSON.parse(response)
397
+ } catch (e) {
398
+ // eslint-disable-next-line no-console
399
+ console.log(e)
400
+ throw new Error(`Failed to parse external ref ${urn}`)
401
+ }
402
+ if (!(typeof externalSchema === 'boolean' || typeof externalSchema === 'object') || externalSchema === null || Array.isArray(externalSchema)) {
403
+ throw new Error(`External ref does not contain a valid schema - ${urn}`)
404
+ }
405
+
406
+ this.refs[uri] = externalSchema
407
+
408
+ await this._asyncloadExternalRefs(externalSchema, uri, fileBase)
270
409
  } catch (e) {
271
410
  // eslint-disable-next-line no-console
272
411
  console.log(e)
273
412
  throw new Error(`Failed to parse external ref ${urn}`)
274
413
  }
275
414
 
276
- if (typeof response !== 'boolean') {
415
+ if (typeof response === 'boolean') {
277
416
  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
417
  }
281
-
282
- return
418
+ continue
283
419
  }
284
-
285
420
  if (!this.options.ajax) throw new Error(`Must set ajax option to true to load external ref ${uri}`)
286
- this.refs[uri] = 'loading'
287
421
  waiting++
288
- let url = this._joinUrl(uri, fileBase)
289
-
290
- const r = new XMLHttpRequest()
291
- r.overrideMimeType('application/json')
292
- r.open('GET', url, true)
293
- if (this.options.ajaxCredentials) r.withCredentials = this.options.ajaxCredentials
294
- r.onreadystatechange = () => {
295
- if (r.readyState !== 4) return
296
- /* Request succeeded */
297
- if (r.status === 200) {
298
- let schema
299
- try {
300
- schema = JSON.parse(r.responseText)
301
- } catch (e) {
302
- // eslint-disable-next-line no-console
303
- console.log(e)
304
- throw new Error(`Failed to parse external ref ${url}`)
305
- }
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
422
 
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()
317
- }
318
-
319
- this._getDefinitions(schema, `${url}#/definitions/`)
320
- this._loadExternalRefs(schema, () => {
321
- waiting--
322
- if (done && !waiting) {
323
- callback()
324
- }
325
- }, url, fileBase)
326
- } else {
327
- /* Request failed */
328
- // eslint-disable-next-line no-console
329
- console.log(r)
330
- throw new Error(`Failed to fetch ref via ajax - ${uri}`)
423
+ let url = this._joinUrl(uri, fileBase)
424
+ const response = await new Promise(resolve => {
425
+ const r = new XMLHttpRequest()
426
+ if (this.options.ajaxCredentials) r.withCredentials = this.options.ajaxCredentials
427
+ r.overrideMimeType('application/json')
428
+ r.open('GET', url, true)
429
+ r.onload = () => {
430
+ resolve(r)
431
+ }
432
+ r.onerror = (e) => {
433
+ resolve(undefined)
331
434
  }
435
+ r.send()
436
+ })
437
+ if (typeof response === 'undefined') throw new Error(`Failed to fetch ref via ajax - ${uri}`)
438
+ let externalSchema
439
+ try {
440
+ externalSchema = JSON.parse(response.responseText)
441
+ } catch (e) {
442
+ // eslint-disable-next-line no-console
443
+ console.log(e)
444
+ throw new Error(`Failed to parse external ref ${url}`)
332
445
  }
333
- r.send()
334
- })
335
446
 
336
- done = true
447
+ if (!(typeof externalSchema === 'boolean' || typeof externalSchema === 'object') || externalSchema === null || Array.isArray(externalSchema)) {
448
+ throw new Error(`External ref does not contain a valid schema - ${url}`)
449
+ }
450
+ this.refs[uri] = externalSchema
451
+ const newfileBase = this._getFileBaseFromFileLocation(url)
452
+
453
+ // Add leading slash.
454
+ if (url !== uri) {
455
+ const pathItems = url.split('/')
456
+ url = (uri.substr(0, 1) === '/' ? '/' : '') + pathItems.pop()
457
+ }
458
+ await this._asyncloadExternalRefs(externalSchema, url, newfileBase)
459
+ }
337
460
  if (!waiting) {
338
- callback()
461
+ return true
339
462
  }
340
463
  }
341
464
 
package/src/style.css CHANGED
@@ -33,6 +33,9 @@
33
33
  position: absolute;
34
34
  }
35
35
 
36
+ .je-not-loaded {
37
+ pointer-events: none;
38
+ }
36
39
  .je-header {
37
40
  display: inline-block
38
41
  }
package/src/style.css.js CHANGED
@@ -1,3 +1,3 @@
1
1
  /* eslint-disable */
2
- export default {".je-float-right-linkholder":"float:right;margin-left:10px",".je-modal":"background-color:white;border:1px%20solid%20black;box-shadow:3px%203px%20black;position:absolute;z-index:10",".je-infobutton-icon":"font-size:16px;font-weight:bold;padding:0.25rem;position:relative;display:inline-block",".je-infobutton-tooltip":"font-size:12px;font-weight:normal;font-family:sans-serif;visibility:hidden;background-color:rgba(50%2C%2050%2C%2050%2C%200.75);margin:0%200.25rem;color:%23fafafa;padding:0.5rem%201rem;border-radius:0.25rem;width:20rem;position:absolute",".je-header":"display:inline-block",".je-upload-preview img":"float:left;margin:0%200.5rem%200.5rem%200;max-width:100%25;max-height:5rem",".je-checkbox":"display:inline-block;width:auto",".je-checkbox-control--compact":"display:inline-block;margin-right:1rem",".je-radio":"display:inline-block;width:auto",".je-radio-control--compact":"display:inline-block;margin-right:1rem",".je-switcher":"background-color:transparent;display:inline-block;font-style:italic;font-weight:normal;height:auto;width:auto;margin-bottom:0;margin-left:5px;padding:0%200%200%203px",".je-textarea":"width:100%25;height:300px;box-sizing:border-box",".je-range-control":"text-align:center",".je-indented-panel":"padding-left:10px;margin-left:10px;border-left:1px%20solid%20%23ccc",".je-indented-panel--top":"padding-left:10px;margin-left:10px",".je-tabholder":"float:left;width:130px",".je-tabholder .content":"margin-left:120px",".je-tabholder--top":"margin-left:10px",".je-tabholder--clear":"clear:both",".je-tab":"border:1px%20solid%20%23ccc;border-width:1px%200%201px%201px;text-align:center;line-height:30px;border-radius:5px;border-bottom-right-radius:0;border-top-right-radius:0;font-weight:bold;cursor:pointer",".je-tab--top":"float:left;border:1px%20solid%20%23ccc;border-width:1px%201px%200px%201px;text-align:center;line-height:30px;border-radius:5px;padding-left:5px;padding-right:5px;border-bottom-right-radius:0;border-bottom-left-radius:0;font-weight:bold;cursor:pointer",".je-block-link":"display:block",".je-media":"width:100%25"}
2
+ export default {".je-float-right-linkholder":"float:right;margin-left:10px",".je-modal":"background-color:white;border:1px%20solid%20black;box-shadow:3px%203px%20black;position:absolute;z-index:10",".je-infobutton-icon":"font-size:16px;font-weight:bold;padding:0.25rem;position:relative;display:inline-block",".je-infobutton-tooltip":"font-size:12px;font-weight:normal;font-family:sans-serif;visibility:hidden;background-color:rgba(50%2C%2050%2C%2050%2C%200.75);margin:0%200.25rem;color:%23fafafa;padding:0.5rem%201rem;border-radius:0.25rem;width:20rem;position:absolute",".je-not-loaded":"pointer-events:none",".je-header":"display:inline-block",".je-upload-preview img":"float:left;margin:0%200.5rem%200.5rem%200;max-width:100%25;max-height:5rem",".je-checkbox":"display:inline-block;width:auto",".je-checkbox-control--compact":"display:inline-block;margin-right:1rem",".je-radio":"display:inline-block;width:auto",".je-radio-control--compact":"display:inline-block;margin-right:1rem",".je-switcher":"background-color:transparent;display:inline-block;font-style:italic;font-weight:normal;height:auto;width:auto;margin-bottom:0;margin-left:5px;padding:0%200%200%203px",".je-textarea":"width:100%25;height:300px;box-sizing:border-box",".je-range-control":"text-align:center",".je-indented-panel":"padding-left:10px;margin-left:10px;border-left:1px%20solid%20%23ccc",".je-indented-panel--top":"padding-left:10px;margin-left:10px",".je-tabholder":"float:left;width:130px",".je-tabholder .content":"margin-left:120px",".je-tabholder--top":"margin-left:10px",".je-tabholder--clear":"clear:both",".je-tab":"border:1px%20solid%20%23ccc;border-width:1px%200%201px%201px;text-align:center;line-height:30px;border-radius:5px;border-bottom-right-radius:0;border-top-right-radius:0;font-weight:bold;cursor:pointer",".je-tab--top":"float:left;border:1px%20solid%20%23ccc;border-width:1px%201px%200px%201px;text-align:center;line-height:30px;border-radius:5px;padding-left:5px;padding-right:5px;border-bottom-right-radius:0;border-bottom-left-radius:0;font-weight:bold;cursor:pointer",".je-block-link":"display:block",".je-media":"width:100%25"}
3
3
  /* eslint-enable */
package/src/theme.js CHANGED
@@ -299,8 +299,6 @@ export class AbstractTheme {
299
299
 
300
300
  getRangeOutput (input, startvalue) {
301
301
  const output = document.createElement('output')
302
- output.value = startvalue || 0
303
-
304
302
  const updateOutput = e => { output.value = e.currentTarget.value }
305
303
  input.addEventListener('change', updateOutput, false)
306
304
  input.addEventListener('input', updateOutput, false)
@@ -325,10 +323,13 @@ export class AbstractTheme {
325
323
 
326
324
  }
327
325
 
328
- getFormControl (label, input, description, infoText) {
326
+ getFormControl (label, input, description, infoText, formName) {
329
327
  const el = document.createElement('div')
330
328
  el.classList.add('form-control')
331
- if (label) el.appendChild(label)
329
+ if (label) {
330
+ el.appendChild(label)
331
+ if (formName) label.setAttribute('for', formName)
332
+ }
332
333
  if ((input.type === 'checkbox' || input.type === 'radio') && label) {
333
334
  input.style.width = 'auto'
334
335
  label.insertBefore(input, label.firstChild)