Commit 21fe787e authored by Boris Kocherov's avatar Boris Kocherov

erp5_json_form: initial add

parent 93558472
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Folder" module="OFS.Folder"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>erp5_json_form</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Folder" module="OFS.Folder"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>json-schema</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
{
"id": "http://json-schema.org/draft-04/schema#",
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Core schema meta-schema",
"definitions": {
"schemaArray": {
"type": "array",
"minItems": 1,
"items": { "$ref": "#" }
},
"positiveInteger": {
"type": "integer",
"minimum": 0
},
"positiveIntegerDefault0": {
"allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ]
},
"simpleTypes": {
"enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
},
"stringArray": {
"type": "array",
"items": { "type": "string" },
"minItems": 1,
"uniqueItems": true
}
},
"type": "object",
"properties": {
"id": {
"type": "string"
},
"$schema": {
"type": "string"
},
"title": {
"type": "string"
},
"description": {
"type": "string"
},
"default": {},
"multipleOf": {
"type": "number",
"minimum": 0,
"exclusiveMinimum": true
},
"maximum": {
"type": "number"
},
"exclusiveMaximum": {
"type": "boolean",
"default": false
},
"minimum": {
"type": "number"
},
"exclusiveMinimum": {
"type": "boolean",
"default": false
},
"maxLength": { "$ref": "#/definitions/positiveInteger" },
"minLength": { "$ref": "#/definitions/positiveIntegerDefault0" },
"pattern": {
"type": "string",
"format": "regex"
},
"additionalItems": {
"anyOf": [
{ "type": "boolean" },
{ "$ref": "#" }
],
"default": {}
},
"items": {
"anyOf": [
{ "$ref": "#" },
{ "$ref": "#/definitions/schemaArray" }
],
"default": {}
},
"maxItems": { "$ref": "#/definitions/positiveInteger" },
"minItems": { "$ref": "#/definitions/positiveIntegerDefault0" },
"uniqueItems": {
"type": "boolean",
"default": false
},
"maxProperties": { "$ref": "#/definitions/positiveInteger" },
"minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" },
"required": { "$ref": "#/definitions/stringArray" },
"additionalProperties": {
"anyOf": [
{ "type": "boolean" },
{ "$ref": "#" }
],
"default": {}
},
"definitions": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"properties": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"patternProperties": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"dependencies": {
"type": "object",
"additionalProperties": {
"anyOf": [
{ "$ref": "#" },
{ "$ref": "#/definitions/stringArray" }
]
}
},
"enum": {
"type": "array",
"minItems": 1,
"uniqueItems": true
},
"type": {
"anyOf": [
{ "$ref": "#/definitions/simpleTypes" },
{
"type": "array",
"items": { "$ref": "#/definitions/simpleTypes" },
"minItems": 1,
"uniqueItems": true
}
]
},
"format": { "type": "string" },
"allOf": { "$ref": "#/definitions/schemaArray" },
"anyOf": { "$ref": "#/definitions/schemaArray" },
"oneOf": { "$ref": "#/definitions/schemaArray" },
"not": { "$ref": "#" }
},
"dependencies": {
"exclusiveMaximum": [ "maximum" ],
"exclusiveMinimum": [ "minimum" ]
},
"default": {}
}
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="File" module="OFS.Image"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>__name__</string> </key>
<value> <string>schema4.json</string> </value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>application/json</string> </value>
</item>
<item>
<key> <string>precondition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>schema4.json</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
{
"$schema": "http://json-schema.org/draft-06/schema#",
"$id": "http://json-schema.org/draft-06/schema#",
"title": "Core schema meta-schema",
"definitions": {
"schemaArray": {
"type": "array",
"minItems": 1,
"items": { "$ref": "#" }
},
"nonNegativeInteger": {
"type": "integer",
"minimum": 0
},
"nonNegativeIntegerDefault0": {
"allOf": [
{ "$ref": "#/definitions/nonNegativeInteger" },
{ "default": 0 }
]
},
"simpleTypes": {
"enum": [
"array",
"boolean",
"integer",
"null",
"number",
"object",
"string"
]
},
"stringArray": {
"type": "array",
"items": { "type": "string" },
"uniqueItems": true,
"default": []
}
},
"type": ["object", "boolean"],
"properties": {
"$id": {
"type": "string",
"format": "uri-reference"
},
"$schema": {
"type": "string",
"format": "uri"
},
"$ref": {
"type": "string",
"format": "uri-reference"
},
"title": {
"type": "string"
},
"description": {
"type": "string"
},
"default": {},
"examples": {
"type": "array",
"items": {}
},
"multipleOf": {
"type": "number",
"exclusiveMinimum": 0
},
"maximum": {
"type": "number"
},
"exclusiveMaximum": {
"type": "number"
},
"minimum": {
"type": "number"
},
"exclusiveMinimum": {
"type": "number"
},
"maxLength": { "$ref": "#/definitions/nonNegativeInteger" },
"minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
"pattern": {
"type": "string",
"format": "regex"
},
"additionalItems": { "$ref": "#" },
"items": {
"anyOf": [
{ "$ref": "#" },
{ "$ref": "#/definitions/schemaArray" }
],
"default": {}
},
"maxItems": { "$ref": "#/definitions/nonNegativeInteger" },
"minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
"uniqueItems": {
"type": "boolean",
"default": false
},
"contains": { "$ref": "#" },
"maxProperties": { "$ref": "#/definitions/nonNegativeInteger" },
"minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
"required": { "$ref": "#/definitions/stringArray" },
"additionalProperties": { "$ref": "#" },
"definitions": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"properties": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"patternProperties": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"dependencies": {
"type": "object",
"additionalProperties": {
"anyOf": [
{ "$ref": "#" },
{ "$ref": "#/definitions/stringArray" }
]
}
},
"propertyNames": { "$ref": "#" },
"const": {},
"enum": {
"type": "array",
"minItems": 1,
"uniqueItems": true
},
"type": {
"anyOf": [
{ "$ref": "#/definitions/simpleTypes" },
{
"type": "array",
"items": { "$ref": "#/definitions/simpleTypes" },
"minItems": 1,
"uniqueItems": true
}
]
},
"format": { "type": "string" },
"allOf": { "$ref": "#/definitions/schemaArray" },
"anyOf": { "$ref": "#/definitions/schemaArray" },
"oneOf": { "$ref": "#/definitions/schemaArray" },
"not": { "$ref": "#" }
},
"default": {}
}
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="File" module="OFS.Image"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>__name__</string> </key>
<value> <string>schema6.json</string> </value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>application/json</string> </value>
</item>
<item>
<key> <string>precondition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>schema6.json</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "http://json-schema.org/draft-07/schema#",
"title": "Core schema meta-schema",
"definitions": {
"schemaArray": {
"type": "array",
"minItems": 1,
"items": { "$ref": "#" }
},
"nonNegativeInteger": {
"type": "integer",
"minimum": 0
},
"nonNegativeIntegerDefault0": {
"allOf": [
{ "$ref": "#/definitions/nonNegativeInteger" },
{ "default": 0 }
]
},
"simpleTypes": {
"enum": [
"array",
"boolean",
"integer",
"null",
"number",
"object",
"string"
]
},
"stringArray": {
"type": "array",
"items": { "type": "string" },
"uniqueItems": true,
"default": []
}
},
"type": ["object", "boolean"],
"properties": {
"$id": {
"type": "string",
"format": "uri-reference"
},
"$schema": {
"type": "string",
"format": "uri"
},
"$ref": {
"type": "string",
"format": "uri-reference"
},
"$comment": {
"type": "string"
},
"title": {
"type": "string"
},
"description": {
"type": "string"
},
"default": true,
"readOnly": {
"type": "boolean",
"default": false
},
"examples": {
"type": "array",
"items": true
},
"multipleOf": {
"type": "number",
"exclusiveMinimum": 0
},
"maximum": {
"type": "number"
},
"exclusiveMaximum": {
"type": "number"
},
"minimum": {
"type": "number"
},
"exclusiveMinimum": {
"type": "number"
},
"maxLength": { "$ref": "#/definitions/nonNegativeInteger" },
"minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
"pattern": {
"type": "string",
"format": "regex"
},
"additionalItems": { "$ref": "#" },
"items": {
"anyOf": [
{ "$ref": "#" },
{ "$ref": "#/definitions/schemaArray" }
],
"default": true
},
"maxItems": { "$ref": "#/definitions/nonNegativeInteger" },
"minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
"uniqueItems": {
"type": "boolean",
"default": false
},
"contains": { "$ref": "#" },
"maxProperties": { "$ref": "#/definitions/nonNegativeInteger" },
"minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
"required": { "$ref": "#/definitions/stringArray" },
"additionalProperties": { "$ref": "#" },
"definitions": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"properties": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"patternProperties": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"propertyNames": { "format": "regex" },
"default": {}
},
"dependencies": {
"type": "object",
"additionalProperties": {
"anyOf": [
{ "$ref": "#" },
{ "$ref": "#/definitions/stringArray" }
]
}
},
"propertyNames": { "$ref": "#" },
"const": true,
"enum": {
"type": "array",
"items": true,
"minItems": 1,
"uniqueItems": true
},
"type": {
"anyOf": [
{ "$ref": "#/definitions/simpleTypes" },
{
"type": "array",
"items": { "$ref": "#/definitions/simpleTypes" },
"minItems": 1,
"uniqueItems": true
}
]
},
"format": { "type": "string" },
"contentMediaType": { "type": "string" },
"contentEncoding": { "type": "string" },
"if": {"$ref": "#"},
"then": {"$ref": "#"},
"else": {"$ref": "#"},
"allOf": { "$ref": "#/definitions/schemaArray" },
"anyOf": { "$ref": "#/definitions/schemaArray" },
"oneOf": { "$ref": "#/definitions/schemaArray" },
"not": { "$ref": "#" }
},
"default": true
}
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="File" module="OFS.Image"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>__name__</string> </key>
<value> <string>schema7.json</string> </value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>application/json</string> </value>
</item>
<item>
<key> <string>precondition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>schema7.json</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
#shared
rsvp.js
renderjs.js
handlebars.js
gadget_erp5_nojqm.css
gadget_erp5_global.js
gadget_html5_select.html
gadget_html5_select.js
#jsonform
jio.js
gadget_html5_select.html
gadget_html5_select.js
json-schema/schema4.json
json-schema/schema6.json
json-schema/schema7.json
jsonform.gadget.html
jsonform.gadget.js
jsonform/gadget_json_generated_form_child.html
jsonform/gadget_json_generated_form_child.js
jsonform/tv4.js
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="File" module="OFS.Image"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>__name__</string> </key>
<value> <string>jsonform.gadget.appcache</string> </value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/cache-manifest</string> </value>
</item>
<item>
<key> <string>precondition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>jsonform.gadget.appcache</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<!DOCTYPE html>
<html manifest="jsonform.gadget.appcache">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ERP5</title>
<link rel="stylesheet" href="gadget_erp5_nojqm.css">
<script src="rsvp.js" type="text/javascript"></script>
<script src="jsonform/tv4.js" type="text/javascript"></script>
<script src="renderjs.js" type="text/javascript"></script>
<script src="jio.js" type="text/javascript"></script>
<script src="jsonform.gadget.js" type="text/javascript"></script>
</head>
<body>
</body>
</html>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="File" module="OFS.Image"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>__name__</string> </key>
<value> <string>jsonform.gadget.html</string> </value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>precondition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>schema.gadget.html</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
/*jslint nomen: true, maxlen: 200, indent: 2, maxerr: 100*/
/*global window, document, URL, rJS, RSVP, jIO, tv4, location */
(function (window, document, location, rJS, RSVP, jIO, tv4) {
"use strict";
var expandSchema;
function decodeJsonPointer(_str) {
// https://tools.ietf.org/html/rfc6901#section-5
return _str.replace(/~1/g, '/').replace(/~0/g, '~');
}
function encodeJsonPointer(_str) {
// https://tools.ietf.org/html/rfc6901#section-5
return _str.replace(/~/g, '~0').replace(/\//g, '~1');
}
function getMaxPathInDict(dict, path) {
var target,
key,
max_len = 0;
if (!path) {
return "";
}
for (key in dict) {
if (dict.hasOwnProperty(key) &&
path.startsWith(key) &&
key.length > max_len) {
target = key;
max_len = key.length;
}
}
return target;
}
function checkCircular(g, path, url) {
var required_stack,
idx,
prev_field_path = getMaxPathInDict(g.props.schema_required_urls, path);
required_stack = g.props.schema_required_urls[prev_field_path] || [];
idx = required_stack.indexOf(url);
if (idx >= 0) {
if (path === prev_field_path && idx === 0) {
return;
}
throw new Error("Circular reference detected");
}
g.props.schema_required_urls[path] = [url].concat(required_stack);
}
function convertToRealWorldSchemaPath(g, path) {
var url,
hash,
map = g.props.schema_map,
prev_downl_path,
max_len = 0;
if (!path) {
return "";
}
// previous downloaded path
prev_downl_path = getMaxPathInDict(map, path);
if (prev_downl_path === undefined) {
url = "";
max_len = 0;
} else {
url = map[prev_downl_path];
if (prev_downl_path === "/") {
max_len = 0;
} else {
max_len = prev_downl_path.length;
}
}
hash = path.substr(max_len);
if (hash) {
// XXX urlencode for hash
if (url.indexOf("#") >= 0) {
url = url + hash;
} else {
url = url + "#" + hash;
}
}
return url;
}
function convertUrlToAbsolute(g, path, url, base_url_failback) {
var // previous downloaded path
base_url = convertToRealWorldSchemaPath(g, path),
absolute_url;
if (base_url === "" || base_url.indexOf("#") === 0) {
absolute_url = new URL(url, base_url_failback);
} else {
absolute_url = new URL(url, base_url);
}
return absolute_url;
}
function downloadJSON(url) {
return RSVP.Queue()
.push(function () {
return jIO.util.ajax({
url: url,
dataType: "json"
});
})
.push(function (evt) {
return evt.target.response;
});
}
function resolveLocalReference(schema, ref) {
// 2 here is for #/
var i, ref_path = ref.substr(2, ref.length),
parts = ref_path.split("/");
if (parts.length === 1 && parts[0] === "") {
// It was uses #/ to reference the entire json so just return it.
return schema;
}
for (i = 0; i < parts.length; i += 1) {
if (schema === undefined) {
throw new Error("local ref `" + ref + "` does not exist in:");
}
schema = schema[decodeJsonPointer(parts[i])];
}
return schema;
}
function schemaPushSchemaPart(schema, schema_path, schema_part) {
var i,
k,
key_list;
if (schema_path === "/") {
schema_path = "";
}
key_list = schema_path.split("/");
for (i = 0; i < key_list.length; i += 1) {
k = decodeJsonPointer(key_list[i]);
if (i === key_list.length - 1) {
if (schema_part !== undefined) {
schema[k] = schema_part;
} else {
return schema[k];
}
} else {
if (!schema.hasOwnProperty(k)) {
schema[k] = {};
}
schema = schema[k];
}
}
}
function loadJSONSchema(g, $ref, path) {
var protocol,
url,
download_url,
hash,
schema_url_map;
// XXX need use `id` property
if (!path) {
path = "/";
}
url = convertUrlToAbsolute(g, path, $ref, window.location);
download_url = url.origin + url.pathname;
schema_url_map = {
"http://json-schema.org/draft-04/schema": "json-schema/schema4.json",
"http://json-schema.org/draft-06/schema": "json-schema/schema6.json",
"http://json-schema.org/draft-07/schema": "json-schema/schema7.json",
"http://json-schema.org/schema": "json-schema/schema7.json"
};
if (schema_url_map.hasOwnProperty(download_url)) {
url = new URL(schema_url_map[download_url], g.__path);
download_url = url.origin + url.pathname;
}
protocol = url.protocol;
if (protocol === "http:" || protocol === "https:") {
if (window.location.protocol !== protocol) {
throw new Error("You cannot mixed http and https calls");
}
}
hash = url.hash;
url = url.href;
return downloadJSON(download_url)
.push(function (json) {
checkCircular(g, path, url);
return resolveLocalReference(json, hash);
})
.push(undefined, function (err) {
// XXX it will be great to have ability convert json_pointers(hash)
// in line numbers for pointed to line in rich editors.
// we can use https://github.com/vtrushin/json-to-ast for it
var url_from_pointed = convertToRealWorldSchemaPath(g, path),
schema_a = document.createElement("a"),
pointed_a = document.createElement("a");
schema_a.setAttribute("href", download_url);
schema_a.text = (new URL(download_url)).pathname;
pointed_a.setAttribute("href", url_from_pointed);
pointed_a.text = (new URL(url_from_pointed)).pathname;
g.props.schema_resolve_errors[url_from_pointed] = {
schemaPath: path,
message: [
document.createTextNode("schema error: "),
document.createTextNode(err.message),
schema_a,
document.createTextNode(" pointed from schema: "),
pointed_a
]
};
return null; // schema part can't be null
})
.push(function (schema_part) {
// console.log(path);
if (schema_part === null) {
// if resolving schema part contain errors
// use {} as failback
schema_part = {};
} else {
// save map url only for correctly resolved schema
// otherwise we have issue in convertToRealWorldSchemaPath
g.props.schema_map[path] = url;
}
schemaPushSchemaPart(g.props.schema, path, JSON.parse(JSON.stringify(schema_part)));
// console.log(g.props.schema[""]);
return expandSchema(g, schema_part, path, $ref);
})
.push(function (schema_arr) {
// if length array > 1 form rendered on demand already
// so not needed circular detection
if (schema_arr.length === 1) {
// XXX need smart circular detection in this place
schema_arr[0].circular = true;
}
return schema_arr;
});
}
function allOf(g, schema_array, schema_path) {
return RSVP.Queue()
.push(function () {
var i,
arr = [];
for (i = 0; i < schema_array.length; i += 1) {
arr.push(expandSchema(g, schema_array[i], schema_path + '/allOf/' + i.toString()));
}
return RSVP.all(arr);
})
.push(function (arr) {
var i,
x,
y,
key,
next_schema,
schema,
schema_item,
summ_arr;
for (i = 0; i < arr.length - 1; i += 1) {
summ_arr = [];
for (x = 0; x < arr[i].length; x += 1) {
for (y = 0; y < arr[i + 1].length; y += 1) {
schema = arr[i][x].schema;
next_schema = arr[i + 1][y].schema;
if (schema === true && next_schema === true) {
schema_item = {
schema: true,
schema_path: arr[i][x].schema_path
};
} else if (schema === false || next_schema === false) {
schema_item = {
schema: false,
schema_path: arr[i][x].schema_path
};
} else {
if (schema === true) {
schema = {};
}
if (next_schema === true) {
next_schema = {};
}
// copy before change
schema = JSON.parse(JSON.stringify(schema));
for (key in next_schema) {
if (next_schema.hasOwnProperty(key)) {
if (schema.hasOwnProperty(key)) {
// XXX need use many many rules for merging
schema[key] = next_schema[key];
} else {
schema[key] = next_schema[key];
}
}
}
schema_item = {
schema: schema,
schema_path: arr[i][x].schema_path
};
}
summ_arr.push(schema_item);
}
}
arr[i + 1] = summ_arr;
}
return arr[arr.length - 1];
});
}
function anyOf(g, schema_array, schema_path) {
return RSVP.Queue()
.push(function () {
var i,
arr = [];
for (i = 0; i < schema_array.length; i += 1) {
arr.push(expandSchema(g, schema_array[i], schema_path + '/anyOf/' + i.toString()));
}
return RSVP.all(arr);
})
.push(function (arr) {
var i,
z,
schema_arr = [];
for (i = 0; i < arr.length; i += 1) {
for (z = 0; z < arr[i].length; z += 1) {
if (arr[i][z].schema === true) {
// or(any, restricted, restricted, .. ) simplify to any
return [arr[i][z]];
}
schema_arr.push(arr[i][z]);
}
}
return schema_arr;
});
}
expandSchema = function (g, schema, schema_path, ref) {
// XXX `if then else` construction can be simplify to
// anyOf(allOf(if_schema, then_schema), else_schema)
// and realized by existed rails
if (schema === undefined) {
schema = true;
}
if (schema.anyOf !== undefined) {
return anyOf(g, schema.anyOf, schema_path);
}
if (schema.allOf !== undefined) {
return allOf(g, schema.allOf, schema_path);
}
if (schema.$ref) {
return loadJSONSchema(g, schema.$ref, schema_path);
}
return RSVP.Queue()
.push(function () {
return [{
title: ref || schema.title,
schema: schema,
schema_path: schema_path
}];
});
};
function expandSchemaForField(g, schema, schema_path, for_required) {
var required_stack,
prev_field_path;
if (for_required) {
prev_field_path = getMaxPathInDict(g.props.schema_required_urls, schema_path);
required_stack = g.props.schema_required_urls[prev_field_path];
} else {
required_stack = [];
}
g.props.schema_required_urls[schema_path] = required_stack;
return expandSchema(g, schema, schema_path);
}
rJS(window)
.ready(function () {
var g = this;
g.props = {};
g.options = {};
})
.declareAcquiredMethod("notifyChange", "notifyChange")
.allowPublicAcquisition("notifyChange", function () {
return this.notifyChange();
})
.declareAcquiredMethod("notifyValid", "notifyValid")
.declareAcquiredMethod("notifyInvalid", "notifyInvalid")
.allowPublicAcquisition("checkValidity", function (arr) {
return this.checkValidity(arr[0]);
})
.declareMethod('checkValidity', function (json_document) {
// XXX need use local schema and local json document
// in every subgadget to take into account user anyOf choice
// and so more precisely point to issue
var g = this.props.form_gadget,
gadget = this;
return RSVP.Queue()
.push(function () {
if (json_document === undefined) {
return g.getContent();
}
return json_document;
})
.push(function (json_d) {
return tv4.validateMultiple(json_d, gadget.props.schema[""]);
})
.push(function (validation) {
var i,
error_id,
error,
span,
tasks = [],
errors = [],
schema_resolve_errors = gadget.props.schema_resolve_errors,
errors_block = g.element.querySelector("div.error-block");
if (errors_block) {
errors_block.parentNode.removeChild(errors_block);
}
g.element.querySelectorAll(".error").forEach(function (error_message) {
error_message.textContent = "";
error_message.removeAttribute("id");
error_message.hidden = true;
});
g.element.querySelectorAll("div.error-input").forEach(function (div) {
div.setAttribute("class", "");
});
for (i in schema_resolve_errors) {
if (schema_resolve_errors.hasOwnProperty(i)) {
errors.push(schema_resolve_errors[i]);
}
}
errors = errors.concat(validation.errors);
errors = errors.concat(validation.missing);
if (errors.length === 0) {
return gadget.notifyValid()
.push(function () {
return false;
});
}
span = document.createElement("span");
span.setAttribute("class", "error");
span.textContent = "errors: ";
errors_block = document.createElement("div");
errors_block.setAttribute("class", "subfield error-block");
errors_block.appendChild(span);
function print_error(error, errorUid, errorId) {
return function (element) {
var id = element.id,
error_message,
createTextNode = document.createTextNode.bind(document),
a = document.createElement("a");
a.setAttribute("href", "#" + errorUid);
a.text = errorId;
element.setAttribute("class", "error-input");
error_message = element.querySelector("#" + id.replace(/\//g, "\\/") + " > .error");
error_message.appendChild(a);
error_message.setAttribute("id", errorUid);
if (error.message instanceof Array) {
error.message.forEach(function (x) {
error_message.appendChild(x);
});
} else {
error_message.appendChild(createTextNode(error.message));
}
error_message.hidden = false;
a = document.createElement("a");
a.text = errorId;
a.setAttribute("data-error-link", "#" + errorUid);
a.setAttribute("class", "error-link");
if (errorId !== "1") {
errors_block.appendChild(createTextNode(","));
}
errors_block.appendChild(a);
};
}
for (i = 0; i < errors.length; i += 1) {
error = errors[i];
error_id = (i + 1).toString();
tasks.push(
g.getElementByPath(error.dataPath || "/")
.push(print_error(error, "error" + error_id, error_id))
);
}
return RSVP.Queue()
.push(function () {
return RSVP.all(tasks);
})
.push(function () {
g.element.insertBefore(errors_block, g.element.firstChild);
})
.push(gadget.notifyInvalid.bind(gadget))
.push(function () {
return false;
});
});
})
.declareMethod('render', function (options) {
return this.changeState({
key: options.key,
value: options.value || "",
schema: options.schema,
schema_url: options.schema_url,
editable: options.editable === undefined ? true : options.editable
});
})
.onStateChange(function (options) {
var g = this;
g.props.toplevel = true;
// contain map of current normalized schema
// json pointer and corresponding url
// it's need for schema uri computation
g.props.schema = {};
g.props.schema_map = {};
// schema_required_urls[path] = [
// stack required urls, on every unrequired field stack begining from []
// "url1",
// "url2"
// ]
g.props.schema_required_urls = {};
// schema_resolve_errors[schema_url] = {
// schemaPath: local_schema_path,
// message: error_message can be array containing dom elements
// }
g.props.schema_resolve_errors = {};
return RSVP.Queue()
.push(function () {
if (!g.props.form_gadget) {
return g.declareGadget('jsonform/gadget_json_generated_form_child.html',
{scope: "j" + Math.random().toString(36).substr(2, 9)})
.push(function (json_form_child) {
g.props.form_gadget = json_form_child;
g.element.appendChild(json_form_child.element);
});
}
})
.push(function () {
if (options.schema) {
return options.schema;
}
var schema_url = options.schema_url ||
(options.value && options.value.$schema);
if (schema_url) {
return loadJSONSchema(g, schema_url)
.push(function (schema_arr) {
return schema_arr[0].schema;
});
}
return {};
})
.push(function (schema) {
g.options.schema = schema;
return g.props.form_gadget.renderForm({
schema: schema,
schema_path: "",
document: options.value,
required: true,
top: true
});
})
.push(function () {
return g.checkValidity();
})
.push(function () {
return g;
});
})
.allowPublicAcquisition("expandSchema", function (arr) {
return expandSchemaForField(this, arr[0], arr[1], arr[2]);
})
.onEvent('click', function (evt) {
if (evt.target === this.props.delete_button) {
return this.selfRemove(evt);
}
var link = evt.target.getAttribute("data-error-link"),
button_list = this.props.add_buttons,
i;
if (link) {
location.href = link;
return;
}
for (i = 0; i < button_list.length; i = i + 1) {
if (evt.target === button_list[i].element) {
return button_list[i].event(evt);
}
}
})
.declareJob('listenEvents', function () {
// XXX Disable
return;
})
.declareMethod('getContent', function () {
var g = this;
if (g.state.editable) {
return g.props.form_gadget.getContent()
.push(function (value) {
// Change the value state in place
// This will prevent the gadget to be changed if
// its parent call render with the same value
// (as ERP5 does in case of formulator error)
g.state.value = value;
if (g.state.key) {
var form_data = {};
value = JSON.stringify(value);
form_data[g.state.key] = value;
return form_data;
}
return value;
});
}
return {};
});
}(window, document, location, rJS, RSVP, jIO, tv4));
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="File" module="OFS.Image"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>__name__</string> </key>
<value> <string>jsonform.gadget.js</string> </value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>application/javascript</string> </value>
</item>
<item>
<key> <string>precondition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>schema.gadget.js</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Folder" module="OFS.Folder"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>jsonform</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ERP5</title>
<link rel="stylesheet" href="../gadget_erp5_nojqm.css">
<script src="../rsvp.js" type="text/javascript"></script>
<script src="tv4.js" type="text/javascript"></script>
<script src="../renderjs.js" type="text/javascript"></script>
<script src="../jio.js" type="text/javascript"></script>
<script src="gadget_json_generated_form_child.js" type="text/javascript"></script>
</head>
<body>
<div data-json-path="/">
</div>
</body>
</html>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="File" module="OFS.Image"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>__name__</string> </key>
<value> <string>gadget_json_generated_form_child.html</string> </value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>precondition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>gadget_json_generated_form_child.html</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
/*jslint nomen: true, maxlen: 200, indent: 2, maxerr: 100*/
/*global window, document, URL, rJS, RSVP, jIO, tv4, location */
(function (window, document, location, rJS, RSVP, jIO, tv4) {
"use strict";
var render_object;
function decodeJsonPointer(_str) {
// https://tools.ietf.org/html/rfc6901#section-5
return _str.replace(/~1/g, '/').replace(/~0/g, '~');
}
function encodeJsonPointer(_str) {
// https://tools.ietf.org/html/rfc6901#section-5
return _str.replace(/~/g, '~0').replace(/\//g, '~1');
}
function getDocumentType(doc) {
if (doc instanceof Array) {
return "array";
}
return typeof doc;
}
function createElement(type, props) {
var element = document.createElement(type),
key;
for (key in props) {
if (props.hasOwnProperty(key)) {
element.setAttribute(key, props[key]);
}
}
return element;
}
function getDocumentSchema(doc) {
var type = getDocumentType(doc),
schema = {
type: type
};
if (type === "array") {
schema.maxItems = 0;
} else if (type === "object") {
schema.additionalProperties = false;
} else {
schema.readOnly = true;
}
return schema;
}
function render_selection(schema, json_document) {
var input = document.createElement("select"),
option,
i,
enum_arr = schema['enum'];
input.size = 1;
if (schema.default) {
if (json_document === undefined) {
json_document = schema.default;
}
} else {
option = document.createElement("option");
option.value = "";
if (json_document === undefined) {
option.selected = true;
}
input.appendChild(option);
}
for (i = 0; i < enum_arr.length; i += 1) {
if (enum_arr.hasOwnProperty(i)) {
option = document.createElement("option");
option.value = enum_arr[i];
option.textContent = enum_arr[i];
if (enum_arr[i] === json_document) {
option.selected = true;
}
input.appendChild(option);
}
}
return input;
}
function render_boolean(schema, json_document) {
var input,
schema_for_selection = {
type: "boolean",
enum: [true, false]
};
// XXX change json_document on open is not correct @bk
if (json_document === "true") {
json_document = true;
}
if (json_document === "false") {
json_document = false;
}
if (getDocumentType(schema.default) === "boolean") {
schema_for_selection.default = schema.default;
}
input = render_selection(schema_for_selection, json_document);
input.setAttribute('data-json-type', "boolean");
return input;
}
function render_textarea(json_field, default_value, data_format) {
var input = document.createElement("textarea");
if (default_value !== undefined) {
if (default_value instanceof Array) {
input.value = default_value.join("\n");
} else {
input.value = default_value;
}
}
input["data-format"] = data_format;
return input;
}
function addSubForm(options) {
var input_element = options.element,
g = options.gadget,
property_name,
parent_path,
scope;
scope = "j" + Math.random().toString(36).substr(2, 9);
if (options.parent_type !== "array") {
parent_path = options.path;
property_name = options.property_name;
if (!property_name) {
property_name = input_element.value;
}
if (!property_name) {
// XXX notify user
// you can't create property without property_name
return RSVP.Queue();
}
if (g.props.objects[parent_path].hasOwnProperty(property_name) && g.props.objects[parent_path][property_name] !== "") {
// XXX notify user
// you can't create property with existed property_name
return RSVP.Queue();
}
if (input_element) {
input_element.value = "";
}
}
return g.declareGadget('gadget_json_generated_form_child.html', {scope: scope})
.push(function (form_gadget) {
form_gadget.element.setAttribute("data-gadget-parent-scope",
g.element.getAttribute("data-gadget-scope"));
if (options.parent_type !== "array") {
g.props.objects[parent_path][property_name] = scope;
form_gadget.element.setAttribute("data-json-parent", parent_path);
form_gadget.element.setAttribute("data-json-property-name", property_name);
}
return form_gadget.renderForm({
type: options.type,
required: options.required,
delete_button: options.delete_button,
schema: options.schema_part,
schema_path: options.schema_path,
document: options.default_dict,
display_label: options.parent_type !== "array",
scope: scope
});
});
}
function expandProperties(g, properties, schema_path, required) {
var ret_obj = {};
return RSVP.Queue()
.push(function () {
var property_name,
arr = [];
function addPropertyName(p_name) {
return function (schema_array) {
ret_obj[p_name] = schema_array;
};
}
for (property_name in properties) {
if (properties.hasOwnProperty(property_name)) {
arr.push(
g.expandSchema(
properties[property_name],
schema_path + encodeJsonPointer(property_name),
required.indexOf(property_name) >= 0
)
.push(addPropertyName(property_name))
);
}
}
return RSVP.all(arr);
})
.push(function () {
return ret_obj;
});
}
function checkSchemaArrOneChoise(schema_arr) {
if (schema_arr.length === 1) {
if (schema_arr[0].schema === true) {
return false;
}
if (schema_arr[0].schema.type instanceof Array) {
return schema_arr[0].schema.type.length <= 1;
}
return true;
}
return false;
}
function checkSchemaSimpleType(schema) {
return [
'string',
'integer',
'number',
'boolean',
'null'
].indexOf(schema.type) >= 0;
}
function convertExpandedProperties2array(properties) {
var property_name,
arr = [],
i,
schema_array;
for (property_name in properties) {
if (properties.hasOwnProperty(property_name)) {
schema_array = properties[property_name];
for (i = 0; i < schema_array.length; i += 1) {
// add propertyName to title
if (schema_array[i].title && schema_array.length > 1) {
schema_array[i].title = property_name + ' /' + schema_array[i].title;
} else {
schema_array[i].title = property_name;
}
// add propertyName to schemaItem
schema_array[i].property_name = property_name;
arr.push(schema_array[i]);
}
}
}
return arr;
}
function schemaArrFilteredByDocument(schema_arr, json_document) {
var i,
flag,
ret_arr = [],
schema;
if (schema_arr.length === 1) {
return schema_arr[0];
}
if (json_document !== undefined) {
for (i = 0; i < schema_arr.length; i += 1) {
schema = schema_arr[i].schema;
if (schema === true) {
flag = true;
} else if (schema === false) {
flag = false;
} else {
flag = tv4.validate(json_document, schema);
}
if (flag) {
ret_arr.push(schema_arr[i]);
}
}
if (ret_arr.length === 0) {
// XXX find schema more compatible with document
return schema_arr[0];
}
}
// XXX if (ret_arr.length > 1) notify user
return ret_arr[0];
}
function checkValidityAndNotifyChange(g) {
return RSVP.all([
g.checkValidity(),
g.notifyChange()
]);
}
function render_schema_selector(gadget, title, schema_arr, event, rerender) {
return RSVP.Queue()
.push(function () {
var schema_alternatives = [],
schema_item,
description,
i,
z,
type;
function generateItemsForAny(property_name, schema_path) {
var desc,
types = [
"string",
"number",
"boolean",
"array",
"object",
"null"
],
ii;
if (property_name) {
desc = property_name + " # ";
} else {
desc = "";
}
for (ii = 0; ii < types.length; ii += 1) {
schema_alternatives.push({
title: desc + types[ii],
value: {
property_name: property_name,
schema: { type: types[ii] },
schema_path: schema_path
}
});
}
}
for (i = 0; i < schema_arr.length; i += 1) {
schema_item = schema_arr[i];
description = schema_item.title;
if (schema_item.schema === true) {
generateItemsForAny(schema_item.property_name, schema_item.schema_path);
} else if (getDocumentType(schema_item.schema.type) === "array") {
description = description || schema_item.schema.description;
for (z = 0; z < schema_item.schema.type.length; z += 1) {
type = schema_item.schema.type[z];
schema_alternatives.push({
title: description + ' # ' + type,
value: {
type: type,
property_name: schema_item.property_name,
schema_path: schema_item.schema_path,
schema: schema_item.schema
}
});
}
} else {
description = description ||
schema_item.schema.type ||
schema_item.schema.description;
schema_alternatives.push({
title: description,
value: {
property_name: schema_item.property_name,
schema_path: schema_item.schema_path,
schema: schema_item.schema
}
});
}
}
return schema_alternatives;
})
.push(function (schema_alternatives) {
var scope = 's' + Math.random().toString(36).substr(2, 9);
if (schema_alternatives.length > 1) {
return gadget.declareGadget("../gadget_html5_select.html", {scope: scope})
.push(function (g) {
return RSVP.Queue()
.push(function () {
var x,
item_list = [[title, title]],
item;
if (rerender) {
return rerender(g, schema_alternatives);
}
for (x = 0; x < schema_alternatives.length; x += 1) {
item = schema_alternatives[x];
item_list.push([item.title, x]);
}
return {
name: scope,
editable: true,
hidden: item_list.length === 0,
value: item_list[0][1],
item_list: item_list
};
})
.push(function (render_options) {
gadget.props.add_custom_data[scope] = {
element: g.element,
event: function () {
return g.getContent()
.push(function (value) {
return event(schema_alternatives[value[scope]].value);
})
.push(function () {
return checkValidityAndNotifyChange(gadget);
})
.push(function () {
if (rerender) {
return rerender(g, schema_alternatives);
}
return render_options;
})
.push(function (render_options) {
return g.render(render_options);
});
},
rerender: function () {
return RSVP.Queue()
.push(function () {
if (rerender) {
return rerender(g, schema_alternatives);
}
return render_options;
})
.push(function (render_options) {
return g.render(render_options);
});
}
};
return g.render(render_options);
})
//not need if gadget_html5_select.render return element
.push(function () {
return g.element;
});
});
}
if (schema_alternatives.length === 1) {
return RSVP.Queue()
.push(function () {
if (rerender) {
return rerender(undefined, schema_alternatives);
}
return true;
})
.push(function (ret) {
var input = document.createElement("button");
input.setAttribute("class", "ui-btn-icon-notext ui-icon-plus");
input.type = "button";
input.title = title;
if (!ret) {
input.setAttribute("style", "display: none;");
}
gadget.props.add_buttons.push({
element: input,
event: function () {
return event(schema_alternatives[0].value)
.push(function () {
if (rerender) {
return rerender(undefined, schema_alternatives);
}
return true;
})
.push(function (r) {
if (!r) {
input.setAttribute("style", "display: none;");
} else {
input.removeAttribute("style");
}
return checkValidityAndNotifyChange(gadget);
});
},
rerender: function () {
return RSVP.Queue()
.push(function () {
if (rerender) {
return rerender(undefined, schema_alternatives);
}
return true;
})
.push(function (r) {
if (!r) {
input.setAttribute("style", "display: none;");
} else {
input.removeAttribute("style");
}
});
}
});
return input;
});
}
return RSVP.Queue()
.push(function () {
return document.createElement("div");
});
});
}
function render_array(gadget, schema, json_document, root, path, schema_path) {
var div,
div_input,
input,
minItems = schema.minItems || 0;
div = document.createElement("div");
div.setAttribute("class", "jsonformfield");
div.title = schema.description;
div_input = document.createElement("div");
div_input.setAttribute("class", "input");
function element_append(child) {
if (child) {
input.parentNode.insertBefore(child, input);
}
}
function div_append(child) {
if (child) {
div_input.appendChild(child);
}
}
// XXX add failback rendering if json_document not array
// input = render_textarea(schema, default_value, "array");
return gadget.expandSchema(schema.items, schema_path + '/items', minItems !== 0)
.push(function (schema_arr) {
var queue = RSVP.Queue(),
i,
len = 0;
// XXX rewrite loading document for anyOf schema
if (json_document) {
for (i = 0; i < json_document.length; i = i + 1) {
queue
.push(
addSubForm.bind(gadget, {
gadget: gadget,
parent_type: 'array',
schema_path: schema_path + '/items',
schema_part: schema_arr,
default_dict: json_document[i],
required: i < minItems
})
)
.push(div_append);
}
len = json_document.length;
}
if (checkSchemaArrOneChoise(schema_arr) && minItems > len) {
for (i = 0; i < (minItems - len); i += 1) {
queue
.push(
addSubForm.bind(gadget, {
gadget: gadget,
parent_type: 'array',
schema_path: schema_arr[0].schema_path,
schema_part: schema_arr[0].schema,
required: true
})
)
.push(div_append);
}
}
queue.push(render_schema_selector.bind(gadget,
gadget, "add item to array",
schema_arr, function (value) {
return addSubForm({
gadget: gadget,
parent_type: 'array',
type: value.type,
schema_path: value.schema_path,
schema_part: value.schema
})
.push(element_append);
}));
return queue;
})
.push(function (element) {
// var maxItems = schema.maxItems;
input = element;
// XXX update on every add/delete item
// input.hidden = maxItems !== undefined && json_document.length >= maxItems;
div_input.appendChild(input);
div.appendChild(div_input);
root.appendChild(div);
});
}
function render_field(gadget, key, path, json_field, default_value, root, schema_path, options) {
var type,
div,
delete_button,
label,
label_text,
div_input,
span_info,
error_message,
input,
first_path,
queue = RSVP.Queue();
if (json_field instanceof Array) {
json_field = schemaArrFilteredByDocument(json_field, default_value);
schema_path = json_field.schema_path;
json_field = json_field.schema;
}
options = options || {};
type = options.type;
if (path && key) {
first_path = path + encodeJsonPointer(key);
} else {
first_path = "";
}
if (json_field === undefined) {
json_field = getDocumentSchema(default_value);
}
if (getDocumentType(json_field.type) === "string") {
type = json_field.type;
} // else json_field.type is array so we use type
if (type === undefined && default_value !== undefined) {
type = getDocumentType(default_value);
}
// XXX bad peace of code
// i do not sure that type can be computed so
// but our schema in slapos bad
if (!type) {
if (json_field.properties &&
json_field.required &&
json_field.required.length > 0) {
type = "object";
}
}
div = document.createElement("div");
div.setAttribute("class", "jsonformfield ui-field-contain");
div.title = json_field.description;
// if (key && !first_path) {
if (options.delete_button === true) {
delete_button = createElement("span",
{"class": "ui-btn-icon-top ui-icon-trash-o"}
);
gadget.props.delete_button = delete_button;
div.appendChild(delete_button);
} else if (options.top !== true) {
if (options.required) {
delete_button = createElement("span",
{"class": "ui-btn-icon-top ui-icon-circle"}
);
div.appendChild(delete_button);
} else {
delete_button = createElement("span");
delete_button.innerHTML = "&emsp;";
div.appendChild(delete_button);
}
}
if (false) {
// XXX;
label = document.createElement("input");
label.value = key;
gadget.props.property_name_edit = label;
} else {
label_text = [key, json_field.title]
.filter(function (v) { return v; })
.join(" ")
// use non-breaking hyphen
.replace(/-/g, "");
if (label_text) {
if (options.top) {
label = document.createElement("span");
label.textContent = label_text;
root.appendChild(label);
} else {
label = document.createElement("label");
label.textContent = label_text;
div.appendChild(label);
}
}
}
div_input = document.createElement("div");
div_input.setAttribute("id", gadget.element.getAttribute("data-gadget-scope") + first_path + '/');
div_input.setAttribute("class", "input");
if (json_field.enum !== undefined) {
input = render_selection(json_field, default_value);
}
if (type === "boolean") {
input = render_boolean(json_field, default_value);
}
if (!input && ["string", "integer", "number"].indexOf(type) >= 0) {
if (json_field.contentMediaType === "text/plain") {
input = render_textarea(json_field, default_value, "string");
} else {
input = document.createElement("input");
if (default_value !== undefined) {
input.value = default_value;
}
if (type === "integer" || type === "number") {
if (default_value === undefined && typeof json_field.default === "number") {
input.value = json_field.default;
}
input.type = "number";
input.setAttribute("data-json-type", type);
if (type === "integer") {
input.setAttribute("step", "1");
}
if (type === "number") {
input.setAttribute("step", "any");
}
} else {
if (default_value === undefined && typeof json_field.default === "string") {
input.value = json_field.default;
}
input.type = "text";
if (json_field.pattern) {
input.pattern = json_field.pattern;
}
if (json_field.format === 'uri') {
input.type = "url";
input.spellcheck = false;
}
}
}
}
if (type === "array") {
queue = render_array(
gadget,
json_field,
default_value,
div_input,
first_path + '/',
schema_path
);
gadget.props.arrays[first_path + '/'] = div;
}
if (type === "object") {
queue
.push(function () {
return render_object(
gadget,
json_field,
default_value,
div_input,
first_path + '/',
schema_path
);
});
}
if (input) {
// object and array excluded from
// gadget.props.inputs not contain values
gadget.props.inputs.push(input);
input.name = first_path;
input.required = options.required;
// XXX for gui
//input.setAttribute("class", "slapos-parameter");
div_input.appendChild(input);
} else {
div.setAttribute("data-json-path", first_path + '/');
div.setAttribute("data-json-type", type);
}
if (json_field.info !== undefined) {
span_info = document.createElement("span");
span_info.textContent = json_field.info;
div_input.appendChild(span_info);
}
error_message = document.createElement("span");
error_message.setAttribute("class", "error");
error_message.hidden = true;
div_input.appendChild(error_message);
div.appendChild(div_input);
return queue
.push(function () {
root.appendChild(div);
return div;
});
}
function render_object_additionalProperty(g, title, json_document, path, schema, schema_path, used, element_append) {
var div,
div_input,
input;
div = document.createElement("div");
div.setAttribute("class", "jsonformfield");
// div.title = json_field.description;
div_input = document.createElement("div");
div_input.setAttribute("class", "input");
input = document.createElement("input");
input.type = "text";
input.placeholder = "name of " + title;
div_input.appendChild(input);
return g.expandSchema(schema, schema_path)
.push(function (schema_arr) {
var queue = RSVP.Queue(),
property_name;
for (property_name in json_document) {
if (json_document.hasOwnProperty(property_name) && !used.hasOwnProperty(property_name)) {
used[property_name] = "";
queue
.push(
addSubForm.bind(g, {
gadget: g,
property_name: property_name,
path: path,
schema_path: schema_path,
schema_part: schema_arr,
default_dict: json_document[property_name]
})
)
.push(element_append);
}
}
queue.push(function () {
return render_schema_selector(g, "add " + title, schema_arr, function (value) {
return addSubForm({
gadget: g,
element: input,
path: path,
type: value.type,
schema_path: value.schema_path,
schema_part: value.schema
})
.push(element_append);
});
});
return queue;
})
.push(function (input) {
div_input.appendChild(input);
div.appendChild(div_input);
return div;
});
}
render_object = function (g, json_field, default_dict, root, path, schema_path) {
var required = json_field.required || [],
schema_editor = json_field.hasOwnProperty("properties") &&
json_field.properties.hasOwnProperty("$schema"),
used_properties = {},
properties,
selector = {};
g.props.objects[path] = used_properties;
function element_append(child) {
if (child) {
// insert additionalProperty before selector
selector.element.parentNode.insertBefore(child, selector.element);
}
}
function root_append(child) {
root.appendChild(child);
}
if (default_dict === undefined) {
default_dict = {};
}
return expandProperties(g, json_field.properties, schema_path + '/properties/', required)
.push(function (ret) {
var schema_arr,
q = RSVP.Queue(),
s_o,
key;
properties = ret;
for (key in properties) {
if (properties.hasOwnProperty(key)) {
schema_arr = properties[key];
s_o = schemaArrFilteredByDocument(schema_arr, default_dict[key]);
if (checkSchemaArrOneChoise(schema_arr)) {
if (required.indexOf(key) >= 0) {
used_properties[key] = false;
q.push(render_field.bind(g, g, key, path,
s_o.schema, default_dict[key], root, s_o.schema_path, {required: true})
);
}
if (!used_properties.hasOwnProperty(key) &&
!schema_editor &&
(checkSchemaSimpleType(s_o.schema) || !s_o.circular)
) {
used_properties[key] = false;
q.push(render_field.bind(g, g, key, path,
s_o.schema, default_dict[key], root, s_o.schema_path, {
required: false,
delete_button: false
}));
}
}
if (!used_properties.hasOwnProperty(key) &&
default_dict.hasOwnProperty(key)) {
used_properties[key] = "";
q.push(
addSubForm.bind(g, {
gadget: g,
property_name: key,
path: path,
schema_path: s_o.schema_path,
schema_part: s_o.schema,
default_dict: default_dict[key]
})
)
.push(root_append);
}
}
}
return q;
})
.push(function () {
var schema_arr = convertExpandedProperties2array(properties);
return render_schema_selector(g, "add property", schema_arr, function (value) {
used_properties[value.property_name] = "";
return addSubForm({
gadget: g,
property_name: value.property_name,
path: path,
type: value.type,
schema_path: value.schema_path,
schema_part: value.schema
})
.push(function (element) {
var s_e = selector.element;
if (s_e) {
s_e.parentNode.insertBefore(element, s_e);
}
});
},
function (gadget_s, schema_alternatives) {
var x,
item_list = [["add property", "add property"]],
item;
if (schema_alternatives) {
for (x = 0; x < schema_alternatives.length; x += 1) {
item = schema_alternatives[x];
if (!used_properties.hasOwnProperty(item.value.property_name)) {
item_list.push([item.title, x]);
}
}
if (gadget_s) {
return {
name: gadget_s.element.getAttribute('data-gadget-scope'),
editable: true,
hidden: item_list.length === 1,
value: item_list[0][1],
item_list: item_list
};
}
return item_list.length > 1;
}
});
})
.push(function (element) {
selector.element = element;
return root_append(element);
})
.push(function () {
var queue = RSVP.Queue(),
additionalProperties;
if (json_field.patternProperties !== undefined) {
// XXX need loop on any pattern properties
if (json_field.patternProperties['.*'] !== undefined) {
queue
.push(render_object_additionalProperty.bind(g,
g,
".* property",
default_dict,
path,
json_field.patternProperties['.*'],
schema_path + '/patternProperties/.*',
used_properties,
element_append
))
.push(root_append);
}
}
if (json_field.additionalProperties === undefined) {
additionalProperties = true;
} else {
additionalProperties = json_field.additionalProperties;
}
if (additionalProperties !== false) {
queue
.push(render_object_additionalProperty.bind(g,
g,
"additional property",
default_dict,
path,
additionalProperties,
schema_path + '/additionalProperties',
used_properties,
element_append
))
.push(root_append);
}
return queue;
})
.push(function () {
var key,
queue = RSVP.Queue();
for (key in default_dict) {
if (default_dict.hasOwnProperty(key)) {
if (!used_properties.hasOwnProperty(key)) {
queue
.push(
addSubForm.bind(g, {
gadget: g,
property_name: key,
path: path,
schema_path: "",
schema_part: undefined,
default_dict: default_dict[key]
})
)
.push(root_append);
}
}
}
return queue;
});
};
function getFormValuesAsJSONDict(g) {
var multi_level_dict = {"": {}},
count_of_values = 0,
scope,
options = g.props,
array,
path,
key,
i,
len,
queue = RSVP.Queue();
function convertOnMultiLevel(d, key, value) {
var ii,
kk,
key_list = key.split("/");
for (ii = 0; ii < key_list.length; ii += 1) {
kk = decodeJsonPointer(key_list[ii]);
if (ii === key_list.length - 1) {
if (value !== undefined) {
d[kk] = value;
count_of_values += 1;
} else {
return d[kk];
}
} else {
if (!d.hasOwnProperty(kk)) {
d[kk] = {};
}
d = d[kk];
}
}
}
function recursiveGetContent(scope, path) {
queue
.push(function () {
return g.getDeclaredGadget(scope);
})
.push(function (gadget) {
return gadget.getContent();
})
.push(function (jdict) {
if (jdict === undefined) {
return;
}
convertOnMultiLevel(multi_level_dict, path, jdict);
});
}
function getContentAndPushArray(scope, parent_path) {
queue
.push(function () {
return g.getDeclaredGadget(scope);
})
.push(function (gadget) {
return gadget.getContent();
})
.push(function (jdict) {
if (jdict === undefined) {
return;
}
var arr = convertOnMultiLevel(multi_level_dict, parent_path);
if (!(arr instanceof Array)) {
arr = [];
convertOnMultiLevel(multi_level_dict, parent_path, arr);
}
arr.push(jdict);
});
}
for (path in options.arrays) {
if (options.arrays.hasOwnProperty(path)) {
array = options.arrays[path]
.querySelectorAll("div[data-gadget-parent-scope='" + g.element.getAttribute("data-gadget-scope") + "']");
len = array.length;
if (len === 0) {
convertOnMultiLevel(multi_level_dict, path.slice(0, -1), []);
}
for (i = 0; i < len; i = i + 1) {
getContentAndPushArray(
array[i].getAttribute('data-gadget-scope'),
// slice remove concluding '/'
path.slice(0, -1)
);
}
}
}
for (path in options.objects) {
if (options.objects.hasOwnProperty(path)) {
for (key in options.objects[path]) {
if (options.objects[path].hasOwnProperty(key)) {
scope = options.objects[path][key];
if (scope) {
recursiveGetContent(scope, path + encodeJsonPointer(key));
}
}
}
}
}
return queue
.push(function () {
var json_dict = {},
k;
g.props.inputs.forEach(function (input) {
if (input.required || input.value !== "") {
var type = input.getAttribute('data-json-type');
if (type === 'number') {
json_dict[input.name] = parseFloat(input.value);
} else if (type === "integer") {
json_dict[input.name] = parseInt(input.value, 10);
} else if (type === "boolean") {
if (input.value === "true") {
json_dict[input.name] = true;
} else if (input.value === "false") {
json_dict[input.name] = false;
}
} else if (input.tagName === "TEXTAREA") {
if (input["data-format"] === "string") {
json_dict[input.name] = input.value;
} else {
json_dict[input.name] = input.value.split('\n');
}
} else {
json_dict[input.name] = input.value;
}
}
});
for (k in json_dict) {
if (json_dict.hasOwnProperty(k)) {
convertOnMultiLevel(multi_level_dict, k, json_dict[k]);
}
}
if (count_of_values === 0) {
return;
}
return multi_level_dict[""];
});
}
function getSubGadgetElement(g, scope) {
return g.element.querySelector("div[data-gadget-scope='" + scope + "']");
}
rJS(window)
.ready(function () {
var g = this;
g.props = {};
g.options = {};
})
.declareAcquiredMethod("notifyChange", "notifyChange")
.declareAcquiredMethod("renameChildrenParent", "renameChildren")
.allowPublicAcquisition("renameChildren", function (opt_arr, scope) {
var property_name,
objects = this.props.objects,
new_name = opt_arr[0],
element = getSubGadgetElement(this, scope),
parent = element.getAttribute('data-json-parent');
if (objects.hasOwnProperty(parent)) {
parent = objects[parent];
if (parent.hasOwnProperty(new_name)) {
throw new Error("property already exist");
}
// XXX validate property if property pattern
for (property_name in parent) {
if (parent.hasOwnProperty(property_name) && parent[property_name] === scope) {
delete parent[property_name];
parent[new_name] = scope;
return new_name;
}
}
throw new Error("gadget not found for renaming");
}
})
.declareMethod("rename", function (new_name, event) {
var g = this,
name = g.element.getAttribute('data-json-property-name');
return this.renameChildrenParent(new_name)
.push(function () {
return g.element.setAttribute('data-json-property-name', new_name);
})
.push(undefined, function (error) {
// XXX notify user
event.srcElement.value = name;
event.srcElement.focus();
});
})
.declareAcquiredMethod("selfRemove", "deleteChildren")
.allowPublicAcquisition("deleteChildren", function (arr, scope) {
var g = this,
key,
i,
button_list = this.props.add_buttons,
objects = this.props.objects,
element = getSubGadgetElement(g, scope),
parent = element.getAttribute("data-json-parent"),
tasks = [];
if (objects.hasOwnProperty(parent)) {
parent = objects[parent];
for (key in parent) {
if (parent.hasOwnProperty(key) && parent[key] === scope) {
delete parent[key];
}
}
}
element.parentNode.removeChild(element);
for (key in g.props.add_custom_data) {
if (g.props.add_custom_data.hasOwnProperty(key)) {
tasks.push(g.props.add_custom_data[key].rerender());
}
}
for (i = 0; i < button_list.length; i = i + 1) {
tasks.push(button_list[i].rerender());
}
tasks.push(checkValidityAndNotifyChange(g));
return RSVP.Queue()
.push(function () {
return RSVP.all(tasks);
});
})
.declareMethod('getElementByPath', function (data_path) {
var g = this,
array,
path,
scope,
key,
next_data_path,
slash_count = 0,
slash_count_next,
bingo,
idx,
options = g.props;
if (data_path !== "/") {
for (path in options.arrays) {
if (options.arrays.hasOwnProperty(path) && data_path.startsWith(path)) {
slash_count_next = path.split("/").length - 1;
if (slash_count_next > slash_count) {
bingo = path;
slash_count = slash_count_next;
}
}
}
if (bingo) {
array = options.arrays[bingo]
.querySelectorAll("div[data-gadget-parent-scope='" + g.element.getAttribute("data-gadget-scope") + "']");
next_data_path = data_path.slice(bingo.length).split("/");
idx = next_data_path[0];
next_data_path = "/" + next_data_path.slice(1).join("/");
return g.getDeclaredGadget(array[idx].getAttribute('data-gadget-scope'))
.push(function (gadget) {
return gadget.getElementByPath(next_data_path);
});
}
slash_count = 0;
for (path in options.objects) {
if (options.objects.hasOwnProperty(path) && data_path.startsWith(path)) {
slash_count_next = path.split("/").length - 1;
if (slash_count_next > slash_count) {
bingo = path;
slash_count = slash_count_next;
}
}
}
if (bingo) {
path = options.objects[bingo];
key = decodeJsonPointer(data_path.slice(bingo.length).split('/')[0]);
if (path.hasOwnProperty(key)) {
next_data_path = data_path.slice(bingo.length + encodeJsonPointer(key).length);
if (!next_data_path) {
next_data_path = "/";
}
scope = path[key];
}
}
if (scope === false) {
// gadget for this element absent
// so find element in current gadget
return document.getElementById(
g.element.getAttribute("data-gadget-scope") + bingo + key + '/'
);
}
if (scope) {
// get gadget by scope and use relative path for find element in gadget
return g.getDeclaredGadget(scope)
.push(function (gadget) {
return gadget.getElementByPath(next_data_path);
});
}
}
return RSVP.Queue()
.push(function () {
return document.getElementById(
g.element.getAttribute("data-gadget-scope") + data_path
);
});
})
.declareAcquiredMethod("notifyValid", "notifyValid")
.declareAcquiredMethod("notifyInvalid", "notifyInvalid")
.declareAcquiredMethod("checkValidity", "checkValidity")
.allowPublicAcquisition("notifyValid", function (arr, sub_scope) {
return true;
})
.allowPublicAcquisition("notifyChange", function (arr, sub_scope) {
var g = this,
opt = arr[0],
event_object;
event_object = g.props.add_custom_data[sub_scope];
if (event_object && opt.type === "change") {
return event_object.event();
}
return g.notifyChange();
})
.declareMethod('renderForm', function (options) {
var g = this,
property_name = g.element.getAttribute('data-json-property-name'),
schema = options.schema,
root;
g.props.inputs = [];
g.props.add_buttons = [];
g.props.add_custom_data = {};
g.props.arrays = {};
g.props.objects = {};
g.props.path = options.path; // self gadget scope
if (!property_name || !options.display_label) {
property_name = "";
}
root = g.element.querySelector('[data-json-path="/"]');
if (!root) {
root = g.element;
}
if (options.delete_button === undefined) {
if (options.top) {
options.delete_button = false;
} else {
options.delete_button = !options.required;
}
}
while (root.firstChild) {
root.removeChild(root.firstChild);
}
return render_field(g, property_name, "", schema,
options.document, root, options.schema_path,
{
type: options.type,
required: options.required,
delete_button: options.delete_button,
top: options.top
})
.push(function () {
g.listenEvents();
return g.element;
});
})
.declareAcquiredMethod("expandSchema", "expandSchema")
.onEvent('click', function (evt) {
if (evt.target === this.props.delete_button) {
return this.selfRemove(evt);
}
var link = evt.target.getAttribute("data-error-link"),
button_list = this.props.add_buttons,
i;
if (link) {
location.href = link;
return;
}
for (i = 0; i < button_list.length; i = i + 1) {
if (evt.target === button_list[i].element) {
return button_list[i].event(evt);
}
}
})
.onEvent('change', function (evt) {
if (evt.target === this.props.property_name_edit) {
return this.rename(this.props.property_name_edit.value, evt);
}
var field_list = this.props.inputs,
i;
for (i = 0; i < field_list.length; i = i + 1) {
if (evt.target === field_list[i]) {
return checkValidityAndNotifyChange(this);
}
}
})
.declareJob('listenEvents', function () {
// XXX Disable
return;
})
.declareMethod('getContent', function () {
var g = this;
return getFormValuesAsJSONDict(g);
});
}(window, document, location, rJS, RSVP, jIO, tv4));
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="File" module="OFS.Image"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>__name__</string> </key>
<value> <string>gadget_json_generated_form_child.js</string> </value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>application/javascript</string> </value>
</item>
<item>
<key> <string>precondition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>gadget_json_generated_form_child.js</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
/*
Author: Geraint Luff and others
Year: 2013
This code is released into the "public domain" by its author(s). Anybody may use, alter and distribute the code without restriction. The author makes no guarantees, and takes no liability of any kind for use of this code.
If you find a bug or make an improvement, it would be courteous to let the author know, but it is not compulsory.
*/
(function (global, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], factory);
} else if (typeof module !== 'undefined' && module.exports){
// CommonJS. Define export.
module.exports = factory();
} else {
// Browser globals
global.tv4 = factory();
}
}(this, function () {
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys?redirectlocale=en-US&redirectslug=JavaScript%2FReference%2FGlobal_Objects%2FObject%2Fkeys
if (!Object.keys) {
Object.keys = (function () {
var hasOwnProperty = Object.prototype.hasOwnProperty,
hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'),
dontEnums = [
'toString',
'toLocaleString',
'valueOf',
'hasOwnProperty',
'isPrototypeOf',
'propertyIsEnumerable',
'constructor'
],
dontEnumsLength = dontEnums.length;
return function (obj) {
if (typeof obj !== 'object' && typeof obj !== 'function' || obj === null) {
throw new TypeError('Object.keys called on non-object');
}
var result = [];
for (var prop in obj) {
if (hasOwnProperty.call(obj, prop)) {
result.push(prop);
}
}
if (hasDontEnumBug) {
for (var i=0; i < dontEnumsLength; i++) {
if (hasOwnProperty.call(obj, dontEnums[i])) {
result.push(dontEnums[i]);
}
}
}
return result;
};
})();
}
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create
if (!Object.create) {
Object.create = (function(){
function F(){}
return function(o){
if (arguments.length !== 1) {
throw new Error('Object.create implementation only accepts one parameter.');
}
F.prototype = o;
return new F();
};
})();
}
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray?redirectlocale=en-US&redirectslug=JavaScript%2FReference%2FGlobal_Objects%2FArray%2FisArray
if(!Array.isArray) {
Array.isArray = function (vArg) {
return Object.prototype.toString.call(vArg) === "[object Array]";
};
}
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf?redirectlocale=en-US&redirectslug=JavaScript%2FReference%2FGlobal_Objects%2FArray%2FindexOf
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
if (this === null) {
throw new TypeError();
}
var t = Object(this);
var len = t.length >>> 0;
if (len === 0) {
return -1;
}
var n = 0;
if (arguments.length > 1) {
n = Number(arguments[1]);
if (n !== n) { // shortcut for verifying if it's NaN
n = 0;
} else if (n !== 0 && n !== Infinity && n !== -Infinity) {
n = (n > 0 || -1) * Math.floor(Math.abs(n));
}
}
if (n >= len) {
return -1;
}
var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
for (; k < len; k++) {
if (k in t && t[k] === searchElement) {
return k;
}
}
return -1;
};
}
// Grungey Object.isFrozen hack
if (!Object.isFrozen) {
Object.isFrozen = function (obj) {
var key = "tv4_test_frozen_key";
while (obj.hasOwnProperty(key)) {
key += Math.random();
}
try {
obj[key] = true;
delete obj[key];
return false;
} catch (e) {
return true;
}
};
}
// Based on: https://github.com/geraintluff/uri-templates, but with all the de-substitution stuff removed
var uriTemplateGlobalModifiers = {
"+": true,
"#": true,
".": true,
"/": true,
";": true,
"?": true,
"&": true
};
var uriTemplateSuffices = {
"*": true
};
function notReallyPercentEncode(string) {
return encodeURI(string).replace(/%25[0-9][0-9]/g, function (doubleEncoded) {
return "%" + doubleEncoded.substring(3);
});
}
function uriTemplateSubstitution(spec) {
var modifier = "";
if (uriTemplateGlobalModifiers[spec.charAt(0)]) {
modifier = spec.charAt(0);
spec = spec.substring(1);
}
var separator = "";
var prefix = "";
var shouldEscape = true;
var showVariables = false;
var trimEmptyString = false;
if (modifier === '+') {
shouldEscape = false;
} else if (modifier === ".") {
prefix = ".";
separator = ".";
} else if (modifier === "/") {
prefix = "/";
separator = "/";
} else if (modifier === '#') {
prefix = "#";
shouldEscape = false;
} else if (modifier === ';') {
prefix = ";";
separator = ";";
showVariables = true;
trimEmptyString = true;
} else if (modifier === '?') {
prefix = "?";
separator = "&";
showVariables = true;
} else if (modifier === '&') {
prefix = "&";
separator = "&";
showVariables = true;
}
var varNames = [];
var varList = spec.split(",");
var varSpecs = [];
var varSpecMap = {};
for (var i = 0; i < varList.length; i++) {
var varName = varList[i];
var truncate = null;
if (varName.indexOf(":") !== -1) {
var parts = varName.split(":");
varName = parts[0];
truncate = parseInt(parts[1], 10);
}
var suffices = {};
while (uriTemplateSuffices[varName.charAt(varName.length - 1)]) {
suffices[varName.charAt(varName.length - 1)] = true;
varName = varName.substring(0, varName.length - 1);
}
var varSpec = {
truncate: truncate,
name: varName,
suffices: suffices
};
varSpecs.push(varSpec);
varSpecMap[varName] = varSpec;
varNames.push(varName);
}
var subFunction = function (valueFunction) {
var result = "";
var startIndex = 0;
for (var i = 0; i < varSpecs.length; i++) {
var varSpec = varSpecs[i];
var value = valueFunction(varSpec.name);
if (value === null || value === undefined || (Array.isArray(value) && value.length === 0) || (typeof value === 'object' && Object.keys(value).length === 0)) {
startIndex++;
continue;
}
if (i === startIndex) {
result += prefix;
} else {
result += (separator || ",");
}
if (Array.isArray(value)) {
if (showVariables) {
result += varSpec.name + "=";
}
for (var j = 0; j < value.length; j++) {
if (j > 0) {
result += varSpec.suffices['*'] ? (separator || ",") : ",";
if (varSpec.suffices['*'] && showVariables) {
result += varSpec.name + "=";
}
}
result += shouldEscape ? encodeURIComponent(value[j]).replace(/!/g, "%21") : notReallyPercentEncode(value[j]);
}
} else if (typeof value === "object") {
if (showVariables && !varSpec.suffices['*']) {
result += varSpec.name + "=";
}
var first = true;
for (var key in value) {
if (!first) {
result += varSpec.suffices['*'] ? (separator || ",") : ",";
}
first = false;
result += shouldEscape ? encodeURIComponent(key).replace(/!/g, "%21") : notReallyPercentEncode(key);
result += varSpec.suffices['*'] ? '=' : ",";
result += shouldEscape ? encodeURIComponent(value[key]).replace(/!/g, "%21") : notReallyPercentEncode(value[key]);
}
} else {
if (showVariables) {
result += varSpec.name;
if (!trimEmptyString || value !== "") {
result += "=";
}
}
if (varSpec.truncate != null) {
value = value.substring(0, varSpec.truncate);
}
result += shouldEscape ? encodeURIComponent(value).replace(/!/g, "%21"): notReallyPercentEncode(value);
}
}
return result;
};
subFunction.varNames = varNames;
return {
prefix: prefix,
substitution: subFunction
};
}
function UriTemplate(template) {
if (!(this instanceof UriTemplate)) {
return new UriTemplate(template);
}
var parts = template.split("{");
var textParts = [parts.shift()];
var prefixes = [];
var substitutions = [];
var varNames = [];
while (parts.length > 0) {
var part = parts.shift();
var spec = part.split("}")[0];
var remainder = part.substring(spec.length + 1);
var funcs = uriTemplateSubstitution(spec);
substitutions.push(funcs.substitution);
prefixes.push(funcs.prefix);
textParts.push(remainder);
varNames = varNames.concat(funcs.substitution.varNames);
}
this.fill = function (valueFunction) {
var result = textParts[0];
for (var i = 0; i < substitutions.length; i++) {
var substitution = substitutions[i];
result += substitution(valueFunction);
result += textParts[i + 1];
}
return result;
};
this.varNames = varNames;
this.template = template;
}
UriTemplate.prototype = {
toString: function () {
return this.template;
},
fillFromObject: function (obj) {
return this.fill(function (varName) {
return obj[varName];
});
}
};
var ValidatorContext = function ValidatorContext(parent, collectMultiple, errorReporter, checkRecursive, trackUnknownProperties) {
this.missing = [];
this.missingMap = {};
this.formatValidators = parent ? Object.create(parent.formatValidators) : {};
this.schemas = parent ? Object.create(parent.schemas) : {};
this.collectMultiple = collectMultiple;
this.errors = [];
this.handleError = collectMultiple ? this.collectError : this.returnError;
if (checkRecursive) {
this.checkRecursive = true;
this.scanned = [];
this.scannedFrozen = [];
this.scannedFrozenSchemas = [];
this.scannedFrozenValidationErrors = [];
this.validatedSchemasKey = 'tv4_validation_id';
this.validationErrorsKey = 'tv4_validation_errors_id';
}
if (trackUnknownProperties) {
this.trackUnknownProperties = true;
this.knownPropertyPaths = {};
this.unknownPropertyPaths = {};
}
this.errorReporter = errorReporter || defaultErrorReporter('en');
if (typeof this.errorReporter === 'string') {
throw new Error('debug');
}
this.definedKeywords = {};
if (parent) {
for (var key in parent.definedKeywords) {
this.definedKeywords[key] = parent.definedKeywords[key].slice(0);
}
}
};
ValidatorContext.prototype.defineKeyword = function (keyword, keywordFunction) {
this.definedKeywords[keyword] = this.definedKeywords[keyword] || [];
this.definedKeywords[keyword].push(keywordFunction);
};
ValidatorContext.prototype.createError = function (code, messageParams, dataPath, schemaPath, subErrors, data, schema) {
var error = new ValidationError(code, messageParams, dataPath, schemaPath, subErrors);
error.message = this.errorReporter(error, data, schema);
return error;
};
ValidatorContext.prototype.returnError = function (error) {
return error;
};
ValidatorContext.prototype.collectError = function (error) {
if (error) {
this.errors.push(error);
}
return null;
};
ValidatorContext.prototype.prefixErrors = function (startIndex, dataPath, schemaPath) {
for (var i = startIndex; i < this.errors.length; i++) {
this.errors[i] = this.errors[i].prefixWith(dataPath, schemaPath);
}
return this;
};
ValidatorContext.prototype.banUnknownProperties = function (data, schema) {
for (var unknownPath in this.unknownPropertyPaths) {
var error = this.createError(ErrorCodes.UNKNOWN_PROPERTY, {path: unknownPath}, unknownPath, "", null, data, schema);
var result = this.handleError(error);
if (result) {
return result;
}
}
return null;
};
ValidatorContext.prototype.addFormat = function (format, validator) {
if (typeof format === 'object') {
for (var key in format) {
this.addFormat(key, format[key]);
}
return this;
}
this.formatValidators[format] = validator;
};
ValidatorContext.prototype.resolveRefs = function (schema, urlHistory) {
if (schema['$ref'] !== undefined) {
urlHistory = urlHistory || {};
if (urlHistory[schema['$ref']]) {
return this.createError(ErrorCodes.CIRCULAR_REFERENCE, {urls: Object.keys(urlHistory).join(', ')}, '', '', null, undefined, schema);
}
urlHistory[schema['$ref']] = true;
schema = this.getSchema(schema['$ref'], urlHistory);
}
return schema;
};
ValidatorContext.prototype.getSchema = function (url, urlHistory) {
var schema;
if (this.schemas[url] !== undefined) {
schema = this.schemas[url];
return this.resolveRefs(schema, urlHistory);
}
var baseUrl = url;
var fragment = "";
if (url.indexOf('#') !== -1) {
fragment = url.substring(url.indexOf("#") + 1);
baseUrl = url.substring(0, url.indexOf("#"));
}
if (typeof this.schemas[baseUrl] === 'object') {
schema = this.schemas[baseUrl];
var pointerPath = decodeURIComponent(fragment);
if (pointerPath === "") {
return this.resolveRefs(schema, urlHistory);
} else if (pointerPath.charAt(0) !== "/") {
return undefined;
}
var parts = pointerPath.split("/").slice(1);
for (var i = 0; i < parts.length; i++) {
var component = parts[i].replace(/~1/g, "/").replace(/~0/g, "~");
if (schema[component] === undefined) {
schema = undefined;
break;
}
schema = schema[component];
}
if (schema !== undefined) {
return this.resolveRefs(schema, urlHistory);
}
}
if (this.missing[baseUrl] === undefined) {
this.missing.push(baseUrl);
this.missing[baseUrl] = baseUrl;
this.missingMap[baseUrl] = baseUrl;
}
};
ValidatorContext.prototype.searchSchemas = function (schema, url) {
if (Array.isArray(schema)) {
for (var i = 0; i < schema.length; i++) {
this.searchSchemas(schema[i], url);
}
} else if (schema && typeof schema === "object") {
if (typeof schema.id === "string") {
if (isTrustedUrl(url, schema.id)) {
if (this.schemas[schema.id] === undefined) {
this.schemas[schema.id] = schema;
}
}
}
for (var key in schema) {
if (key !== "enum") {
if (typeof schema[key] === "object") {
this.searchSchemas(schema[key], url);
} else if (key === "$ref") {
var uri = getDocumentUri(schema[key]);
if (uri && this.schemas[uri] === undefined && this.missingMap[uri] === undefined) {
this.missingMap[uri] = uri;
}
}
}
}
}
};
ValidatorContext.prototype.addSchema = function (url, schema) {
//overload
if (typeof url !== 'string' || typeof schema === 'undefined') {
if (typeof url === 'object' && typeof url.id === 'string') {
schema = url;
url = schema.id;
}
else {
return;
}
}
if (url === getDocumentUri(url) + "#") {
// Remove empty fragment
url = getDocumentUri(url);
}
this.schemas[url] = schema;
delete this.missingMap[url];
// schema normalisation and downloading are disabled because already done by us.
// and current realisation not support CIRCULAR_REFERENCE
// normSchema(schema, url);
// this.searchSchemas(schema, url);
};
ValidatorContext.prototype.getSchemaMap = function () {
var map = {};
for (var key in this.schemas) {
map[key] = this.schemas[key];
}
return map;
};
ValidatorContext.prototype.getSchemaUris = function (filterRegExp) {
var list = [];
for (var key in this.schemas) {
if (!filterRegExp || filterRegExp.test(key)) {
list.push(key);
}
}
return list;
};
ValidatorContext.prototype.getMissingUris = function (filterRegExp) {
var list = [];
for (var key in this.missingMap) {
if (!filterRegExp || filterRegExp.test(key)) {
list.push(key);
}
}
return list;
};
ValidatorContext.prototype.dropSchemas = function () {
this.schemas = {};
this.reset();
};
ValidatorContext.prototype.reset = function () {
this.missing = [];
this.missingMap = {};
this.errors = [];
};
ValidatorContext.prototype.validateAll = function (data, schema, dataPathParts, schemaPathParts, dataPointerPath) {
var topLevel;
if (!schema) {
return null;
} else if (schema instanceof ValidationError) {
this.errors.push(schema);
return schema;
}
var startErrorCount = this.errors.length;
var frozenIndex, scannedFrozenSchemaIndex = null, scannedSchemasIndex = null;
if (this.checkRecursive && data && typeof data === 'object') {
topLevel = !this.scanned.length;
if (data[this.validatedSchemasKey]) {
var schemaIndex = data[this.validatedSchemasKey].indexOf(schema);
if (schemaIndex !== -1) {
this.errors = this.errors.concat(data[this.validationErrorsKey][schemaIndex]);
return null;
}
}
if (Object.isFrozen(data)) {
frozenIndex = this.scannedFrozen.indexOf(data);
if (frozenIndex !== -1) {
var frozenSchemaIndex = this.scannedFrozenSchemas[frozenIndex].indexOf(schema);
if (frozenSchemaIndex !== -1) {
this.errors = this.errors.concat(this.scannedFrozenValidationErrors[frozenIndex][frozenSchemaIndex]);
return null;
}
}
}
this.scanned.push(data);
if (Object.isFrozen(data)) {
if (frozenIndex === -1) {
frozenIndex = this.scannedFrozen.length;
this.scannedFrozen.push(data);
this.scannedFrozenSchemas.push([]);
}
scannedFrozenSchemaIndex = this.scannedFrozenSchemas[frozenIndex].length;
this.scannedFrozenSchemas[frozenIndex][scannedFrozenSchemaIndex] = schema;
this.scannedFrozenValidationErrors[frozenIndex][scannedFrozenSchemaIndex] = [];
} else {
if (!data[this.validatedSchemasKey]) {
try {
Object.defineProperty(data, this.validatedSchemasKey, {
value: [],
configurable: true
});
Object.defineProperty(data, this.validationErrorsKey, {
value: [],
configurable: true
});
} catch (e) {
//IE 7/8 workaround
data[this.validatedSchemasKey] = [];
data[this.validationErrorsKey] = [];
}
}
scannedSchemasIndex = data[this.validatedSchemasKey].length;
data[this.validatedSchemasKey][scannedSchemasIndex] = schema;
data[this.validationErrorsKey][scannedSchemasIndex] = [];
}
}
var errorCount = this.errors.length;
var error = this.validateBasic(data, schema, dataPointerPath)
|| this.validateNumeric(data, schema, dataPointerPath)
|| this.validateString(data, schema, dataPointerPath)
|| this.validateArray(data, schema, dataPointerPath)
|| this.validateObject(data, schema, dataPointerPath)
|| this.validateCombinations(data, schema, dataPointerPath)
|| this.validateHypermedia(data, schema, dataPointerPath)
|| this.validateFormat(data, schema, dataPointerPath)
|| this.validateDefinedKeywords(data, schema, dataPointerPath)
|| null;
if (topLevel) {
while (this.scanned.length) {
var item = this.scanned.pop();
delete item[this.validatedSchemasKey];
}
this.scannedFrozen = [];
this.scannedFrozenSchemas = [];
}
if (error || errorCount !== this.errors.length) {
while ((dataPathParts && dataPathParts.length) || (schemaPathParts && schemaPathParts.length)) {
var dataPart = (dataPathParts && dataPathParts.length) ? "" + dataPathParts.pop() : null;
var schemaPart = (schemaPathParts && schemaPathParts.length) ? "" + schemaPathParts.pop() : null;
if (error) {
error = error.prefixWith(dataPart, schemaPart);
}
this.prefixErrors(errorCount, dataPart, schemaPart);
}
}
if (scannedFrozenSchemaIndex !== null) {
this.scannedFrozenValidationErrors[frozenIndex][scannedFrozenSchemaIndex] = this.errors.slice(startErrorCount);
} else if (scannedSchemasIndex !== null) {
data[this.validationErrorsKey][scannedSchemasIndex] = this.errors.slice(startErrorCount);
}
return this.handleError(error);
};
ValidatorContext.prototype.validateFormat = function (data, schema) {
if (typeof schema.format !== 'string' || !this.formatValidators[schema.format]) {
return null;
}
var errorMessage = this.formatValidators[schema.format].call(null, data, schema);
if (typeof errorMessage === 'string' || typeof errorMessage === 'number') {
return this.createError(ErrorCodes.FORMAT_CUSTOM, {message: errorMessage}, '', '/format', null, data, schema);
} else if (errorMessage && typeof errorMessage === 'object') {
return this.createError(ErrorCodes.FORMAT_CUSTOM, {message: errorMessage.message || "?"}, errorMessage.dataPath || '', errorMessage.schemaPath || "/format", null, data, schema);
}
return null;
};
ValidatorContext.prototype.validateDefinedKeywords = function (data, schema, dataPointerPath) {
for (var key in this.definedKeywords) {
if (typeof schema[key] === 'undefined') {
continue;
}
var validationFunctions = this.definedKeywords[key];
for (var i = 0; i < validationFunctions.length; i++) {
var func = validationFunctions[i];
var result = func(data, schema[key], schema, dataPointerPath);
if (typeof result === 'string' || typeof result === 'number') {
return this.createError(ErrorCodes.KEYWORD_CUSTOM, {key: key, message: result}, '', '', null, data, schema).prefixWith(null, key);
} else if (result && typeof result === 'object') {
var code = result.code;
if (typeof code === 'string') {
if (!ErrorCodes[code]) {
throw new Error('Undefined error code (use defineError): ' + code);
}
code = ErrorCodes[code];
} else if (typeof code !== 'number') {
code = ErrorCodes.KEYWORD_CUSTOM;
}
var messageParams = (typeof result.message === 'object') ? result.message : {key: key, message: result.message || "?"};
var schemaPath = result.schemaPath || ("/" + key.replace(/~/g, '~0').replace(/\//g, '~1'));
return this.createError(code, messageParams, result.dataPath || null, schemaPath, null, data, schema);
}
}
}
return null;
};
function recursiveCompare(A, B) {
if (A === B) {
return true;
}
if (A && B && typeof A === "object" && typeof B === "object") {
if (Array.isArray(A) !== Array.isArray(B)) {
return false;
} else if (Array.isArray(A)) {
if (A.length !== B.length) {
return false;
}
for (var i = 0; i < A.length; i++) {
if (!recursiveCompare(A[i], B[i])) {
return false;
}
}
} else {
var key;
for (key in A) {
if (B[key] === undefined && A[key] !== undefined) {
return false;
}
}
for (key in B) {
if (A[key] === undefined && B[key] !== undefined) {
return false;
}
}
for (key in A) {
if (!recursiveCompare(A[key], B[key])) {
return false;
}
}
}
return true;
}
return false;
}
ValidatorContext.prototype.validateBasic = function validateBasic(data, schema, dataPointerPath) {
var error;
if (error = this.validateType(data, schema, dataPointerPath)) {
return error.prefixWith(null, "type");
}
if (error = this.validateEnum(data, schema, dataPointerPath)) {
return error.prefixWith(null, "type");
}
return null;
};
ValidatorContext.prototype.validateType = function validateType(data, schema) {
if (schema.type === undefined) {
return null;
}
var dataType = typeof data;
if (data === null) {
dataType = "null";
} else if (Array.isArray(data)) {
dataType = "array";
}
var allowedTypes = schema.type;
if (!Array.isArray(allowedTypes)) {
allowedTypes = [allowedTypes];
}
for (var i = 0; i < allowedTypes.length; i++) {
var type = allowedTypes[i];
if (type === dataType || (type === "integer" && dataType === "number" && (data % 1 === 0))) {
return null;
}
}
return this.createError(ErrorCodes.INVALID_TYPE, {type: dataType, expected: allowedTypes.join("/")}, '', '', null, data, schema);
};
ValidatorContext.prototype.validateEnum = function validateEnum(data, schema) {
if (schema["enum"] === undefined) {
return null;
}
for (var i = 0; i < schema["enum"].length; i++) {
var enumVal = schema["enum"][i];
if (recursiveCompare(data, enumVal)) {
return null;
}
}
return this.createError(ErrorCodes.ENUM_MISMATCH, {value: (typeof JSON !== 'undefined') ? JSON.stringify(data) : data}, '', '', null, data, schema);
};
ValidatorContext.prototype.validateNumeric = function validateNumeric(data, schema, dataPointerPath) {
return this.validateMultipleOf(data, schema, dataPointerPath)
|| this.validateMinMax(data, schema, dataPointerPath)
|| this.validateNaN(data, schema, dataPointerPath)
|| null;
};
var CLOSE_ENOUGH_LOW = Math.pow(2, -51);
var CLOSE_ENOUGH_HIGH = 1 - CLOSE_ENOUGH_LOW;
ValidatorContext.prototype.validateMultipleOf = function validateMultipleOf(data, schema) {
var multipleOf = schema.multipleOf || schema.divisibleBy;
if (multipleOf === undefined) {
return null;
}
if (typeof data === "number") {
var remainder = (data/multipleOf)%1;
if (remainder >= CLOSE_ENOUGH_LOW && remainder < CLOSE_ENOUGH_HIGH) {
return this.createError(ErrorCodes.NUMBER_MULTIPLE_OF, {value: data, multipleOf: multipleOf}, '', '', null, data, schema);
}
}
return null;
};
ValidatorContext.prototype.validateMinMax = function validateMinMax(data, schema) {
if (typeof data !== "number") {
return null;
}
if (schema.minimum !== undefined) {
if (data < schema.minimum) {
return this.createError(ErrorCodes.NUMBER_MINIMUM, {value: data, minimum: schema.minimum}, '', '/minimum', null, data, schema);
}
if (schema.exclusiveMinimum && data === schema.minimum) {
return this.createError(ErrorCodes.NUMBER_MINIMUM_EXCLUSIVE, {value: data, minimum: schema.minimum}, '', '/exclusiveMinimum', null, data, schema);
}
}
if (schema.maximum !== undefined) {
if (data > schema.maximum) {
return this.createError(ErrorCodes.NUMBER_MAXIMUM, {value: data, maximum: schema.maximum}, '', '/maximum', null, data, schema);
}
if (schema.exclusiveMaximum && data === schema.maximum) {
return this.createError(ErrorCodes.NUMBER_MAXIMUM_EXCLUSIVE, {value: data, maximum: schema.maximum}, '', '/exclusiveMaximum', null, data, schema);
}
}
return null;
};
ValidatorContext.prototype.validateNaN = function validateNaN(data, schema) {
if (typeof data !== "number") {
return null;
}
if (isNaN(data) === true || data === Infinity || data === -Infinity) {
return this.createError(ErrorCodes.NUMBER_NOT_A_NUMBER, {value: data}, '', '/type', null, data, schema);
}
return null;
};
ValidatorContext.prototype.validateString = function validateString(data, schema, dataPointerPath) {
return this.validateStringLength(data, schema, dataPointerPath)
|| this.validateStringPattern(data, schema, dataPointerPath)
|| null;
};
ValidatorContext.prototype.validateStringLength = function validateStringLength(data, schema) {
if (typeof data !== "string") {
return null;
}
if (schema.minLength !== undefined) {
if (data.length < schema.minLength) {
return this.createError(ErrorCodes.STRING_LENGTH_SHORT, {length: data.length, minimum: schema.minLength}, '', '/minLength', null, data, schema);
}
}
if (schema.maxLength !== undefined) {
if (data.length > schema.maxLength) {
return this.createError(ErrorCodes.STRING_LENGTH_LONG, {length: data.length, maximum: schema.maxLength}, '', '/maxLength', null, data, schema);
}
}
return null;
};
ValidatorContext.prototype.validateStringPattern = function validateStringPattern(data, schema) {
if (typeof data !== "string" || (typeof schema.pattern !== "string" && !(schema.pattern instanceof RegExp))) {
return null;
}
var regexp;
if (schema.pattern instanceof RegExp) {
regexp = schema.pattern;
}
else {
var body, flags = '';
// Check for regular expression literals
// @see http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.5
var literal = schema.pattern.match(/^\/(.+)\/([img]*)$/);
if (literal) {
body = literal[1];
flags = literal[2];
}
else {
body = schema.pattern;
}
regexp = new RegExp(body, flags);
}
if (!regexp.test(data)) {
return this.createError(ErrorCodes.STRING_PATTERN, {pattern: schema.pattern}, '', '/pattern', null, data, schema);
}
return null;
};
ValidatorContext.prototype.validateArray = function validateArray(data, schema, dataPointerPath) {
if (!Array.isArray(data)) {
return null;
}
return this.validateArrayLength(data, schema, dataPointerPath)
|| this.validateArrayUniqueItems(data, schema, dataPointerPath)
|| this.validateArrayItems(data, schema, dataPointerPath)
|| null;
};
ValidatorContext.prototype.validateArrayLength = function validateArrayLength(data, schema) {
var error;
if (schema.minItems !== undefined) {
if (data.length < schema.minItems) {
error = this.createError(ErrorCodes.ARRAY_LENGTH_SHORT, {length: data.length, minimum: schema.minItems}, '', '/minItems', null, data, schema);
if (this.handleError(error)) {
return error;
}
}
}
if (schema.maxItems !== undefined) {
if (data.length > schema.maxItems) {
error = this.createError(ErrorCodes.ARRAY_LENGTH_LONG, {length: data.length, maximum: schema.maxItems}, '', '/maxItems', null, data, schema);
if (this.handleError(error)) {
return error;
}
}
}
return null;
};
ValidatorContext.prototype.validateArrayUniqueItems = function validateArrayUniqueItems(data, schema) {
if (schema.uniqueItems) {
for (var i = 0; i < data.length; i++) {
for (var j = i + 1; j < data.length; j++) {
if (recursiveCompare(data[i], data[j])) {
var error = this.createError(ErrorCodes.ARRAY_UNIQUE, {match1: i, match2: j}, '', '/uniqueItems', null, data, schema);
if (this.handleError(error)) {
return error;
}
}
}
}
}
return null;
};
ValidatorContext.prototype.validateArrayItems = function validateArrayItems(data, schema, dataPointerPath) {
if (schema.items === undefined) {
return null;
}
var error, i;
if (Array.isArray(schema.items)) {
for (i = 0; i < data.length; i++) {
if (i < schema.items.length) {
if (error = this.validateAll(data[i], schema.items[i], [i], ["items", i], dataPointerPath + "/" + i)) {
return error;
}
} else if (schema.additionalItems !== undefined) {
if (typeof schema.additionalItems === "boolean") {
if (!schema.additionalItems) {
error = (this.createError(ErrorCodes.ARRAY_ADDITIONAL_ITEMS, {}, '/' + i, '/additionalItems', null, data, schema));
if (this.handleError(error)) {
return error;
}
}
} else if (error = this.validateAll(data[i], schema.additionalItems, [i], ["additionalItems"], dataPointerPath + "/" + i)) {
return error;
}
}
}
} else {
for (i = 0; i < data.length; i++) {
if (error = this.validateAll(data[i], schema.items, [i], ["items"], dataPointerPath + "/" + i)) {
return error;
}
}
}
return null;
};
ValidatorContext.prototype.validateObject = function validateObject(data, schema, dataPointerPath) {
if (typeof data !== "object" || data === null || Array.isArray(data)) {
return null;
}
return this.validateObjectMinMaxProperties(data, schema, dataPointerPath)
|| this.validateObjectRequiredProperties(data, schema, dataPointerPath)
|| this.validateObjectProperties(data, schema, dataPointerPath)
|| this.validateObjectDependencies(data, schema, dataPointerPath)
|| null;
};
ValidatorContext.prototype.validateObjectMinMaxProperties = function validateObjectMinMaxProperties(data, schema) {
var keys = Object.keys(data);
var error;
if (schema.minProperties !== undefined) {
if (keys.length < schema.minProperties) {
error = this.createError(ErrorCodes.OBJECT_PROPERTIES_MINIMUM, {propertyCount: keys.length, minimum: schema.minProperties}, '', '/minProperties', null, data, schema);
if (this.handleError(error)) {
return error;
}
}
}
if (schema.maxProperties !== undefined) {
if (keys.length > schema.maxProperties) {
error = this.createError(ErrorCodes.OBJECT_PROPERTIES_MAXIMUM, {propertyCount: keys.length, maximum: schema.maxProperties}, '', '/maxProperties', null, data, schema);
if (this.handleError(error)) {
return error;
}
}
}
return null;
};
ValidatorContext.prototype.validateObjectRequiredProperties = function validateObjectRequiredProperties(data, schema) {
if (schema.required !== undefined) {
for (var i = 0; i < schema.required.length; i++) {
var key = schema.required[i];
if (data[key] === undefined) {
var error = this.createError(ErrorCodes.OBJECT_REQUIRED, {key: key}, '', '/required/' + i, null, data, schema);
if (this.handleError(error)) {
return error;
}
}
}
}
return null;
};
ValidatorContext.prototype.validateObjectProperties = function validateObjectProperties(data, schema, dataPointerPath) {
var error;
for (var key in data) {
var keyPointerPath = dataPointerPath + "/" + key.replace(/~/g, '~0').replace(/\//g, '~1');
var foundMatch = false;
if (schema.properties !== undefined && schema.properties[key] !== undefined) {
foundMatch = true;
if (error = this.validateAll(data[key], schema.properties[key], [key], ["properties", key], keyPointerPath)) {
return error;
}
}
if (schema.patternProperties !== undefined) {
for (var patternKey in schema.patternProperties) {
var regexp = new RegExp(patternKey);
if (regexp.test(key)) {
foundMatch = true;
if (error = this.validateAll(data[key], schema.patternProperties[patternKey], [key], ["patternProperties", patternKey], keyPointerPath)) {
return error;
}
}
}
}
if (!foundMatch) {
if (schema.additionalProperties !== undefined) {
if (this.trackUnknownProperties) {
this.knownPropertyPaths[keyPointerPath] = true;
delete this.unknownPropertyPaths[keyPointerPath];
}
if (typeof schema.additionalProperties === "boolean") {
if (!schema.additionalProperties) {
error = this.createError(ErrorCodes.OBJECT_ADDITIONAL_PROPERTIES, {key: key}, '', '/additionalProperties', null, data, schema).prefixWith(key, null);
if (this.handleError(error)) {
return error;
}
}
} else {
if (error = this.validateAll(data[key], schema.additionalProperties, [key], ["additionalProperties"], keyPointerPath)) {
return error;
}
}
} else if (this.trackUnknownProperties && !this.knownPropertyPaths[keyPointerPath]) {
this.unknownPropertyPaths[keyPointerPath] = true;
}
} else if (this.trackUnknownProperties) {
this.knownPropertyPaths[keyPointerPath] = true;
delete this.unknownPropertyPaths[keyPointerPath];
}
}
return null;
};
ValidatorContext.prototype.validateObjectDependencies = function validateObjectDependencies(data, schema, dataPointerPath) {
var error;
if (schema.dependencies !== undefined) {
for (var depKey in schema.dependencies) {
if (data[depKey] !== undefined) {
var dep = schema.dependencies[depKey];
if (typeof dep === "string") {
if (data[dep] === undefined) {
error = this.createError(ErrorCodes.OBJECT_DEPENDENCY_KEY, {key: depKey, missing: dep}, '', '', null, data, schema).prefixWith(null, depKey).prefixWith(null, "dependencies");
if (this.handleError(error)) {
return error;
}
}
} else if (Array.isArray(dep)) {
for (var i = 0; i < dep.length; i++) {
var requiredKey = dep[i];
if (data[requiredKey] === undefined) {
error = this.createError(ErrorCodes.OBJECT_DEPENDENCY_KEY, {key: depKey, missing: requiredKey}, '', '/' + i, null, data, schema).prefixWith(null, depKey).prefixWith(null, "dependencies");
if (this.handleError(error)) {
return error;
}
}
}
} else {
if (error = this.validateAll(data, dep, [], ["dependencies", depKey], dataPointerPath)) {
return error;
}
}
}
}
}
return null;
};
ValidatorContext.prototype.validateCombinations = function validateCombinations(data, schema, dataPointerPath) {
return this.validateAllOf(data, schema, dataPointerPath)
|| this.validateAnyOf(data, schema, dataPointerPath)
|| this.validateOneOf(data, schema, dataPointerPath)
|| this.validateNot(data, schema, dataPointerPath)
|| null;
};
ValidatorContext.prototype.validateAllOf = function validateAllOf(data, schema, dataPointerPath) {
if (schema.allOf === undefined) {
return null;
}
var error;
for (var i = 0; i < schema.allOf.length; i++) {
var subSchema = schema.allOf[i];
if (error = this.validateAll(data, subSchema, [], ["allOf", i], dataPointerPath)) {
return error;
}
}
return null;
};
ValidatorContext.prototype.validateAnyOf = function validateAnyOf(data, schema, dataPointerPath) {
if (schema.anyOf === undefined) {
return null;
}
var errors = [];
var startErrorCount = this.errors.length;
var oldUnknownPropertyPaths, oldKnownPropertyPaths;
if (this.trackUnknownProperties) {
oldUnknownPropertyPaths = this.unknownPropertyPaths;
oldKnownPropertyPaths = this.knownPropertyPaths;
}
var errorAtEnd = true;
for (var i = 0; i < schema.anyOf.length; i++) {
if (this.trackUnknownProperties) {
this.unknownPropertyPaths = {};
this.knownPropertyPaths = {};
}
var subSchema = schema.anyOf[i];
var errorCount = this.errors.length;
var error = this.validateAll(data, subSchema, [], ["anyOf", i], dataPointerPath);
if (error === null && errorCount === this.errors.length) {
this.errors = this.errors.slice(0, startErrorCount);
if (this.trackUnknownProperties) {
for (var knownKey in this.knownPropertyPaths) {
oldKnownPropertyPaths[knownKey] = true;
delete oldUnknownPropertyPaths[knownKey];
}
for (var unknownKey in this.unknownPropertyPaths) {
if (!oldKnownPropertyPaths[unknownKey]) {
oldUnknownPropertyPaths[unknownKey] = true;
}
}
// We need to continue looping so we catch all the property definitions, but we don't want to return an error
errorAtEnd = false;
continue;
}
return null;
}
if (error) {
errors.push(error.prefixWith(null, "" + i).prefixWith(null, "anyOf"));
}
}
if (this.trackUnknownProperties) {
this.unknownPropertyPaths = oldUnknownPropertyPaths;
this.knownPropertyPaths = oldKnownPropertyPaths;
}
if (errorAtEnd) {
errors = errors.concat(this.errors.slice(startErrorCount));
this.errors = this.errors.slice(0, startErrorCount);
return this.createError(ErrorCodes.ANY_OF_MISSING, {}, "", "/anyOf", errors, data, schema);
}
};
ValidatorContext.prototype.validateOneOf = function validateOneOf(data, schema, dataPointerPath) {
if (schema.oneOf === undefined) {
return null;
}
var validIndex = null;
var errors = [];
var startErrorCount = this.errors.length;
var oldUnknownPropertyPaths, oldKnownPropertyPaths;
if (this.trackUnknownProperties) {
oldUnknownPropertyPaths = this.unknownPropertyPaths;
oldKnownPropertyPaths = this.knownPropertyPaths;
}
for (var i = 0; i < schema.oneOf.length; i++) {
if (this.trackUnknownProperties) {
this.unknownPropertyPaths = {};
this.knownPropertyPaths = {};
}
var subSchema = schema.oneOf[i];
var errorCount = this.errors.length;
var error = this.validateAll(data, subSchema, [], ["oneOf", i], dataPointerPath);
if (error === null && errorCount === this.errors.length) {
if (validIndex === null) {
validIndex = i;
} else {
this.errors = this.errors.slice(0, startErrorCount);
return this.createError(ErrorCodes.ONE_OF_MULTIPLE, {index1: validIndex, index2: i}, "", "/oneOf", null, data, schema);
}
if (this.trackUnknownProperties) {
for (var knownKey in this.knownPropertyPaths) {
oldKnownPropertyPaths[knownKey] = true;
delete oldUnknownPropertyPaths[knownKey];
}
for (var unknownKey in this.unknownPropertyPaths) {
if (!oldKnownPropertyPaths[unknownKey]) {
oldUnknownPropertyPaths[unknownKey] = true;
}
}
}
} else if (error) {
errors.push(error);
}
}
if (this.trackUnknownProperties) {
this.unknownPropertyPaths = oldUnknownPropertyPaths;
this.knownPropertyPaths = oldKnownPropertyPaths;
}
if (validIndex === null) {
errors = errors.concat(this.errors.slice(startErrorCount));
this.errors = this.errors.slice(0, startErrorCount);
return this.createError(ErrorCodes.ONE_OF_MISSING, {}, "", "/oneOf", errors, data, schema);
} else {
this.errors = this.errors.slice(0, startErrorCount);
}
return null;
};
ValidatorContext.prototype.validateNot = function validateNot(data, schema, dataPointerPath) {
if (schema.not === undefined) {
return null;
}
var oldErrorCount = this.errors.length;
var oldUnknownPropertyPaths, oldKnownPropertyPaths;
if (this.trackUnknownProperties) {
oldUnknownPropertyPaths = this.unknownPropertyPaths;
oldKnownPropertyPaths = this.knownPropertyPaths;
this.unknownPropertyPaths = {};
this.knownPropertyPaths = {};
}
var error = this.validateAll(data, schema.not, null, null, dataPointerPath);
var notErrors = this.errors.slice(oldErrorCount);
this.errors = this.errors.slice(0, oldErrorCount);
if (this.trackUnknownProperties) {
this.unknownPropertyPaths = oldUnknownPropertyPaths;
this.knownPropertyPaths = oldKnownPropertyPaths;
}
if (error === null && notErrors.length === 0) {
return this.createError(ErrorCodes.NOT_PASSED, {}, "", "/not", null, data, schema);
}
return null;
};
ValidatorContext.prototype.validateHypermedia = function validateCombinations(data, schema, dataPointerPath) {
if (!schema.links) {
return null;
}
var error;
for (var i = 0; i < schema.links.length; i++) {
var ldo = schema.links[i];
if (ldo.rel === "describedby") {
var template = new UriTemplate(ldo.href);
var allPresent = true;
for (var j = 0; j < template.varNames.length; j++) {
if (!(template.varNames[j] in data)) {
allPresent = false;
break;
}
}
if (allPresent) {
var schemaUrl = template.fillFromObject(data);
var subSchema = {"$ref": schemaUrl};
if (error = this.validateAll(data, subSchema, [], ["links", i], dataPointerPath)) {
return error;
}
}
}
}
};
// parseURI() and resolveUrl() are from https://gist.github.com/1088850
// - released as public domain by author ("Yaffle") - see comments on gist
function parseURI(url) {
var m = String(url).replace(/^\s+|\s+$/g, '').match(/^([^:\/?#]+:)?(\/\/(?:[^:@]*(?::[^:@]*)?@)?(([^:\/?#]*)(?::(\d*))?))?([^?#]*)(\?[^#]*)?(#[\s\S]*)?/);
// authority = '//' + user + ':' + pass '@' + hostname + ':' port
return (m ? {
href : m[0] || '',
protocol : m[1] || '',
authority: m[2] || '',
host : m[3] || '',
hostname : m[4] || '',
port : m[5] || '',
pathname : m[6] || '',
search : m[7] || '',
hash : m[8] || ''
} : null);
}
function resolveUrl(base, href) {// RFC 3986
function removeDotSegments(input) {
var output = [];
input.replace(/^(\.\.?(\/|$))+/, '')
.replace(/\/(\.(\/|$))+/g, '/')
.replace(/\/\.\.$/, '/../')
.replace(/\/?[^\/]*/g, function (p) {
if (p === '/..') {
output.pop();
} else {
output.push(p);
}
});
return output.join('').replace(/^\//, input.charAt(0) === '/' ? '/' : '');
}
href = parseURI(href || '');
base = parseURI(base || '');
return !href || !base ? null : (href.protocol || base.protocol) +
(href.protocol || href.authority ? href.authority : base.authority) +
removeDotSegments(href.protocol || href.authority || href.pathname.charAt(0) === '/' ? href.pathname : (href.pathname ? ((base.authority && !base.pathname ? '/' : '') + base.pathname.slice(0, base.pathname.lastIndexOf('/') + 1) + href.pathname) : base.pathname)) +
(href.protocol || href.authority || href.pathname ? href.search : (href.search || base.search)) +
href.hash;
}
function getDocumentUri(uri) {
return uri.split('#')[0];
}
function normSchema(schema, baseUri) {
if (schema && typeof schema === "object") {
if (baseUri === undefined) {
baseUri = schema.id;
} else if (typeof schema.id === "string") {
baseUri = resolveUrl(baseUri, schema.id);
schema.id = baseUri;
}
if (Array.isArray(schema)) {
for (var i = 0; i < schema.length; i++) {
normSchema(schema[i], baseUri);
}
} else {
if (typeof schema['$ref'] === "string") {
schema['$ref'] = resolveUrl(baseUri, schema['$ref']);
}
for (var key in schema) {
if (key !== "enum") {
normSchema(schema[key], baseUri);
}
}
}
}
}
function defaultErrorReporter(language) {
language = language || 'en';
var errorMessages = languages[language];
return function (error) {
var messageTemplate = errorMessages[error.code] || ErrorMessagesDefault[error.code];
if (typeof messageTemplate !== 'string') {
return "Unknown error code " + error.code + ": " + JSON.stringify(error.messageParams);
}
var messageParams = error.params;
// Adapted from Crockford's supplant()
return messageTemplate.replace(/\{([^{}]*)\}/g, function (whole, varName) {
var subValue = messageParams[varName];
return typeof subValue === 'string' || typeof subValue === 'number' ? subValue : whole;
});
};
}
var ErrorCodes = {
INVALID_TYPE: 0,
ENUM_MISMATCH: 1,
ANY_OF_MISSING: 10,
ONE_OF_MISSING: 11,
ONE_OF_MULTIPLE: 12,
NOT_PASSED: 13,
// Numeric errors
NUMBER_MULTIPLE_OF: 100,
NUMBER_MINIMUM: 101,
NUMBER_MINIMUM_EXCLUSIVE: 102,
NUMBER_MAXIMUM: 103,
NUMBER_MAXIMUM_EXCLUSIVE: 104,
NUMBER_NOT_A_NUMBER: 105,
// String errors
STRING_LENGTH_SHORT: 200,
STRING_LENGTH_LONG: 201,
STRING_PATTERN: 202,
// Object errors
OBJECT_PROPERTIES_MINIMUM: 300,
OBJECT_PROPERTIES_MAXIMUM: 301,
OBJECT_REQUIRED: 302,
OBJECT_ADDITIONAL_PROPERTIES: 303,
OBJECT_DEPENDENCY_KEY: 304,
// Array errors
ARRAY_LENGTH_SHORT: 400,
ARRAY_LENGTH_LONG: 401,
ARRAY_UNIQUE: 402,
ARRAY_ADDITIONAL_ITEMS: 403,
// Custom/user-defined errors
FORMAT_CUSTOM: 500,
KEYWORD_CUSTOM: 501,
// Schema structure
CIRCULAR_REFERENCE: 600,
// Non-standard validation options
UNKNOWN_PROPERTY: 1000
};
var ErrorCodeLookup = {};
for (var key in ErrorCodes) {
ErrorCodeLookup[ErrorCodes[key]] = key;
}
var ErrorMessagesDefault = {
INVALID_TYPE: "Invalid type: {type} (expected {expected})",
ENUM_MISMATCH: "No enum match for: {value}",
ANY_OF_MISSING: "Data does not match any schemas from \"anyOf\"",
ONE_OF_MISSING: "Data does not match any schemas from \"oneOf\"",
ONE_OF_MULTIPLE: "Data is valid against more than one schema from \"oneOf\": indices {index1} and {index2}",
NOT_PASSED: "Data matches schema from \"not\"",
// Numeric errors
NUMBER_MULTIPLE_OF: "Value {value} is not a multiple of {multipleOf}",
NUMBER_MINIMUM: "Value {value} is less than minimum {minimum}",
NUMBER_MINIMUM_EXCLUSIVE: "Value {value} is equal to exclusive minimum {minimum}",
NUMBER_MAXIMUM: "Value {value} is greater than maximum {maximum}",
NUMBER_MAXIMUM_EXCLUSIVE: "Value {value} is equal to exclusive maximum {maximum}",
NUMBER_NOT_A_NUMBER: "Value {value} is not a valid number",
// String errors
STRING_LENGTH_SHORT: "String is too short ({length} chars), minimum {minimum}",
STRING_LENGTH_LONG: "String is too long ({length} chars), maximum {maximum}",
STRING_PATTERN: "String does not match pattern: {pattern}",
// Object errors
OBJECT_PROPERTIES_MINIMUM: "Too few properties defined ({propertyCount}), minimum {minimum}",
OBJECT_PROPERTIES_MAXIMUM: "Too many properties defined ({propertyCount}), maximum {maximum}",
OBJECT_REQUIRED: "Missing required property: {key}",
OBJECT_ADDITIONAL_PROPERTIES: "Additional properties not allowed",
OBJECT_DEPENDENCY_KEY: "Dependency failed - key must exist: {missing} (due to key: {key})",
// Array errors
ARRAY_LENGTH_SHORT: "Array is too short ({length}), minimum {minimum}",
ARRAY_LENGTH_LONG: "Array is too long ({length}), maximum {maximum}",
ARRAY_UNIQUE: "Array items are not unique (indices {match1} and {match2})",
ARRAY_ADDITIONAL_ITEMS: "Additional items not allowed",
// Format errors
FORMAT_CUSTOM: "Format validation failed ({message})",
KEYWORD_CUSTOM: "Keyword failed: {key} ({message})",
// Schema structure
CIRCULAR_REFERENCE: "Circular $refs: {urls}",
// Non-standard validation options
UNKNOWN_PROPERTY: "Unknown property (not in schema)"
};
function ValidationError(code, params, dataPath, schemaPath, subErrors) {
Error.call(this);
if (code === undefined) {
throw new Error ("No error code supplied: " + schemaPath);
}
this.message = '';
this.params = params;
this.code = code;
this.dataPath = dataPath || "";
this.schemaPath = schemaPath || "";
this.subErrors = subErrors || null;
var err = new Error(this.message);
this.stack = err.stack || err.stacktrace;
if (!this.stack) {
try {
throw err;
}
catch(err) {
this.stack = err.stack || err.stacktrace;
}
}
}
ValidationError.prototype = Object.create(Error.prototype);
ValidationError.prototype.constructor = ValidationError;
ValidationError.prototype.name = 'ValidationError';
ValidationError.prototype.prefixWith = function (dataPrefix, schemaPrefix) {
if (dataPrefix !== null) {
dataPrefix = dataPrefix.replace(/~/g, "~0").replace(/\//g, "~1");
this.dataPath = "/" + dataPrefix + this.dataPath;
}
if (schemaPrefix !== null) {
schemaPrefix = schemaPrefix.replace(/~/g, "~0").replace(/\//g, "~1");
this.schemaPath = "/" + schemaPrefix + this.schemaPath;
}
if (this.subErrors !== null) {
for (var i = 0; i < this.subErrors.length; i++) {
this.subErrors[i].prefixWith(dataPrefix, schemaPrefix);
}
}
return this;
};
function isTrustedUrl(baseUrl, testUrl) {
if(testUrl.substring(0, baseUrl.length) === baseUrl){
var remainder = testUrl.substring(baseUrl.length);
if ((testUrl.length > 0 && testUrl.charAt(baseUrl.length - 1) === "/")
|| remainder.charAt(0) === "#"
|| remainder.charAt(0) === "?") {
return true;
}
}
return false;
}
var languages = {};
function createApi(language) {
var globalContext = new ValidatorContext();
var currentLanguage;
var customErrorReporter;
var api = {
setErrorReporter: function (reporter) {
if (typeof reporter === 'string') {
return this.language(reporter);
}
customErrorReporter = reporter;
return true;
},
addFormat: function () {
globalContext.addFormat.apply(globalContext, arguments);
},
language: function (code) {
if (!code) {
return currentLanguage;
}
if (!languages[code]) {
code = code.split('-')[0]; // fall back to base language
}
if (languages[code]) {
currentLanguage = code;
return code; // so you can tell if fall-back has happened
}
return false;
},
addLanguage: function (code, messageMap) {
var key;
for (key in ErrorCodes) {
if (messageMap[key] && !messageMap[ErrorCodes[key]]) {
messageMap[ErrorCodes[key]] = messageMap[key];
}
}
var rootCode = code.split('-')[0];
if (!languages[rootCode]) { // use for base language if not yet defined
languages[code] = messageMap;
languages[rootCode] = messageMap;
} else {
languages[code] = Object.create(languages[rootCode]);
for (key in messageMap) {
if (typeof languages[rootCode][key] === 'undefined') {
languages[rootCode][key] = messageMap[key];
}
languages[code][key] = messageMap[key];
}
}
return this;
},
freshApi: function (language) {
var result = createApi();
if (language) {
result.language(language);
}
return result;
},
validate: function (data, schema, checkRecursive, banUnknownProperties) {
var def = defaultErrorReporter(currentLanguage);
var errorReporter = customErrorReporter ? function (error, data, schema) {
return customErrorReporter(error, data, schema) || def(error, data, schema);
} : def;
var context = new ValidatorContext(globalContext, false, errorReporter, checkRecursive, banUnknownProperties);
if (typeof schema === "string") {
schema = {"$ref": schema};
}
context.addSchema("", schema);
var error = context.validateAll(data, schema, null, null, "");
if (!error && banUnknownProperties) {
error = context.banUnknownProperties(data, schema);
}
this.error = error;
this.missing = context.missing;
this.valid = (error === null);
this.toString = function () {
if (this.error) {
return this.error.message;
} else {
return 'Object passed schema validation';
}
};
return this.valid;
},
validateResult: function () {
var result = {};
this.validate.apply(result, arguments);
return result;
},
validateMultiple: function (data, schema, checkRecursive, banUnknownProperties) {
var def = defaultErrorReporter(currentLanguage);
var errorReporter = customErrorReporter ? function (error, data, schema) {
return customErrorReporter(error, data, schema) || def(error, data, schema);
} : def;
var context = new ValidatorContext(globalContext, true, errorReporter, checkRecursive, banUnknownProperties);
if (typeof schema === "string") {
schema = {"$ref": schema};
}
context.addSchema("", schema);
context.validateAll(data, schema, null, null, "");
if (banUnknownProperties) {
context.banUnknownProperties(data, schema);
}
var result = {};
result.errors = context.errors;
result.missing = context.missing;
result.valid = (result.errors.length === 0);
return result;
},
addSchema: function () {
return globalContext.addSchema.apply(globalContext, arguments);
},
getSchema: function () {
return globalContext.getSchema.apply(globalContext, arguments);
},
getSchemaMap: function () {
return globalContext.getSchemaMap.apply(globalContext, arguments);
},
getSchemaUris: function () {
return globalContext.getSchemaUris.apply(globalContext, arguments);
},
getMissingUris: function () {
return globalContext.getMissingUris.apply(globalContext, arguments);
},
dropSchemas: function () {
globalContext.dropSchemas.apply(globalContext, arguments);
},
defineKeyword: function () {
globalContext.defineKeyword.apply(globalContext, arguments);
},
defineError: function (codeName, codeNumber, defaultMessage) {
if (typeof codeName !== 'string' || !/^[A-Z]+(_[A-Z]+)*$/.test(codeName)) {
throw new Error('Code name must be a string in UPPER_CASE_WITH_UNDERSCORES');
}
if (typeof codeNumber !== 'number' || codeNumber%1 !== 0 || codeNumber < 10000) {
throw new Error('Code number must be an integer > 10000');
}
if (typeof ErrorCodes[codeName] !== 'undefined') {
throw new Error('Error already defined: ' + codeName + ' as ' + ErrorCodes[codeName]);
}
if (typeof ErrorCodeLookup[codeNumber] !== 'undefined') {
throw new Error('Error code already used: ' + ErrorCodeLookup[codeNumber] + ' as ' + codeNumber);
}
ErrorCodes[codeName] = codeNumber;
ErrorCodeLookup[codeNumber] = codeName;
ErrorMessagesDefault[codeName] = ErrorMessagesDefault[codeNumber] = defaultMessage;
for (var langCode in languages) {
var language = languages[langCode];
if (language[codeName]) {
language[codeNumber] = language[codeNumber] || language[codeName];
}
}
},
reset: function () {
globalContext.reset();
this.error = null;
this.missing = [];
this.valid = true;
},
missing: [],
error: null,
valid: true,
normSchema: normSchema,
resolveUrl: resolveUrl,
getDocumentUri: getDocumentUri,
errorCodes: ErrorCodes
};
api.language(language || 'en');
return api;
}
var tv4 = createApi();
tv4.addLanguage('en-gb', ErrorMessagesDefault);
//legacy property
tv4.tv4 = tv4;
return tv4; // used by _header.js to globalise.
}));
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="File" module="OFS.Image"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>__name__</string> </key>
<value> <string>tv4.js</string> </value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>application/javascript</string> </value>
</item>
<item>
<key> <string>precondition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>tv4.js</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
jsonform is available at:
https://lab.nexedi.com/bk/rjs_json_form
\ No newline at end of file
erp5_view_style
\ No newline at end of file
Form generation based on http://json-schema.org/
\ No newline at end of file
bk
\ No newline at end of file
erp5_json_form
\ No newline at end of file
erp5_json_form
\ No newline at end of file
5.4.7
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment