@json-editor/json-editor 1.3.5 → 1.17.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 (436) hide show
  1. package/.claude/settings.local.json +8 -0
  2. package/.env +2 -0
  3. package/.env-dist +2 -0
  4. package/.eslintrc +10 -0
  5. package/.github/PULL_REQUEST_TEMPLATE.md +9 -0
  6. package/.github/workflows/build.yml +70 -0
  7. package/.travis.yml +63 -9
  8. package/CHANGELOG.md +1152 -0
  9. package/CONTRIBUTING.md +44 -5
  10. package/Makefile +26 -0
  11. package/README.md +765 -138
  12. package/README_ADDON.md +577 -0
  13. package/UPGRADING.md +46 -0
  14. package/build/CssToJson.js +55 -0
  15. package/codecept.conf.js +35 -0
  16. package/config/.eslintrc +7 -0
  17. package/config/codeceptjs_helpers.js +146 -0
  18. package/config/helpers.js +10 -0
  19. package/config/karma.conf.js +89 -0
  20. package/config/readme.md +31 -0
  21. package/config/webpack.common.js +71 -0
  22. package/config/webpack.dev.js +15 -0
  23. package/config/webpack.nonmin.js +19 -0
  24. package/config/webpack.prod.js +24 -0
  25. package/dist/jsoneditor.js +2 -11013
  26. package/dist/jsoneditor.js.LICENSE.txt +15 -0
  27. package/dist/nonmin/jsoneditor.js +31392 -0
  28. package/dist/nonmin/jsoneditor.js.map +1 -0
  29. package/docs/basic.html +2 -2
  30. package/docs/basic_person.json +2 -1
  31. package/docs/choices.html +86 -0
  32. package/docs/cleave.html +11 -19
  33. package/docs/colorpicker.html +194 -0
  34. package/docs/css_integration.html +56 -54
  35. package/docs/custom-editor.html +92 -0
  36. package/docs/datetime.html +48 -21
  37. package/docs/describedby.html +161 -0
  38. package/docs/enumsource.html +67 -0
  39. package/docs/form-submission.html +162 -0
  40. package/docs/imask.html +192 -0
  41. package/docs/index.html +606 -423
  42. package/docs/materialize_css.html +1 -1
  43. package/docs/meta-schema.html +793 -0
  44. package/docs/meta_schema.json +446 -400
  45. package/docs/polyfills/assign.js +29 -0
  46. package/docs/radio.html +156 -0
  47. package/docs/recursive.html +15 -17
  48. package/docs/scripts/ajv-validator.js +8695 -0
  49. package/docs/select2.html +15 -9
  50. package/docs/selectize.html +11 -9
  51. package/docs/signature.html +12 -11
  52. package/docs/starrating.html +5 -17
  53. package/docs/upload.html +33 -23
  54. package/docs/uuid.html +70 -0
  55. package/docs/wysiwyg.html +14 -7
  56. package/jasmine.json +11 -0
  57. package/package.json +74 -25
  58. package/release-notes.md +90 -0
  59. package/src/core.js +411 -611
  60. package/src/defaults.js +385 -306
  61. package/src/editor.js +761 -520
  62. package/src/editors/ace.js +90 -0
  63. package/src/editors/array/choices.js +104 -0
  64. package/src/editors/array/select2.js +112 -0
  65. package/src/editors/array/selectize.js +108 -86
  66. package/src/editors/array.css +9 -0
  67. package/src/editors/array.css.js +3 -0
  68. package/src/editors/array.js +812 -695
  69. package/src/editors/autocomplete.js +59 -0
  70. package/src/editors/base64.js +148 -129
  71. package/src/editors/button.js +104 -0
  72. package/src/editors/checkbox.js +111 -76
  73. package/src/editors/choices.css +3 -0
  74. package/src/editors/choices.css.js +3 -0
  75. package/src/editors/choices.js +71 -0
  76. package/src/editors/colorpicker.js +105 -0
  77. package/src/editors/datetime.js +93 -79
  78. package/src/editors/describedby.js +190 -0
  79. package/src/editors/enum.js +131 -116
  80. package/src/editors/hidden.js +100 -86
  81. package/src/editors/index.js +81 -0
  82. package/src/editors/info.js +28 -0
  83. package/src/editors/integer.js +21 -8
  84. package/src/editors/ip.js +36 -0
  85. package/src/editors/jodit.js +66 -0
  86. package/src/editors/multiple.js +380 -241
  87. package/src/editors/multiselect.js +207 -207
  88. package/src/editors/null.js +15 -11
  89. package/src/editors/number.js +39 -30
  90. package/src/editors/object.css +41 -0
  91. package/src/editors/object.css.js +3 -0
  92. package/src/editors/object.js +1209 -1007
  93. package/src/editors/radio.js +128 -0
  94. package/src/editors/sceditor.js +74 -0
  95. package/src/editors/select.js +318 -342
  96. package/src/editors/select2.js +112 -0
  97. package/src/editors/selectize.js +89 -338
  98. package/src/editors/signature.js +100 -104
  99. package/src/editors/simplemde.js +103 -0
  100. package/src/{styles → editors}/starrating.css +11 -2
  101. package/src/editors/starrating.css.js +3 -0
  102. package/src/editors/starrating.js +126 -95
  103. package/src/editors/stepper.js +27 -0
  104. package/src/editors/string.js +352 -426
  105. package/src/editors/table.js +486 -410
  106. package/src/editors/upload.js +297 -131
  107. package/src/editors/uuid.js +49 -0
  108. package/src/iconlib.js +22 -27
  109. package/src/iconlibs/bootstrap.js +28 -0
  110. package/src/iconlibs/bootstrap2.js +28 -17
  111. package/src/iconlibs/bootstrap3.js +28 -17
  112. package/src/iconlibs/fontawesome3.js +28 -17
  113. package/src/iconlibs/fontawesome4.js +28 -18
  114. package/src/iconlibs/fontawesome5.js +28 -18
  115. package/src/iconlibs/foundation2.js +24 -17
  116. package/src/iconlibs/foundation3.js +28 -17
  117. package/src/iconlibs/index.js +27 -0
  118. package/src/iconlibs/jqueryui.js +28 -17
  119. package/src/iconlibs/materialicons.js +49 -44
  120. package/src/iconlibs/openiconic.js +28 -0
  121. package/src/iconlibs/spectre.js +28 -0
  122. package/src/resolvers.js +135 -0
  123. package/src/schemaloader.js +639 -0
  124. package/src/style.css +157 -0
  125. package/src/style.css.js +3 -0
  126. package/src/templates/default.js +46 -51
  127. package/src/templates/ejs.js +8 -10
  128. package/src/templates/handlebars.js +1 -3
  129. package/src/templates/hogan.js +7 -9
  130. package/src/templates/index.js +21 -0
  131. package/src/templates/lodash.js +6 -8
  132. package/src/templates/markup.js +6 -8
  133. package/src/templates/mustache.js +6 -8
  134. package/src/templates/swig.js +1 -3
  135. package/src/templates/underscore.js +6 -8
  136. package/src/theme.js +717 -426
  137. package/src/themes/barebones.css +35 -0
  138. package/src/themes/barebones.css.js +3 -0
  139. package/src/themes/barebones.js +31 -0
  140. package/src/themes/bootstrap2.js +302 -264
  141. package/src/themes/bootstrap3.css +53 -0
  142. package/src/themes/bootstrap3.css.js +3 -0
  143. package/src/themes/bootstrap3.js +356 -259
  144. package/src/themes/bootstrap4.css +89 -0
  145. package/src/themes/bootstrap4.css.js +3 -0
  146. package/src/themes/bootstrap4.js +744 -234
  147. package/src/themes/bootstrap5.css +97 -0
  148. package/src/themes/bootstrap5.css.js +3 -0
  149. package/src/themes/bootstrap5.js +687 -0
  150. package/src/themes/foundation.js +539 -465
  151. package/src/themes/html.css +60 -0
  152. package/src/themes/html.css.js +3 -0
  153. package/src/themes/html.js +69 -82
  154. package/src/themes/index.js +29 -0
  155. package/src/themes/jqueryui.js +181 -166
  156. package/src/themes/materialize.js +263 -291
  157. package/src/themes/spectre.css +208 -0
  158. package/src/themes/spectre.css.js +3 -0
  159. package/src/themes/spectre.js +443 -0
  160. package/src/themes/tailwind.css +303 -0
  161. package/src/themes/tailwind.css.js +3 -0
  162. package/src/themes/tailwind.js +469 -0
  163. package/src/utilities.js +127 -68
  164. package/src/validator.js +914 -551
  165. package/src/validators/ip-validator.js +51 -0
  166. package/tests/Dockerfile +3 -0
  167. package/tests/README.md +27 -6
  168. package/tests/codeceptjs/codecept.json +29 -5
  169. package/tests/codeceptjs/constrains/contains_test.js +37 -0
  170. package/tests/codeceptjs/constrains/dependentRequired_test.js +33 -0
  171. package/tests/codeceptjs/constrains/dependentSchemas_test.js +16 -0
  172. package/tests/codeceptjs/constrains/if-then-else_test.js +186 -0
  173. package/tests/codeceptjs/core_test.js +534 -68
  174. package/tests/codeceptjs/editors/advanced_test.js +12 -10
  175. package/tests/codeceptjs/editors/array_any_of_test.js +50 -0
  176. package/tests/codeceptjs/editors/array_test.js +935 -677
  177. package/tests/codeceptjs/editors/autocomplete_test.js +15 -0
  178. package/tests/codeceptjs/editors/button_test.js +50 -0
  179. package/tests/codeceptjs/editors/checkbox_test.js +21 -8
  180. package/tests/codeceptjs/editors/colorpicker_test.js +29 -0
  181. package/tests/codeceptjs/editors/datetime_test.js +33 -0
  182. package/tests/codeceptjs/editors/inheritance_test.js +10 -0
  183. package/tests/codeceptjs/editors/integer_test.js +82 -71
  184. package/tests/codeceptjs/editors/jodit_test.js +23 -0
  185. package/tests/codeceptjs/editors/multiple_test.js +27 -0
  186. package/tests/codeceptjs/editors/multiselect_test.js +6 -9
  187. package/tests/codeceptjs/editors/number_test.js +75 -67
  188. package/tests/codeceptjs/editors/object_test.js +337 -28
  189. package/tests/codeceptjs/editors/option-no_default_values_test.js +42 -0
  190. package/tests/codeceptjs/editors/programmatic-changes_test.js +20 -0
  191. package/tests/codeceptjs/editors/purify_test.js +26 -0
  192. package/tests/codeceptjs/editors/radio_test.js +9 -0
  193. package/tests/codeceptjs/editors/range_test.js +10 -0
  194. package/tests/codeceptjs/editors/rating_test.js +10 -16
  195. package/tests/codeceptjs/editors/select_test.js +46 -23
  196. package/tests/codeceptjs/editors/starrating_test.js +15 -0
  197. package/tests/codeceptjs/editors/stepper_test.js +37 -0
  198. package/tests/codeceptjs/editors/string_test.js +108 -101
  199. package/tests/codeceptjs/editors/table-confirm-delete_test.js +60 -55
  200. package/tests/codeceptjs/editors/tabs_test.js +14 -11
  201. package/tests/codeceptjs/editors/uuid_test.js +48 -0
  202. package/tests/codeceptjs/editors/validation_test.js +13 -9
  203. package/tests/codeceptjs/issues/issue-gh-1133_test.js +9 -0
  204. package/tests/codeceptjs/issues/issue-gh-1158-2_test.js +10 -0
  205. package/tests/codeceptjs/issues/issue-gh-1158_test.js +8 -0
  206. package/tests/codeceptjs/issues/issue-gh-1164_test.js +9 -0
  207. package/tests/codeceptjs/issues/issue-gh-1171_test.js +11 -0
  208. package/tests/codeceptjs/issues/issue-gh-1211_test.js +17 -0
  209. package/tests/codeceptjs/issues/issue-gh-1257_test.js +11 -0
  210. package/tests/codeceptjs/issues/issue-gh-1272_test.js +21 -0
  211. package/tests/codeceptjs/issues/issue-gh-1330_test.js +8 -0
  212. package/tests/codeceptjs/issues/issue-gh-1338_test.js +18 -0
  213. package/tests/codeceptjs/issues/issue-gh-1347_test.js +8 -0
  214. package/tests/codeceptjs/issues/issue-gh-1364_test.js +13 -0
  215. package/tests/codeceptjs/issues/issue-gh-1367_test.js +11 -0
  216. package/tests/codeceptjs/issues/issue-gh-1383_test.js +9 -0
  217. package/tests/codeceptjs/issues/issue-gh-1384_test.js +9 -0
  218. package/tests/codeceptjs/issues/issue-gh-1410_test.js +13 -0
  219. package/tests/codeceptjs/issues/issue-gh-1422_test.js +9 -0
  220. package/tests/codeceptjs/issues/issue-gh-1431_test.js +12 -0
  221. package/tests/codeceptjs/issues/issue-gh-1439_test.js +12 -0
  222. package/tests/codeceptjs/issues/issue-gh-1452_test.js +10 -0
  223. package/tests/codeceptjs/issues/issue-gh-1453_test.js +18 -0
  224. package/tests/codeceptjs/issues/issue-gh-1461_test.js +14 -0
  225. package/tests/codeceptjs/issues/issue-gh-1463_test.js +9 -0
  226. package/tests/codeceptjs/issues/issue-gh-1471_test.js +17 -0
  227. package/tests/codeceptjs/issues/issue-gh-1485_test.js +13 -0
  228. package/tests/codeceptjs/issues/issue-gh-1491_test.js +9 -0
  229. package/tests/codeceptjs/issues/issue-gh-1525_test.js +9 -0
  230. package/tests/codeceptjs/issues/issue-gh-1536_test.js +12 -0
  231. package/tests/codeceptjs/issues/issue-gh-1538_test.js +10 -0
  232. package/tests/codeceptjs/issues/issue-gh-1541_test.js +8 -0
  233. package/tests/codeceptjs/issues/issue-gh-1559_test.js +15 -0
  234. package/tests/codeceptjs/issues/issue-gh-1562_test.js +12 -0
  235. package/tests/codeceptjs/issues/issue-gh-1586_test.js +15 -0
  236. package/tests/codeceptjs/issues/issue-gh-1636_test.js +9 -0
  237. package/tests/codeceptjs/issues/issue-gh-795_test.js +13 -0
  238. package/tests/codeceptjs/issues/issue-gh-810_test.js +52 -0
  239. package/tests/codeceptjs/issues/issue-gh-812_test.js +25 -0
  240. package/tests/codeceptjs/meta-schema_test.js +85 -0
  241. package/tests/codeceptjs/schemaloader_test.js +14 -0
  242. package/tests/codeceptjs/steps.d.ts +13 -0
  243. package/tests/codeceptjs/steps_file.js +4 -4
  244. package/tests/codeceptjs/test-config.js +3 -0
  245. package/tests/codeceptjs/themes_test.js +564 -0
  246. package/tests/docker-compose-local.yml +5 -0
  247. package/tests/docker-compose.yml +23 -17
  248. package/tests/fixtures/basic_person.json +2 -1
  249. package/tests/fixtures/definitions.json +22 -0
  250. package/tests/fixtures/nested_object.json +26 -0
  251. package/tests/fixtures/properties.json +20 -0
  252. package/tests/fixtures/some_types.json +32 -0
  253. package/tests/fixtures/validation.json +1347 -0
  254. package/tests/pages/_demo.html +475 -0
  255. package/tests/pages/advanced.html +1 -1
  256. package/tests/pages/anyof-2.html +91 -0
  257. package/tests/pages/anyof.html +82 -0
  258. package/tests/pages/array-anyof.html +145 -0
  259. package/tests/pages/array-checkboxes-infotext.html +55 -0
  260. package/tests/pages/array-checkboxes.html +5 -2
  261. package/tests/pages/array-choices.html +48 -0
  262. package/tests/pages/array-events-table.html +70 -0
  263. package/tests/pages/array-events.html +75 -0
  264. package/tests/pages/array-header-template.html +60 -0
  265. package/tests/pages/array-integers.html +5 -2
  266. package/tests/pages/array-multiselects.html +5 -2
  267. package/tests/pages/array-nested-arrays.html +5 -2
  268. package/tests/pages/array-numbers.html +5 -2
  269. package/tests/pages/array-objects.html +5 -2
  270. package/tests/pages/array-ratings.html +5 -2
  271. package/tests/pages/array-selectize-create.html +63 -0
  272. package/tests/pages/array-selectize.html +54 -0
  273. package/tests/pages/array-selects.html +5 -2
  274. package/tests/pages/array-strings.html +5 -2
  275. package/tests/pages/array-table-responsive.html +66 -0
  276. package/tests/pages/array-unique-items-sort.html +81 -0
  277. package/tests/pages/array.html +5 -2
  278. package/tests/pages/assets/autocomplete.css +1 -0
  279. package/tests/pages/assets/autocomplete.min.js +1 -0
  280. package/tests/pages/assets/pages.css +130 -0
  281. package/tests/pages/autocomplete.html +72 -0
  282. package/tests/pages/button-callbacks.html +79 -0
  283. package/tests/pages/button-icons.html +39 -0
  284. package/tests/pages/button_state_mode_1.html +35 -0
  285. package/tests/pages/button_state_mode_2.html +36 -0
  286. package/tests/pages/checkbox-labels.html +116 -0
  287. package/tests/pages/colorpicker-no-3rd-party.html +45 -0
  288. package/tests/pages/colorpicker-use-vanilla-picker.html +52 -0
  289. package/tests/pages/container-attributes.html +51 -0
  290. package/tests/pages/contains.html +39 -0
  291. package/tests/pages/core.html +14 -7
  292. package/tests/pages/datetime.html +78 -0
  293. package/tests/pages/dependentRequired.html +72 -0
  294. package/tests/pages/dependentSchemas.html +53 -0
  295. package/tests/pages/disable-button-in-object-editors.html +57 -0
  296. package/tests/pages/editor-show-validation-errors.html +54 -0
  297. package/tests/pages/enforce-const.html +168 -0
  298. package/tests/pages/error-messages.html +48 -0
  299. package/tests/pages/form-name.html +111 -0
  300. package/tests/pages/grid-strict.html +308 -0
  301. package/tests/pages/grid.html +281 -0
  302. package/tests/pages/if-else.html +58 -0
  303. package/tests/pages/if-then-else-allOf.html +118 -0
  304. package/tests/pages/if-then-else-disable-fields.html +70 -0
  305. package/tests/pages/if-then-else.html +65 -0
  306. package/tests/pages/if-then.html +58 -0
  307. package/tests/pages/inheritance.html +80 -0
  308. package/tests/pages/integer.html +17 -10
  309. package/tests/pages/issues/_template.html +50 -0
  310. package/tests/pages/issues/issue-gh-1133.html +64 -0
  311. package/tests/pages/issues/issue-gh-1158-2.html +189 -0
  312. package/tests/pages/issues/issue-gh-1158.html +50 -0
  313. package/tests/pages/issues/issue-gh-1164.html +71 -0
  314. package/tests/pages/issues/issue-gh-1165.html +63 -0
  315. package/tests/pages/issues/issue-gh-1165.json +8 -0
  316. package/tests/pages/issues/issue-gh-1171.html +39 -0
  317. package/tests/pages/issues/issue-gh-1211.html +1022 -0
  318. package/tests/pages/issues/issue-gh-1233-failing.html +46 -0
  319. package/tests/pages/issues/issue-gh-1233-passing.html +49 -0
  320. package/tests/pages/issues/issue-gh-1257.html +77 -0
  321. package/tests/pages/issues/issue-gh-1272.html +167 -0
  322. package/tests/pages/issues/issue-gh-1330.html +52 -0
  323. package/tests/pages/issues/issue-gh-1338.html +74 -0
  324. package/tests/pages/issues/issue-gh-1347.html +142 -0
  325. package/tests/pages/issues/issue-gh-1364.html +64 -0
  326. package/tests/pages/issues/issue-gh-1367.html +49 -0
  327. package/tests/pages/issues/issue-gh-1383.html +31 -0
  328. package/tests/pages/issues/issue-gh-1383.json +14 -0
  329. package/tests/pages/issues/issue-gh-1384.html +31 -0
  330. package/tests/pages/issues/issue-gh-1384.json +36 -0
  331. package/tests/pages/issues/issue-gh-1410.html +57 -0
  332. package/tests/pages/issues/issue-gh-1422.html +68 -0
  333. package/tests/pages/issues/issue-gh-1431.html +49 -0
  334. package/tests/pages/issues/issue-gh-1439.html +69 -0
  335. package/tests/pages/issues/issue-gh-1452.html +98 -0
  336. package/tests/pages/issues/issue-gh-1453.html +45 -0
  337. package/tests/pages/issues/issue-gh-1461.html +55 -0
  338. package/tests/pages/issues/issue-gh-1463.html +41 -0
  339. package/tests/pages/issues/issue-gh-1466.html +63 -0
  340. package/tests/pages/issues/issue-gh-1471.html +49 -0
  341. package/tests/pages/issues/issue-gh-1485.html +59 -0
  342. package/tests/pages/issues/issue-gh-1491.html +59 -0
  343. package/tests/pages/issues/issue-gh-1525.html +62 -0
  344. package/tests/pages/issues/issue-gh-1536.html +55 -0
  345. package/tests/pages/issues/issue-gh-1538.html +56 -0
  346. package/tests/pages/issues/issue-gh-1541.html +51 -0
  347. package/tests/pages/issues/issue-gh-1541.json +9 -0
  348. package/tests/pages/issues/issue-gh-1559.html +68 -0
  349. package/tests/pages/issues/issue-gh-1562.html +170 -0
  350. package/tests/pages/issues/issue-gh-1586.html +48 -0
  351. package/tests/pages/issues/issue-gh-1636.html +52 -0
  352. package/tests/pages/issues/issue-gh-795.html +58 -0
  353. package/tests/pages/issues/issue-gh-810.html +149 -0
  354. package/tests/pages/issues/issue-gh-812.html +113 -0
  355. package/tests/pages/issues/issue-gh-823-meta-schema.html +35 -0
  356. package/tests/pages/issues/issue-gh-848.html +81 -0
  357. package/tests/pages/keep_only_existing_values.html +81 -0
  358. package/tests/pages/load-events.html +61 -0
  359. package/tests/pages/maxContains.html +40 -0
  360. package/tests/pages/meta-schema.html +793 -0
  361. package/tests/pages/meta_schema.json +740 -0
  362. package/tests/pages/minContains.html +40 -0
  363. package/tests/pages/number.html +53 -6
  364. package/tests/pages/object-case-sensitive-property-search-false.html +42 -0
  365. package/tests/pages/object-case-sensitive-property-search-true.html +42 -0
  366. package/tests/pages/object-no-additional-properties.html +68 -0
  367. package/tests/pages/object-no-duplicated-id.html +70 -0
  368. package/tests/pages/object-required-properties.html +261 -0
  369. package/tests/pages/object-show-opt-in.html +111 -0
  370. package/tests/pages/object-with-dependencies-array.html +58 -0
  371. package/tests/pages/object-with-dependencies.html +62 -0
  372. package/tests/pages/object.html +5 -3
  373. package/tests/pages/oneof-2.html +91 -0
  374. package/tests/pages/oneof.html +105 -0
  375. package/tests/pages/opt-in-widget.html +134 -0
  376. package/tests/pages/option-dependencies.html +107 -0
  377. package/tests/pages/option-no_default_values.html +60 -0
  378. package/tests/pages/per-editor-options.html +44 -0
  379. package/tests/pages/placeholder-options.html +57 -0
  380. package/tests/pages/programmatic-changes.html +121 -0
  381. package/tests/pages/prompt-paste-max-length-reached.html +51 -0
  382. package/tests/pages/purify.html +66 -0
  383. package/tests/pages/range.html +62 -0
  384. package/tests/pages/read-only.html +60 -8
  385. package/tests/pages/ready.html +44 -0
  386. package/tests/pages/references.html +168 -0
  387. package/tests/pages/remove-false-properties.html +85 -0
  388. package/tests/pages/select-values.html +91 -0
  389. package/tests/pages/select.html +4 -3
  390. package/tests/pages/show-validation-errors.html +73 -0
  391. package/tests/pages/starrating.html +86 -0
  392. package/tests/pages/stepper-manual.html +59 -0
  393. package/tests/pages/stepper.html +61 -0
  394. package/tests/pages/string-ace-editor.html +7 -3
  395. package/tests/pages/string-cleave.html +48 -0
  396. package/tests/pages/string-custom-attributes.html +9 -6
  397. package/tests/pages/string-formats.html +54 -0
  398. package/tests/pages/string-formats2.html +59 -0
  399. package/tests/pages/{array-move-events.html → string-jodit-editor.html} +21 -29
  400. package/tests/pages/string-sceditor.html +9 -7
  401. package/tests/pages/string-simplemde-editor.html +83 -0
  402. package/tests/pages/switcher-option.html +69 -0
  403. package/tests/pages/table.html +4 -2
  404. package/tests/pages/tabs.html +1 -1
  405. package/tests/pages/themes.html +517 -0
  406. package/tests/pages/title-hidden.html +75 -0
  407. package/tests/pages/translate-property.html +248 -0
  408. package/tests/pages/urn.html +98 -0
  409. package/tests/pages/use-name-attributes.html +207 -0
  410. package/tests/pages/uuid.html +113 -0
  411. package/tests/pages/validation-messages.json +705 -0
  412. package/tests/pages/validation.html +20 -730
  413. package/tests/unit/.eslintrc +8 -0
  414. package/tests/unit/core.spec.js +320 -0
  415. package/tests/unit/defaults.spec.js +40 -0
  416. package/tests/unit/editor.spec.js +172 -0
  417. package/tests/unit/editors/array.spec.js +87 -0
  418. package/tests/unit/editors/object.spec.js +81 -0
  419. package/tests/unit/editors/table.spec.js +93 -0
  420. package/tests/unit/readme.md +35 -0
  421. package/tests/unit/schemaloader.spec.js +576 -0
  422. package/tests/unit/validator.spec.js +104 -0
  423. package/tests/unit/validators/ip-validator.spec.js +62 -0
  424. package/Gruntfile.js +0 -226
  425. package/dist/css/jsoneditor.min.css +0 -1
  426. package/dist/jsoneditor.js.map +0 -1
  427. package/dist/jsoneditor.min.js +0 -19
  428. package/dist/jsoneditor.min.js.map +0 -1
  429. package/docs/demo.html +0 -646
  430. package/src/class.js +0 -68
  431. package/src/editors/rating.js +0 -152
  432. package/src/ie9.js +0 -51
  433. package/src/intro.js +0 -23
  434. package/src/jquery.js +0 -64
  435. package/src/outro.js +0 -2
  436. package/src/themes/jsoneditor.barebones-theme.js +0 -60
package/src/validator.js CHANGED
@@ -1,645 +1,1008 @@
1
- JSONEditor.Validator = Class.extend({
2
- init: function(jsoneditor,schema,options) {
3
- this.jsoneditor = jsoneditor;
4
- this.schema = schema || this.jsoneditor.schema;
5
- this.options = options || {};
6
- this.translate = this.jsoneditor.translate || JSONEditor.defaults.translate;
7
- },
8
- validate: function(value) {
9
- return this._validateSchema(this.schema, value);
10
- },
11
- _validateSchema: function(schema,value,path) {
12
- var self = this;
13
- var errors = [];
14
- var valid, i, j;
15
- var stringified = JSON.stringify(value);
16
-
17
- path = path || 'root';
18
-
19
- // Work on a copy of the schema
20
- schema = $extend({},this.jsoneditor.expandRefs(schema));
1
+ import { ipValidator } from './validators/ip-validator.js'
2
+ import { extend, hasOwnProperty } from './utilities.js'
3
+
4
+ export class Validator {
5
+ constructor (jsoneditor, schema, options, defaults) {
6
+ this.jsoneditor = jsoneditor
7
+ this.schema = schema || this.jsoneditor.schema
8
+ this.options = options || {}
9
+ this.translate = this.jsoneditor.translate || defaults.translate
10
+ this.translateProperty = this.jsoneditor.translateProperty || defaults.translateProperty
11
+ this.defaults = defaults
12
+
13
+ this._validateSubSchema = {
14
+ dependentRequired (schema, value, path) {
15
+ const errors = []
16
+
17
+ if (typeof schema.dependentRequired !== 'undefined') {
18
+ let missingProperties = []
19
+
20
+ Object.keys(schema.dependentRequired).forEach((key) => {
21
+ if (typeof value[key] !== 'undefined') {
22
+ const requiredProperties = schema.dependentRequired[key]
23
+
24
+ missingProperties = requiredProperties.filter((property) => {
25
+ return !hasOwnProperty(value, property)
26
+ })
27
+ }
28
+ })
21
29
 
22
- /*
23
- * Type Agnostic Validation
24
- */
30
+ const invalid = missingProperties.length > 0
25
31
 
26
- // Version 3 `required` and `required_by_default`
27
- if(typeof value === "undefined") {
28
- if((typeof schema.required !== "undefined" && schema.required === true) || (typeof schema.required === "undefined" && this.jsoneditor.options.required_by_default === true)) {
29
- errors.push({
30
- path: path,
31
- property: 'required',
32
- message: this.translate("error_notset")
33
- });
34
- }
35
-
36
- return errors;
37
- }
32
+ if (invalid) {
33
+ errors.push({
34
+ message: 'Must have the required properties: ' + missingProperties.join(', '),
35
+ path
36
+ })
37
+ }
38
+ }
38
39
 
39
- // `enum`
40
- if(schema["enum"]) {
41
- valid = false;
42
- for(i=0; i<schema["enum"].length; i++) {
43
- if(stringified === JSON.stringify(schema["enum"][i])) valid = true;
44
- }
45
- if(!valid) {
46
- errors.push({
47
- path: path,
48
- property: 'enum',
49
- message: this.translate("error_enum")
50
- });
51
- }
52
- }
40
+ return errors
41
+ },
42
+ dependentSchemas (schema, value, path) {
43
+ let errors = []
53
44
 
54
- // `extends` (version 3)
55
- if(schema["extends"]) {
56
- for(i=0; i<schema["extends"].length; i++) {
57
- errors = errors.concat(this._validateSchema(schema["extends"][i],value,path));
58
- }
59
- }
45
+ Object.keys(schema.dependentSchemas).forEach((key) => {
46
+ if (typeof value[key] !== 'undefined') {
47
+ const dependentSchema = schema.dependentSchemas[key]
48
+ const tmpErrors = this._validateSchema(dependentSchema, value, path)
49
+ errors = [...errors, ...tmpErrors]
50
+ }
51
+ })
60
52
 
61
- // `allOf`
62
- if(schema.allOf) {
63
- for(i=0; i<schema.allOf.length; i++) {
64
- errors = errors.concat(this._validateSchema(schema.allOf[i],value,path));
65
- }
66
- }
53
+ return errors
54
+ },
55
+ contains (schema, value, path) {
56
+ const errors = []
57
+ let counter = 0
67
58
 
68
- // `anyOf`
69
- if(schema.anyOf) {
70
- valid = false;
71
- for(i=0; i<schema.anyOf.length; i++) {
72
- if(!this._validateSchema(schema.anyOf[i],value,path).length) {
73
- valid = true;
74
- break;
75
- }
76
- }
77
- if(!valid) {
78
- errors.push({
79
- path: path,
80
- property: 'anyOf',
81
- message: this.translate('error_anyOf')
82
- });
83
- }
84
- }
59
+ value.forEach((item) => {
60
+ const containsErrors = this._validateSchema(schema.contains, item, path)
85
61
 
86
- // `oneOf`
87
- if(schema.oneOf) {
88
- valid = 0;
89
- var oneof_errors = [];
90
- for(i=0; i<schema.oneOf.length; i++) {
91
- // Set the error paths to be path.oneOf[i].rest.of.path
92
- var tmp = this._validateSchema(schema.oneOf[i],value,path);
93
- if(!tmp.length) {
94
- valid++;
95
- }
62
+ if (containsErrors.length === 0) {
63
+ counter++
64
+ }
65
+ })
96
66
 
97
- for(j=0; j<tmp.length; j++) {
98
- tmp[j].path = path+'.oneOf['+i+']'+tmp[j].path.substr(path.length);
99
- }
100
- oneof_errors = oneof_errors.concat(tmp);
67
+ const containsInvalid = (counter === 0)
101
68
 
102
- }
103
- if(valid !== 1) {
104
- errors.push({
105
- path: path,
106
- property: 'oneOf',
107
- message: this.translate('error_oneOf', [valid])
108
- });
109
- errors = errors.concat(oneof_errors);
110
- }
111
- }
69
+ if (typeof schema.minContains !== 'undefined') {
70
+ const minContainsInvalid = (counter < schema.minContains)
112
71
 
113
- // `not`
114
- if(schema.not) {
115
- if(!this._validateSchema(schema.not,value,path).length) {
116
- errors.push({
117
- path: path,
118
- property: 'not',
119
- message: this.translate('error_not')
120
- });
121
- }
122
- }
123
-
124
- // `type` (both Version 3 and Version 4 support)
125
- if(schema.type) {
126
- // Union type
127
- if(Array.isArray(schema.type)) {
128
- valid = false;
129
- for(i=0;i<schema.type.length;i++) {
130
- if(this._checkType(schema.type[i], value)) {
131
- valid = true;
132
- break;
72
+ if (minContainsInvalid) {
73
+ errors.push({
74
+ message: this.translate('error_minContains', [counter, schema.minContains], schema),
75
+ path
76
+ })
133
77
  }
134
- }
135
- if(!valid) {
136
- errors.push({
137
- path: path,
138
- property: 'type',
139
- message: this.translate('error_type_union')
140
- });
141
- }
142
- }
143
- // Simple type
144
- else {
145
- if(['date', 'time', 'datetime-local'].indexOf(schema.format) != -1 && schema.type == 'integer') {
146
- // Hack to get validator to validate as string even if value is integer
147
- // As validation of 'date', 'time', 'datetime-local' is done in separate validator
148
- if(!this._checkType('string', ""+value)) {
78
+ } else {
79
+ if (containsInvalid) {
149
80
  errors.push({
150
- path: path,
151
- property: 'type',
152
- message: this.translate('error_type', [schema.format])
153
- });
81
+ message: this.translate('error_contains', null, schema),
82
+ path
83
+ })
154
84
  }
155
85
  }
156
- else if(!this._checkType(schema.type, value)) {
157
- errors.push({
158
- path: path,
159
- property: 'type',
160
- message: this.translate('error_type', [schema.type])
161
- });
162
- }
163
- }
164
- }
165
86
 
87
+ if (typeof schema.maxContains !== 'undefined') {
88
+ const maxContainsInvalid = (counter > schema.maxContains)
166
89
 
167
- // `disallow` (version 3)
168
- if(schema.disallow) {
169
- // Union type
170
- if(Array.isArray(schema.disallow)) {
171
- valid = true;
172
- for(i=0;i<schema.disallow.length;i++) {
173
- if(this._checkType(schema.disallow[i], value)) {
174
- valid = false;
175
- break;
90
+ if (maxContainsInvalid) {
91
+ errors.push({
92
+ message: this.translate('error_maxContains', [counter, schema.maxContains], schema),
93
+ path
94
+ })
176
95
  }
177
96
  }
178
- if(!valid) {
179
- errors.push({
180
- path: path,
181
- property: 'disallow',
182
- message: this.translate('error_disallow_union')
183
- });
184
- }
185
- }
186
- // Simple type
187
- else {
188
- if(this._checkType(schema.disallow, value)) {
189
- errors.push({
190
- path: path,
191
- property: 'disallow',
192
- message: this.translate('error_disallow', [schema.disallow])
193
- });
194
- }
195
- }
196
- }
197
-
198
- /*
199
- * Type Specific Validation
200
- */
201
-
202
- // Number Specific Validation
203
- if(typeof value === "number") {
204
- // `multipleOf` and `divisibleBy`
205
- if(schema.multipleOf || schema.divisibleBy) {
206
- var divisor = schema.multipleOf || schema.divisibleBy;
207
- // Vanilla JS, prone to floating point rounding errors (e.g. 1.14 / .01 == 113.99999)
208
- valid = (value/divisor === Math.floor(value/divisor));
209
97
 
210
- // Use math.js is available
211
- if(window.math) {
212
- valid = window.math.mod(window.math.bignumber(value), window.math.bignumber(divisor)).equals(0);
213
- }
214
- // Use decimal.js is available
215
- else if(window.Decimal) {
216
- valid = (new window.Decimal(value)).mod(new window.Decimal(divisor)).equals(0);
217
- }
98
+ return errors
99
+ },
100
+ if (schema, value, path) {
101
+ if (typeof schema.then === 'undefined' && typeof schema.else === 'undefined') {
102
+ return []
103
+ }
104
+
105
+ const ifErrors = this._validateSchema(schema.if, value, path)
106
+ let thenErrors = []
107
+ let elseErrors = []
108
+
109
+ if (typeof schema.then !== 'undefined') {
110
+ thenErrors = this._validateSchema(schema.then, value, path)
111
+ }
112
+
113
+ if (typeof schema.else !== 'undefined') {
114
+ elseErrors = this._validateSchema(schema.else, value, path)
115
+ }
116
+
117
+ if (schema.if === true) {
118
+ return thenErrors
119
+ }
120
+
121
+ if (schema.if === false) {
122
+ return elseErrors
123
+ }
124
+
125
+ if (ifErrors.length === 0) {
126
+ return thenErrors
127
+ }
128
+
129
+ if (ifErrors.length > 0) {
130
+ return elseErrors
131
+ }
132
+
133
+ return []
134
+ },
135
+ const (schema, value, path) {
136
+ const valid = JSON.stringify(schema.const) === JSON.stringify(value)
137
+
138
+ if (!valid) {
139
+ return [{
140
+ path,
141
+ property: 'const',
142
+ message: this.translate('error_const', null, schema)
143
+ }]
144
+ }
145
+ return []
146
+ },
147
+ enum (schema, value, path) {
148
+ const stringified = JSON.stringify(value)
149
+ const valid = schema.enum.some(e => stringified === JSON.stringify(e))
150
+ if (!valid) {
151
+ return [{
152
+ path,
153
+ property: 'enum',
154
+ message: this.translate('error_enum', null, schema)
155
+ }]
156
+ }
157
+ return []
158
+ },
159
+ extends (schema, value, path) {
160
+ const validate = (errors, e) => {
161
+ errors.push(...this._validateSchema(e, value, path))
162
+ return errors
163
+ }
164
+ return schema.extends.reduce(validate, [])
165
+ },
166
+ allOf (schema, value, path) {
167
+ const validate = (errors, e) => {
168
+ errors.push(...this._validateSchema(e, value, path))
169
+ return errors
170
+ }
171
+ return schema.allOf.reduce(validate, [])
172
+ },
173
+ anyOf (schema, value, path) {
174
+ const valid = schema.anyOf.some(e => !this._validateSchema(e, value, path).length)
175
+ if (!valid) {
176
+ return [{
177
+ path,
178
+ property: 'anyOf',
179
+ message: this.translate('error_anyOf', null, schema)
180
+ }]
181
+ }
182
+ return []
183
+ },
184
+ oneOf (schema, value, path) {
185
+ let valid = 0
186
+ const oneofErrors = []
187
+ schema.oneOf.forEach((o, i) => {
188
+ /* Set the error paths to be path.oneOf[i].rest.of.path */
189
+ const tmp = this._validateSchema(o, value, path)
190
+ if (!tmp.length) {
191
+ valid++
192
+ }
218
193
 
219
- if(!valid) {
194
+ tmp.forEach(e => {
195
+ e.path = `${path}.oneOf[${i}]${e.path.substr(path.length)}`
196
+ })
197
+ oneofErrors.push(...tmp)
198
+ })
199
+ const errors = []
200
+ if (valid !== 1) {
220
201
  errors.push({
221
- path: path,
222
- property: schema.multipleOf? 'multipleOf' : 'divisibleBy',
223
- message: this.translate('error_multipleOf', [divisor])
224
- });
202
+ path,
203
+ property: 'oneOf',
204
+ message: this.translate('error_oneOf', [valid], schema)
205
+ })
206
+ errors.push(...oneofErrors)
207
+ }
208
+ return errors
209
+ },
210
+ not (schema, value, path) {
211
+ if (!this._validateSchema(schema.not, value, path).length) {
212
+ return [{
213
+ path,
214
+ property: 'not',
215
+ message: this.translate('error_not', null, schema)
216
+ }]
217
+ }
218
+ return []
219
+ },
220
+ type (schema, value, path) {
221
+ /* Union type */
222
+ if (Array.isArray(schema.type)) {
223
+ const valid = schema.type.some(e => this._checkType(e, value))
224
+ if (!valid) {
225
+ return [{
226
+ path,
227
+ property: 'type',
228
+ message: this.translate('error_type_union', null, schema)
229
+ }]
230
+ }
231
+ } else {
232
+ /* Simple type */
233
+ if (['date', 'time', 'datetime-local'].includes(schema.format) && schema.type === 'integer') {
234
+ /* Hack to get validator to validate as string even if value is integer */
235
+ /* As validation of 'date', 'time', 'datetime-local' is done in separate validator */
236
+ if (!this._checkType('string', `${value}`)) {
237
+ return [{
238
+ path,
239
+ property: 'type',
240
+ message: this.translate('error_type', [schema.format], schema)
241
+ }]
242
+ }
243
+ } else if (!this._checkType(schema.type, value)) {
244
+ return [{
245
+ path,
246
+ property: 'type',
247
+ message: this.translate('error_type', [schema.type], schema)
248
+ }]
249
+ }
250
+ }
251
+ return []
252
+ },
253
+ disallow (schema, value, path) {
254
+ /* Union type */
255
+ if (Array.isArray(schema.disallow)) {
256
+ const invalid = schema.disallow.some(e => this._checkType(e, value))
257
+ if (invalid) {
258
+ return [{
259
+ path,
260
+ property: 'disallow',
261
+ message: this.translate('error_disallow_union', null, schema)
262
+ }]
263
+ }
264
+ } else {
265
+ /* Simple type */
266
+ if (this._checkType(schema.disallow, value)) {
267
+ return [{
268
+ path,
269
+ property: 'disallow',
270
+ message: this.translate('error_disallow', [schema.disallow], schema)
271
+ }]
272
+ }
225
273
  }
274
+ return []
226
275
  }
276
+ }
227
277
 
228
- // `maximum`
229
- if(schema.hasOwnProperty('maximum')) {
230
- // Vanilla JS, prone to floating point rounding errors (e.g. .999999999999999 == 1)
231
- valid = schema.exclusiveMaximum? (value < schema.maximum) : (value <= schema.maximum);
278
+ this._validateNumberSubSchema = {
279
+ multipleOf (schema, value, path) { return this._validateNumberSubSchemaMultipleDivisible(schema, value, path) },
280
+ divisibleBy (schema, value, path) { return this._validateNumberSubSchemaMultipleDivisible(schema, value, path) },
281
+ maximum (schema, value, path) {
282
+ /* Vanilla JS, prone to floating point rounding errors (e.g. .999999999999999 == 1) */
283
+ let valid = schema.exclusiveMaximum ? (value < schema.maximum) : (value <= schema.maximum)
232
284
 
233
- // Use math.js is available
234
- if(window.math) {
235
- valid = window.math[schema.exclusiveMaximum?'smaller':'smallerEq'](
285
+ /* Use math.js is available */
286
+ if (window.math) {
287
+ valid = window.math[schema.exclusiveMaximum ? 'smaller' : 'smallerEq'](
236
288
  window.math.bignumber(value),
237
289
  window.math.bignumber(schema.maximum)
238
- );
239
- }
240
- // Use Decimal.js if available
241
- else if(window.Decimal) {
242
- valid = (new window.Decimal(value))[schema.exclusiveMaximum?'lt':'lte'](new window.Decimal(schema.maximum));
290
+ )
291
+ } else if (window.Decimal) {
292
+ /* Use Decimal.js if available */
293
+ valid = (new window.Decimal(value))[schema.exclusiveMaximum ? 'lt' : 'lte'](new window.Decimal(schema.maximum))
243
294
  }
244
295
 
245
- if(!valid) {
246
- errors.push({
247
- path: path,
296
+ if (!valid) {
297
+ return [{
298
+ path,
248
299
  property: 'maximum',
249
300
  message: this.translate(
250
- (schema.exclusiveMaximum?'error_maximum_excl':'error_maximum_incl'),
251
- [schema.maximum]
301
+ (schema.exclusiveMaximum ? 'error_maximum_excl' : 'error_maximum_incl'),
302
+ [schema.maximum],
303
+ schema
252
304
  )
253
- });
305
+ }]
254
306
  }
255
- }
307
+ return []
308
+ },
309
+ minimum (schema, value, path) {
310
+ /* Vanilla JS, prone to floating point rounding errors (e.g. .999999999999999 == 1) */
311
+ let valid = schema.exclusiveMinimum ? (value > schema.minimum) : (value >= schema.minimum)
256
312
 
257
- // `minimum`
258
- if(schema.hasOwnProperty('minimum')) {
259
- // Vanilla JS, prone to floating point rounding errors (e.g. .999999999999999 == 1)
260
- valid = schema.exclusiveMinimum? (value > schema.minimum) : (value >= schema.minimum);
261
-
262
- // Use math.js is available
263
- if(window.math) {
264
- valid = window.math[schema.exclusiveMinimum?'larger':'largerEq'](
313
+ /* Use math.js is available */
314
+ if (window.math) {
315
+ valid = window.math[schema.exclusiveMinimum ? 'larger' : 'largerEq'](
265
316
  window.math.bignumber(value),
266
317
  window.math.bignumber(schema.minimum)
267
- );
268
- }
269
- // Use Decimal.js if available
270
- else if(window.Decimal) {
271
- valid = (new window.Decimal(value))[schema.exclusiveMinimum?'gt':'gte'](new window.Decimal(schema.minimum));
318
+ )
319
+ /* Use Decimal.js if available */
320
+ } else if (window.Decimal) {
321
+ valid = (new window.Decimal(value))[schema.exclusiveMinimum ? 'gt' : 'gte'](new window.Decimal(schema.minimum))
272
322
  }
273
323
 
274
- if(!valid) {
275
- errors.push({
276
- path: path,
324
+ if (!valid) {
325
+ return [{
326
+ path,
277
327
  property: 'minimum',
278
328
  message: this.translate(
279
- (schema.exclusiveMinimum?'error_minimum_excl':'error_minimum_incl'),
280
- [schema.minimum]
329
+ (schema.exclusiveMinimum ? 'error_minimum_excl' : 'error_minimum_incl'),
330
+ [schema.minimum],
331
+ schema
281
332
  )
282
- });
333
+ }]
283
334
  }
335
+ return []
284
336
  }
285
337
  }
286
- // String specific validation
287
- else if(typeof value === "string") {
288
- // `maxLength`
289
- if(schema.maxLength) {
290
- if((value+"").length > schema.maxLength) {
291
- errors.push({
292
- path: path,
293
- property: 'maxLength',
294
- message: this.translate('error_maxLength', [schema.maxLength])
295
- });
296
- }
297
- }
298
338
 
299
- // `minLength`
300
- if(schema.minLength) {
301
- if((value+"").length < schema.minLength) {
339
+ this._validateStringSubSchema = {
340
+ maxLength (schema, value, path) {
341
+ const errors = []
342
+ if ((`${value}`).length > schema.maxLength) {
302
343
  errors.push({
303
- path: path,
344
+ path,
345
+ property: 'maxLength',
346
+ message: this.translate('error_maxLength', [schema.maxLength], schema)
347
+ })
348
+ }
349
+ return errors
350
+ },
351
+ /* `minLength` */
352
+ minLength (schema, value, path) {
353
+ if ((`${value}`).length < schema.minLength) {
354
+ return [{
355
+ path,
304
356
  property: 'minLength',
305
- message: this.translate((schema.minLength===1?'error_notempty':'error_minLength'), [schema.minLength])
306
- });
307
- }
308
- }
309
-
310
- // `pattern`
311
- if(schema.pattern) {
312
- if(!(new RegExp(schema.pattern)).test(value)) {
313
- errors.push({
314
- path: path,
357
+ message: this.translate((schema.minLength === 1 ? 'error_notempty' : 'error_minLength'), [schema.minLength], schema)
358
+ }]
359
+ }
360
+ return []
361
+ },
362
+ /* `pattern` */
363
+ pattern (schema, value, path) {
364
+ if (!(new RegExp(schema.pattern)).test(value)) {
365
+ return [{
366
+ path,
315
367
  property: 'pattern',
316
- message: this.translate('error_pattern', [schema.pattern])
317
- });
368
+ message: (schema.options && schema.options.patternmessage) ? schema.options.patternmessage : this.translate('error_pattern', [schema.pattern], schema)
369
+ }]
318
370
  }
371
+ return []
319
372
  }
320
373
  }
321
- // Array specific validation
322
- else if(typeof value === "object" && value !== null && Array.isArray(value)) {
323
- // `items` and `additionalItems`
324
- if(schema.items) {
325
- // `items` is an array
326
- if(Array.isArray(schema.items)) {
327
- for(i=0; i<value.length; i++) {
328
- // If this item has a specific schema tied to it
329
- // Validate against it
330
- if(schema.items[i]) {
331
- errors = errors.concat(this._validateSchema(schema.items[i],value[i],path+'.'+i));
332
- }
333
- // If all additional items are allowed
334
- else if(schema.additionalItems === true) {
335
- break;
336
- }
337
- // If additional items is a schema
338
- // TODO: Incompatibility between version 3 and 4 of the spec
339
- else if(schema.additionalItems) {
340
- errors = errors.concat(this._validateSchema(schema.additionalItems,value[i],path+'.'+i));
341
- }
342
- // If no additional items are allowed
343
- else if(schema.additionalItems === false) {
374
+
375
+ this._validateArraySubSchema = {
376
+ items (schema, value, path) {
377
+ const errors = []
378
+ if (Array.isArray(schema.items)) {
379
+ for (let i = 0; i < value.length; i++) {
380
+ /* If this item has a specific schema tied to it */
381
+ /* Validate against it */
382
+ if (schema.items[i]) {
383
+ errors.push(...this._validateSchema(schema.items[i], value[i], `${path}.${i}`))
384
+ /* If all additional items are allowed */
385
+ } else if (schema.additionalItems === true) {
386
+ break
387
+ /* If additional items is a schema */
388
+ /* TODO: Incompatibility between version 3 and 4 of the spec */
389
+ } else if (schema.additionalItems) {
390
+ errors.push(...this._validateSchema(schema.additionalItems, value[i], `${path}.${i}`))
391
+ /* If no additional items are allowed */
392
+ } else if (schema.additionalItems === false) {
344
393
  errors.push({
345
- path: path,
394
+ path,
346
395
  property: 'additionalItems',
347
- message: this.translate('error_additionalItems')
348
- });
349
- break;
350
- }
351
- // Default for `additionalItems` is an empty schema
352
- else {
353
- break;
396
+ message: this.translate('error_additionalItems', null, schema)
397
+ })
398
+ break
399
+ /* Default for `additionalItems` is an empty schema */
400
+ } else {
401
+ break
354
402
  }
355
403
  }
356
- }
357
- // `items` is a schema
358
- else {
359
- // Each item in the array must validate against the schema
360
- for(i=0; i<value.length; i++) {
361
- errors = errors.concat(this._validateSchema(schema.items,value[i],path+'.'+i));
362
- }
363
- }
364
- }
365
-
366
- // `maxItems`
367
- if(schema.maxItems) {
368
- if(value.length > schema.maxItems) {
369
- errors.push({
370
- path: path,
404
+ /* `items` is a schema */
405
+ } else {
406
+ /* Each item in the array must validate against the schema */
407
+ value.forEach((e, i) => {
408
+ errors.push(...this._validateSchema(schema.items, e, `${path}.${i}`))
409
+ })
410
+ }
411
+ return errors
412
+ },
413
+ maxItems (schema, value, path) {
414
+ if (value.length > schema.maxItems) {
415
+ return [{
416
+ path,
371
417
  property: 'maxItems',
372
- message: this.translate('error_maxItems', [schema.maxItems])
373
- });
374
- }
375
- }
376
-
377
- // `minItems`
378
- if(schema.minItems) {
379
- if(value.length < schema.minItems) {
380
- errors.push({
381
- path: path,
418
+ message: this.translate('error_maxItems', [schema.maxItems], schema)
419
+ }]
420
+ }
421
+ return []
422
+ },
423
+ minItems (schema, value, path) {
424
+ if (value.length < schema.minItems) {
425
+ return [{
426
+ path,
382
427
  property: 'minItems',
383
- message: this.translate('error_minItems', [schema.minItems])
384
- });
385
- }
386
- }
387
-
388
- // `uniqueItems`
389
- if(schema.uniqueItems) {
390
- var seen = {};
391
- for(i=0; i<value.length; i++) {
392
- valid = JSON.stringify(value[i]);
393
- if(seen[valid]) {
394
- errors.push({
395
- path: path,
428
+ message: this.translate('error_minItems', [schema.minItems], schema)
429
+ }]
430
+ }
431
+ return []
432
+ },
433
+ uniqueItems (schema, value, path) {
434
+ const seen = {}
435
+ for (let i = 0; i < value.length; i++) {
436
+ const valid = JSON.stringify(value[i])
437
+ if (seen[valid]) {
438
+ return [{
439
+ path,
396
440
  property: 'uniqueItems',
397
- message: this.translate('error_uniqueItems')
398
- });
399
- break;
441
+ message: this.translate('error_uniqueItems', null, schema)
442
+ }]
400
443
  }
401
- seen[valid] = true;
444
+ seen[valid] = true
402
445
  }
446
+ return []
403
447
  }
404
448
  }
405
- // Object specific validation
406
- else if(typeof value === "object" && value !== null) {
407
- // `maxProperties`
408
- if(schema.maxProperties) {
409
- valid = 0;
410
- for(i in value) {
411
- if(!value.hasOwnProperty(i)) continue;
412
- valid++;
413
- }
414
- if(valid > schema.maxProperties) {
415
- errors.push({
416
- path: path,
417
- property: 'maxProperties',
418
- message: this.translate('error_maxProperties', [schema.maxProperties])
419
- });
420
- }
421
- }
422
449
 
423
- // `minProperties`
424
- if(schema.minProperties) {
425
- valid = 0;
426
- for(i in value) {
427
- if(!value.hasOwnProperty(i)) continue;
428
- valid++;
429
- }
430
- if(valid < schema.minProperties) {
431
- errors.push({
432
- path: path,
450
+ this._validateObjectSubSchema = {
451
+ maxProperties (schema, value, path) {
452
+ if (Object.keys(value).length > schema.maxProperties) {
453
+ return [{
454
+ path,
455
+ property: 'maxProperties',
456
+ message: this.translate('error_maxProperties', [schema.maxProperties], schema)
457
+ }]
458
+ }
459
+ return []
460
+ },
461
+ minProperties (schema, value, path) {
462
+ if (Object.keys(value).length < schema.minProperties) {
463
+ return [{
464
+ path,
433
465
  property: 'minProperties',
434
- message: this.translate('error_minProperties', [schema.minProperties])
435
- });
436
- }
437
- }
438
-
439
- // Version 4 `required`
440
- if(typeof schema.required !== "undefined" && Array.isArray(schema.required)) {
441
- for(i=0; i<schema.required.length; i++) {
442
- if(typeof value[schema.required[i]] === "undefined") {
466
+ message: this.translate('error_minProperties', [schema.minProperties], schema)
467
+ }]
468
+ }
469
+ return []
470
+ },
471
+ required (schema, value, path) {
472
+ const errors = []
473
+ if (Array.isArray(schema.required)) {
474
+ schema.required.forEach(e => {
475
+ if (typeof value[e] !== 'undefined') return
476
+ const editor = this.jsoneditor.getEditor(`${path}.${e}`)
477
+ if (editor && editor.dependenciesFulfilled === false) return
478
+ /* Ignore required error if editor is of type "button" or "info" */
479
+ if (editor && ['button', 'info'].includes(editor.schema.format || editor.schema.type)) return
443
480
  errors.push({
444
- path: path,
481
+ path,
445
482
  property: 'required',
446
- message: this.translate('error_required', [schema.required[i]])
447
- });
448
- }
449
- }
450
- }
451
-
452
- // `properties`
453
- var validated_properties = {};
454
- for(i in schema.properties) {
455
- if(!schema.properties.hasOwnProperty(i)) continue;
456
- validated_properties[i] = true;
457
- errors = errors.concat(this._validateSchema(schema.properties[i],value[i],path+'.'+i));
458
- }
459
-
460
- // `patternProperties`
461
- if(schema.patternProperties) {
462
- for(i in schema.patternProperties) {
463
- if(!schema.patternProperties.hasOwnProperty(i)) continue;
464
- var regex = new RegExp(i);
465
-
466
- // Check which properties match
467
- for(j in value) {
468
- if(!value.hasOwnProperty(j)) continue;
469
- if(regex.test(j)) {
470
- validated_properties[j] = true;
471
- errors = errors.concat(this._validateSchema(schema.patternProperties[i],value[j],path+'.'+j));
483
+ message: this.translate('error_required', [schema && schema.properties && schema.properties[e] && schema.properties[e].title ? schema.properties[e].title : e], schema)
484
+ })
485
+ })
486
+ }
487
+ return errors
488
+ },
489
+ properties (schema, value, path, validatedProperties) {
490
+ const errors = []
491
+ Object.entries(schema.properties).forEach(([key, prop]) => {
492
+ validatedProperties[key] = true
493
+ errors.push(...this._validateSchema(prop, value[key], `${path}.${key}`))
494
+ })
495
+ return errors
496
+ },
497
+ patternProperties (schema, value, path, validatedProperties) {
498
+ const errors = []
499
+ Object.entries(schema.patternProperties).forEach(([i, prop]) => {
500
+ const regex = new RegExp(i)
501
+ /* Check which properties match */
502
+ Object.entries(value).forEach(([j, v]) => {
503
+ if (regex.test(j)) {
504
+ validatedProperties[j] = true
505
+ errors.push(...this._validateSchema(prop, v, `${path}.${j}`))
472
506
  }
473
- }
474
- }
475
- }
476
-
477
- // The no_additional_properties option currently doesn't work with extended schemas that use oneOf or anyOf
478
- if(typeof schema.additionalProperties === "undefined" && this.jsoneditor.options.no_additional_properties && !schema.oneOf && !schema.anyOf) {
479
- schema.additionalProperties = false;
507
+ })
508
+ })
509
+ return errors
480
510
  }
511
+ }
481
512
 
482
- // `additionalProperties`
483
- if(typeof schema.additionalProperties !== "undefined") {
484
- for(i in value) {
485
- if(!value.hasOwnProperty(i)) continue;
486
- if(!validated_properties[i]) {
487
- // No extra properties allowed
488
- if(!schema.additionalProperties) {
489
- errors.push({
490
- path: path,
491
- property: 'additionalProperties',
492
- message: this.translate('error_additional_properties', [i])
493
- });
494
- break;
513
+ this._validateObjectSubSchema2 = {
514
+ propertyNames (schema, value, path, validatedProperties) {
515
+ const errors = []
516
+ const keys = Object.keys(value)
517
+ let k = null
518
+ for (let i = 0; i < keys.length; i++) {
519
+ let msg = ''
520
+ let truthy = false
521
+ k = keys[i]
522
+ /* Check property names that don't match */
523
+ if (typeof schema.propertyNames === 'boolean') {
524
+ if (schema.propertyNames === true) {
525
+ continue
495
526
  }
496
- // Allowed
497
- else if(schema.additionalProperties === true) {
498
- break;
499
- }
500
- // Must match schema
501
- // TODO: incompatibility between version 3 and 4 of the spec
502
- else {
503
- errors = errors.concat(this._validateSchema(schema.additionalProperties,value[i],path+'.'+i));
527
+ errors.push({
528
+ path,
529
+ property: 'propertyNames',
530
+ message: this.translate('error_property_names_false', [k], schema)
531
+ })
532
+ break
533
+ }
534
+ truthy = Object.entries(schema.propertyNames).every(([j, prop]) => {
535
+ let match = false
536
+ let regex = null
537
+ switch (j) {
538
+ case 'maxLength':
539
+ if (typeof prop !== 'number') {
540
+ msg = 'error_property_names_maxlength'
541
+ break
542
+ }
543
+ if (k.length > prop) {
544
+ msg = 'error_property_names_exceeds_maxlength'
545
+ break
546
+ }
547
+ return true
548
+ case 'const':
549
+ if (prop !== k) {
550
+ msg = 'error_property_names_const_mismatch'
551
+ break
552
+ }
553
+ return true
554
+ case 'enum':
555
+ if (!Array.isArray(prop)) {
556
+ msg = 'error_property_names_enum'
557
+ break
558
+ }
559
+ prop.forEach(p => {
560
+ if (p === k) {
561
+ match = true
562
+ }
563
+ })
564
+ if (!match) {
565
+ msg = 'error_property_names_enum_mismatch'
566
+ break
567
+ }
568
+ return true
569
+ case 'pattern':
570
+ if (typeof prop !== 'string') {
571
+ msg = 'error_property_names_pattern'
572
+ break
573
+ }
574
+ regex = new RegExp(prop)
575
+ if (!regex.test(k)) {
576
+ msg = 'error_property_names_pattern_mismatch'
577
+ break
578
+ }
579
+ return true
580
+ default:
581
+ errors.push({
582
+ path,
583
+ property: 'propertyNames',
584
+ message: this.translate('error_property_names_unsupported', [j], schema)
585
+ })
586
+ return false
504
587
  }
588
+ errors.push({
589
+ path,
590
+ property: 'propertyNames',
591
+ message: this.translate(msg, [k], schema)
592
+ })
593
+ return false
594
+ })
595
+ if (!truthy) break
596
+ }
597
+ return errors
598
+ },
599
+ additionalProperties (schema, value, path, validatedProperties) {
600
+ const errors = []
601
+ const keys = Object.keys(value)
602
+ for (let i = 0; i < keys.length; i++) {
603
+ const k = keys[i]
604
+ if (validatedProperties[k]) continue
605
+ /* No extra properties allowed */
606
+ if (!schema.additionalProperties) {
607
+ errors.push({
608
+ path,
609
+ property: 'additionalProperties',
610
+ message: this.translate('error_additional_properties', [k], schema)
611
+ })
612
+ break
613
+ /* Allowed */
614
+ } else if (schema.additionalProperties === true) {
615
+ break
616
+ /* Must match schema */
617
+ /* TODO: incompatibility between version 3 and 4 of the spec */
618
+ } else {
619
+ errors.push(...this._validateSchema(schema.additionalProperties, value[k], `${path}.${k}`))
505
620
  }
506
621
  }
507
- }
508
-
509
- // `dependencies`
510
- if(schema.dependencies) {
511
- for(i in schema.dependencies) {
512
- if(!schema.dependencies.hasOwnProperty(i)) continue;
513
-
514
- // Doesn't need to meet the dependency
515
- if(typeof value[i] === "undefined") continue;
516
-
517
- // Property dependency
518
- if(Array.isArray(schema.dependencies[i])) {
519
- for(j=0; j<schema.dependencies[i].length; j++) {
520
- if(typeof value[schema.dependencies[i][j]] === "undefined") {
622
+ return errors
623
+ },
624
+ dependencies (schema, value, path) {
625
+ const errors = []
626
+ Object.entries(schema.dependencies).forEach(([i, dep]) => {
627
+ /* Doesn't need to meet the dependency */
628
+ if (typeof value[i] === 'undefined') return
629
+
630
+ /* Property dependency */
631
+ if (Array.isArray(dep)) {
632
+ dep.forEach(d => {
633
+ if (typeof value[d] === 'undefined') {
521
634
  errors.push({
522
- path: path,
635
+ path,
523
636
  property: 'dependencies',
524
- message: this.translate('error_dependency', [schema.dependencies[i][j]])
525
- });
637
+ message: this.translate('error_dependency', [d], schema)
638
+ })
526
639
  }
527
- }
640
+ })
641
+ /* Schema dependency */
642
+ } else {
643
+ errors.push(...this._validateSchema(dep, value, path))
528
644
  }
529
- // Schema dependency
530
- else {
531
- errors = errors.concat(this._validateSchema(schema.dependencies[i],value,path));
532
- }
533
- }
645
+ })
646
+ return errors
534
647
  }
535
648
  }
649
+ }
536
650
 
537
- // date, time and datetime-local validation
538
- if(['date', 'time', 'datetime-local'].indexOf(schema.format) != -1) {
539
-
540
- var validator = {
541
- 'date': /^(\d{4}\D\d{2}\D\d{2})?$/,
542
- 'time': /^(\d{2}:\d{2}(?::\d{2})?)?$/,
543
- 'datetime-local': /^(\d{4}\D\d{2}\D\d{2} \d{2}:\d{2}(?::\d{2})?)?$/
544
- };
545
- var format = {
546
- 'date': '"YYYY-MM-DD"',
547
- 'time': '"HH:MM"',
548
- 'datetime-local': '"YYYY-MM-DD HH:MM"'
549
- };
550
-
551
- var ed = this.jsoneditor.getEditor(path);
552
- var dateFormat = ed.flatpickr ? ed.flatpickr.config.dateFormat : format[ed.format];
553
-
554
- if (schema.type == 'integer') {
555
- // The value is a timestamp
556
- if (value * 1 < 1) {
557
- // If value is less than 1, then it's an invalid epoch date before 00:00:00 UTC Thursday, 1 January 1970
558
- errors.push({
559
- path: path,
560
- property: 'format',
561
- message: this.translate('error_invalid_epoch')
562
- });
651
+ fitTest (value, givenSchema, weight = 10000000) {
652
+ const fit = { match: 0, extra: 0 }
653
+ if (typeof value === 'object' && value !== null) {
654
+ /* Work on a copy of the schema */
655
+ const schema = this._getSchema(givenSchema)
656
+ /* If the schema is an anyOf declaration, do use the properties of the allowed sub schemata instead.
657
+ Of these sub schemata, the best fit is selected */
658
+ if (schema.anyOf) {
659
+ let bestFit = { ...fit }
660
+ for (const subSchema of schema.anyOf) {
661
+ const subFit = this.fitTest(value, subSchema, weight)
662
+ /* The best fit is the one with the best value for match. If there are multiple results
663
+ with the same match value, use the one with the least number of extra properties */
664
+ if ((subFit.match > bestFit.match) || (subFit.match === bestFit.match && subFit.extra < bestFit.extra)) {
665
+ bestFit = subFit
666
+ }
563
667
  }
564
- else if (value != Math.abs(parseInt(value))) {
565
- // not much to check for, so we assume value is ok if it's a positive number
566
- errors.push({
567
- path: path,
568
- property: 'format',
569
- message: this.translate('error_' + ed.format.replace(/-/g, "_"), [dateFormat])
570
- });
668
+ return bestFit
669
+ } else {
670
+ const properties = this._getSchema(givenSchema).properties
671
+ for (const i in properties) {
672
+ if (!hasOwnProperty(properties, i)) {
673
+ fit.extra += weight
674
+ continue
675
+ }
676
+ if (typeof value[i] === 'object' && typeof properties[i] === 'object' && typeof properties[i].properties === 'object') {
677
+ const result = this.fitTest(value[i], properties[i], weight / 100)
678
+ fit.match += result.match
679
+ fit.extra += result.extra
680
+ }
681
+ if (typeof value[i] !== 'undefined') {
682
+ fit.match += weight
683
+ }
571
684
  }
572
685
  }
573
- else if (!ed.flatpickr) {
574
- // Standard string input, without flatpickr
575
- if(!validator[ed.format].test(value)) {
576
- errors.push({
577
- path: path,
578
- property: 'format',
579
- message: this.translate('error_' + ed.format.replace(/-/g, "_"), [format[ed.format]])
580
- });
581
- }
686
+ }
687
+ return fit
688
+ }
689
+
690
+ _getSchema (schema) {
691
+ return typeof schema === 'undefined' ? extend({}, this.jsoneditor.expandRefs(this.schema)) : schema
692
+ }
693
+
694
+ validate (value) {
695
+ return this._validateSchema(this.schema, value)
696
+ }
697
+
698
+ _validateSchema (schema, value, path) {
699
+ const errors = []
700
+ path = path || this.jsoneditor.root.formname
701
+
702
+ /* Work on a copy of the schema */
703
+ schema = extend({}, this.jsoneditor.expandRefs(schema))
704
+
705
+ /*
706
+ * Type Agnostic Validation
707
+ */
708
+ /* Version 3 `required` and `required_by_default` */
709
+ if (typeof value === 'undefined') {
710
+ return this._validateV3Required(schema, value, path)
711
+ }
712
+
713
+ Object.keys(schema).forEach(key => {
714
+ if (this._validateSubSchema[key]) {
715
+ errors.push(...this._validateSubSchema[key].call(this, schema, value, path))
582
716
  }
583
- else {
584
- // Flatpickr validation
585
- if (value !== '') {
586
-
587
- var compareValue;
588
- if(ed.flatpickr.config.mode != 'single') {
589
- var seperator = ed.flatpickr.config.mode == 'range' ? ed.flatpickr.l10n.rangeSeparator : ', ';
590
- var selectedDates = ed.flatpickr.selectedDates.map(function(val) {
591
- return ed.flatpickr.formatDate(val, ed.flatpickr.config.dateFormat);
592
- });
593
- compareValue = selectedDates.join(seperator);
594
- }
717
+ })
595
718
 
596
- try {
597
- if (compareValue) {
598
- // Not the best validation method, but range and multiple mode are special
599
- // Optimal solution would be if it is possible to change the return format from string/integer to array
600
- if (compareValue != value) throw ed.flatpickr.config.mode + ' mismatch';
601
- }
602
- else if (ed.flatpickr.formatDate(ed.flatpickr.parseDate(value, ed.flatpickr.config.dateFormat), ed.flatpickr.config.dateFormat) != value) throw 'mismatch';
603
- }
604
- catch(err) {
605
- var errorDateFormat = ed.flatpickr.config.errorDateFormat !== undefined ? ed.flatpickr.config.errorDateFormat : ed.flatpickr.config.dateFormat;
606
- errors.push({
607
- path: path,
608
- property: 'format',
609
- message: this.translate('error_' + ed.format.replace(/-/g, "_"), [errorDateFormat])
610
- });
719
+ /*
720
+ * Type Specific Validation
721
+ */
722
+ errors.push(...this._validateByValueType(schema, value, path))
723
+
724
+ if (schema.links) {
725
+ schema.links.forEach((s, m) => {
726
+ if (s.rel && s.rel.toLowerCase() === 'describedby') {
727
+ schema = this._expandSchemaLink(schema, m)
728
+ errors.push(...this._validateSchema(schema, value, path, this.translate))
729
+ }
730
+ })
731
+ }
732
+
733
+ /* date, time and datetime-local validation */
734
+ if (['date', 'time', 'datetime-local'].includes(schema.format)) {
735
+ errors.push(...this._validateDateTimeSubSchema(schema, value, path))
736
+ }
737
+
738
+ /* uuid validation */
739
+ if (['uuid'].includes(schema.format)) {
740
+ errors.push(...this._validateUUIDSchema(schema, value, path))
741
+ }
742
+
743
+ /* custom validator */
744
+ errors.push(...this._validateCustomValidator(schema, value, path))
745
+
746
+ /* Remove duplicate errors and add "errorcount" property */
747
+ return this._removeDuplicateErrors(errors)
748
+ }
749
+
750
+ _expandSchemaLink (schema, m) {
751
+ const href = schema.links[m].href
752
+ const data = this.jsoneditor.root.getValue()
753
+ const template = this.jsoneditor.compileTemplate(href, this.jsoneditor.template)
754
+ const ref = document.location.origin + document.location.pathname + template(data)
755
+
756
+ schema.links = schema.links.slice(0, m).concat(schema.links.slice(m + 1))
757
+ return extend({}, schema, this.jsoneditor.refs[ref])
758
+ }
759
+
760
+ _validateV3Required (schema, value, path) {
761
+ if (((typeof schema.required !== 'undefined' && schema.required === true) || (typeof schema.required === 'undefined' && this.jsoneditor.options.required_by_default === true)) && (schema.type !== 'info')) {
762
+ return [{
763
+ path,
764
+ property: 'required',
765
+ message: this.translate('error_notset', null, schema)
766
+ }]
767
+ }
768
+ return []
769
+ }
770
+
771
+ _validateByValueType (schema, value, path) {
772
+ const errors = []
773
+ if (value === null) return errors
774
+ /* Number Specific Validation */
775
+ if (typeof value === 'number') {
776
+ /* `multipleOf` and `divisibleBy` */
777
+ /* `maximum` */
778
+ /* `minimum` */
779
+ Object.keys(schema).forEach(key => {
780
+ if (this._validateNumberSubSchema[key]) {
781
+ errors.push(...this._validateNumberSubSchema[key].call(this, schema, value, path))
782
+ }
783
+ })
784
+ /* String specific validation */
785
+ } else if (typeof value === 'string') {
786
+ /* `maxLength` */
787
+ /* `minLength` */
788
+ /* `pattern` */
789
+ Object.keys(schema).forEach(key => {
790
+ if (this._validateStringSubSchema[key]) {
791
+ errors.push(...this._validateStringSubSchema[key].call(this, schema, value, path))
792
+ }
793
+ })
794
+ /* Array specific validation */
795
+ } else if (Array.isArray(value)) {
796
+ /* `items` and `additionalItems`
797
+ /* `maxItems`
798
+ /* `minItems`
799
+ /* `uniqueItems` */
800
+ Object.keys(schema).forEach(key => {
801
+ if (this._validateArraySubSchema[key]) {
802
+ errors.push(...this._validateArraySubSchema[key].call(this, schema, value, path))
803
+ }
804
+ })
805
+ /* Object specific validation */
806
+ } else if (typeof value === 'object') {
807
+ const validatedProperties = {}
808
+ /* `maxProperties`
809
+ /* `minProperties`
810
+ /* Version 4 `required`
811
+ /* `properties`
812
+ /* `patternProperties` */
813
+ Object.keys(schema).forEach(key => {
814
+ if (this._validateObjectSubSchema[key]) {
815
+ errors.push(...this._validateObjectSubSchema[key].call(this, schema, value, path, validatedProperties))
816
+ }
817
+ })
818
+
819
+ /* The no_additional_properties option currently doesn't work with extended schemas that use oneOf or anyOf or allOf */
820
+ if (typeof schema.additionalProperties === 'undefined' && this.jsoneditor.options.no_additional_properties && !schema.oneOf && !schema.anyOf && !schema.allOf) {
821
+ schema.additionalProperties = false
822
+ }
823
+
824
+ /* `additionalProperties` */
825
+ /* `dependencies` */
826
+ Object.keys(schema).forEach(key => {
827
+ if (typeof this._validateObjectSubSchema2[key] !== 'undefined') {
828
+ errors.push(...this._validateObjectSubSchema2[key].call(this, schema, value, path, validatedProperties))
829
+ }
830
+ })
831
+ }
832
+ return errors
833
+ }
834
+
835
+ _validateUUIDSchema (schema, value, path) {
836
+ if (!(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value))) {
837
+ return [{
838
+ path,
839
+ property: 'format',
840
+ message: this.translate('error_pattern', ['^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$'], schema)
841
+ }]
842
+ }
843
+ return []
844
+ }
845
+
846
+ _validateNumberSubSchemaMultipleDivisible (schema, value, path) {
847
+ const divisor = schema.multipleOf || schema.divisibleBy
848
+ /* Vanilla JS, prone to floating point rounding errors (e.g. 1.14 / .01 == 113.99999) */
849
+ let valid = (value / divisor === Math.floor(value / divisor))
850
+
851
+ /* Use math.js is available */
852
+ if (window.math) {
853
+ valid = window.math.mod(window.math.bignumber(value), window.math.bignumber(divisor)).equals(0)
854
+ } else if (window.Decimal) {
855
+ /* Use decimal.js is available */
856
+ valid = (new window.Decimal(value)).mod(new window.Decimal(divisor)).equals(0)
857
+ }
858
+
859
+ if (!valid) {
860
+ return [{
861
+ path,
862
+ property: schema.multipleOf ? 'multipleOf' : 'divisibleBy',
863
+ message: this.translate('error_multipleOf', [divisor], schema)
864
+ }]
865
+ }
866
+ return []
867
+ }
868
+
869
+ _validateDateTimeSubSchema (schema, value, path) {
870
+ const _validateInteger = (schema, value, path) => {
871
+ /* The value is a timestamp */
872
+ if (value * 1 < 1) {
873
+ /* If value is less than 1, then it's an invalid epoch date before 00:00:00 UTC Thursday, 1 January 1970 */
874
+ return [{
875
+ path,
876
+ property: 'format',
877
+ message: this.translate('error_invalid_epoch', null, schema)
878
+ }]
879
+ } else if (value !== Math.abs(parseInt(value))) {
880
+ /* not much to check for, so we assume value is ok if it's a positive number */
881
+ return [{
882
+ path,
883
+ property: 'format',
884
+ message: this.translate(`error_${schema.format.replace(/-/g, '_')}`, [dateFormat], schema)
885
+ }]
886
+ }
887
+ return []
888
+ }
889
+ const _validateFlatPicker = (schema, value, path, editor) => {
890
+ if (value !== '') {
891
+ let compareValue
892
+ if (editor.flatpickr.config.mode !== 'single') {
893
+ const seperator = editor.flatpickr.config.mode === 'range' ? editor.flatpickr.l10n.rangeSeparator : ', '
894
+ const selectedDates = editor.flatpickr.selectedDates.map(val =>
895
+ editor.flatpickr.formatDate(val, editor.flatpickr.config.dateFormat)
896
+ )
897
+ compareValue = selectedDates.join(seperator)
898
+ }
899
+
900
+ try {
901
+ if (compareValue) {
902
+ /* Not the best validation method, but range and multiple mode are special */
903
+ /* Optimal solution would be if it is possible to change the return format from string/integer to array */
904
+ if (compareValue !== value) throw new Error(`${editor.flatpickr.config.mode} mismatch`)
905
+ } else if (editor.flatpickr.formatDate(editor.flatpickr.parseDate(value, editor.flatpickr.config.dateFormat), editor.flatpickr.config.dateFormat) !== value) {
906
+ throw new Error('mismatch')
611
907
  }
908
+ } catch (err) {
909
+ const errorDateFormat = editor.flatpickr.config.errorDateFormat !== undefined ? editor.flatpickr.config.errorDateFormat : editor.flatpickr.config.dateFormat
910
+ return [{
911
+ path,
912
+ property: 'format',
913
+ message: this.translate(`error_${editor.format.replace(/-/g, '_')}`, [errorDateFormat], schema)
914
+ }]
612
915
  }
613
916
  }
917
+ return []
614
918
  }
615
- // Custom type validation (global)
616
- $each(JSONEditor.defaults.custom_validators,function(i,validator) {
617
- errors = errors.concat(validator.call(self,schema,value,path));
618
- });
619
- // Custom type validation (instance specific)
620
- if(this.options.custom_validators) {
621
- $each(this.options.custom_validators,function(i,validator) {
622
- errors = errors.concat(validator.call(self,schema,value,path));
623
- });
919
+
920
+ const validatorRx = {
921
+ date: /^(\d{4}\D\d{2}\D\d{2})$/,
922
+ time: /^(\d{2}:\d{2}(?::\d{2})?)$/,
923
+ 'datetime-local': /^(\d{4}\D\d{2}\D\d{2}[ T]\d{2}:\d{2}(?::\d{2})?)$/
924
+ }
925
+ const format = {
926
+ date: '"YYYY-MM-DD"',
927
+ time: '"HH:MM"',
928
+ 'datetime-local': '"YYYY-MM-DD HH:MM"'
929
+ }
930
+
931
+ const editor = this.jsoneditor.getEditor(path)
932
+ const dateFormat = (editor && editor.flatpickr) ? editor.flatpickr.config.dateFormat : format[schema.format]
933
+
934
+ if (schema.type === 'integer') {
935
+ return _validateInteger(schema, value, path)
936
+ } else if (!editor || !editor.flatpickr) {
937
+ /* Standard string input, without flatpickr */
938
+ if (!validatorRx[schema.format].test(value)) {
939
+ return [{
940
+ path,
941
+ property: 'format',
942
+ message: this.translate(`error_${schema.format.replace(/-/g, '_')}`, [dateFormat], schema)
943
+ }]
944
+ }
945
+ } else if (editor) {
946
+ /* Flatpickr validation */
947
+ return _validateFlatPicker(schema, value, path, editor)
624
948
  }
625
949
 
626
- return errors;
627
- },
628
- _checkType: function(type, value) {
629
- // Simple types
630
- if(typeof type === "string") {
631
- if(type==="string") return typeof value === "string";
632
- else if(type==="number") return typeof value === "number";
633
- else if(type==="integer") return typeof value === "number" && value === Math.floor(value);
634
- else if(type==="boolean") return typeof value === "boolean";
635
- else if(type==="array") return Array.isArray(value);
636
- else if(type === "object") return value !== null && !(Array.isArray(value)) && typeof value === "object";
637
- else if(type === "null") return value === null;
638
- else return true;
950
+ return []
951
+ }
952
+
953
+ _validateCustomValidator (schema, value, path) {
954
+ const errors = []
955
+ /* Internal validators using the custom validator format */
956
+ errors.push(...ipValidator.call(this, schema, value, path, this.translate))
957
+
958
+ const validate = validator => {
959
+ errors.push(...validator.call(this, schema, value, path))
960
+ }
961
+ /* Custom type validation (global) */
962
+ this.defaults.custom_validators.forEach(validate)
963
+ /* Custom type validation (instance specific) */
964
+ if (this.options.custom_validators) {
965
+ this.options.custom_validators.forEach(validate)
966
+ }
967
+ return errors
968
+ }
969
+
970
+ _removeDuplicateErrors (errors) {
971
+ return errors.reduce((err, obj) => {
972
+ let first = true
973
+ if (!err) err = []
974
+ err.forEach(a => {
975
+ if (a.message === obj.message && a.path === obj.path && a.property === obj.property) {
976
+ a.errorcount++
977
+ first = false
978
+ }
979
+ })
980
+ if (first) {
981
+ obj.errorcount = 1
982
+ err.push(obj)
983
+ }
984
+ return err
985
+ }, [])
986
+ }
987
+
988
+ _checkType (type, value) {
989
+ const types = {
990
+ string: value => typeof value === 'string',
991
+ number: value => typeof value === 'number',
992
+ integer: value => typeof value === 'number' && value === Math.floor(value),
993
+ boolean: value => typeof value === 'boolean',
994
+ array: value => Array.isArray(value),
995
+ object: value => value !== null && !(Array.isArray(value)) && typeof value === 'object',
996
+ null: value => value === null
639
997
  }
640
- // Schema
641
- else {
642
- return !this._validateSchema(type,value).length;
998
+ /* Simple types */
999
+ if (typeof type === 'string') {
1000
+ if (types[type]) {
1001
+ return types[type](value)
1002
+ } else return true
1003
+ /* Schema */
1004
+ } else {
1005
+ return !this._validateSchema(type, value).length
643
1006
  }
644
1007
  }
645
- });
1008
+ }