22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/demos/index-edit.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/tests/tests/04 - Default values and data creation/08 - baseUri.js:
--------------------------------------------------------------------------------
1 | tests.add("can specify baseUri", function () {
2 | var schema1 = Jsonary.createSchema({
3 | "type": "string",
4 | "default": ":)"
5 | });
6 | var createdData = schema1.createData(5, "http://example.com/");
7 |
8 | this.assert(createdData.resolveUrl('') == 'http://example.com/', "base URI matches");
9 | return true;
10 | });
11 |
12 | tests.add("take baseUri from data", function () {
13 | var data1 = Jsonary.create(":)", "http://example.com/");
14 |
15 | var schema1 = Jsonary.createSchema({
16 | "type": "string",
17 | "default": ":)"
18 | });
19 | var createdData = schema1.createData(data1);
20 |
21 | this.assert(createdData.value() === ":)", "value matches");
22 | this.assert(createdData.resolveUrl('') == 'http://example.com/', "base URI matches: " + createdData.resolveUrl(''));
23 | return true;
24 | });
25 |
26 |
27 | tests.add("submission data has baseUri", function () {
28 | var data1 = Jsonary.create(":)", "http://example.com/");
29 | data1.addLink({
30 | rel: 'test',
31 | href: 'blah',
32 | schema: {
33 | type: 'string',
34 | links: [{
35 | rel: 'self',
36 | href: 'foo/bar'
37 | }]
38 | }
39 | });
40 |
41 | var createdData = data1.getLink('test').createSubmissionData();
42 |
43 | this.assert(createdData.resolveUrl('') == 'http://example.com/foo/bar', "base URI matches: " + createdData.resolveUrl(''));
44 | return true;
45 | });
--------------------------------------------------------------------------------
/tests/render-tests/00 - Bugs/01 - re-rendering remote items.js:
--------------------------------------------------------------------------------
1 | tests.add("Preserve state when re-rendering remote items (as sub-render)", function() {
2 | var thisTest = this;
3 | // Simple read-only boolean
4 | var data = Jsonary.create(true, null, true);
5 |
6 | var remoteUrl = "http://example.com/data/" + Math.random();
7 | Jsonary.addToCache(remoteUrl, "Remote data");
8 |
9 | var renderer = Jsonary.render.register({
10 | name: "Test renderer",
11 | renderHtml: function (data, context) {
12 | return context.renderHtml(remoteUrl, 'remote');
13 | },
14 | filter: function (d) {
15 | return d === data;
16 | }
17 | });
18 | var remoteUiState = null;
19 | Jsonary.render.register({
20 | name: "Remote data",
21 | renderHtml: function (data, context) {
22 | if (remoteUiState) {
23 | thisTest.assert(remoteUiState == context.uiState, "UI state should not be reset");
24 | } else {
25 | context.uiState.testVar = Math.random();
26 | remoteUiState = context.uiState;
27 | }
28 | return Jsonary.escapeHtml(data.value()) + ": " + context.uiState.testVar;
29 | },
30 | filter: function (d) {
31 | return d.document.url == remoteUrl;
32 | }
33 | });
34 |
35 | Jsonary.asyncRenderHtml(data, null, function (error, html, renderContext) {
36 | renderContext.asyncRerenderHtml(function (error2, html2, renderContext) {
37 | thisTest.assert(html == html2, "HTML renderings should be equivalent");
38 | thisTest.pass();
39 | });
40 | });
41 | });
--------------------------------------------------------------------------------
/tests/tests/05 - Tertiary tests/04 - Objects.js:
--------------------------------------------------------------------------------
1 | tests.add("minProperties() and maxProperties", function() {
2 | var schema1 = Jsonary.createSchema({
3 | maxProperties: 10,
4 | minProperties: 1
5 | });
6 | var schema2 = Jsonary.createSchema({
7 | maxProperties: 5,
8 | minProperties: 0
9 | });
10 | var schema3 = Jsonary.createSchema({
11 | maxProperties: 5,
12 | minProperties: 5
13 | });
14 | var data = Jsonary.create({"key": true});
15 |
16 | var maxProperties = data.schemas().maxProperties();
17 | this.assert(maxProperties == null, "maxProperties == null");
18 | var minProperties = data.schemas().minProperties();
19 | this.assert(minProperties == 0, "minProperties == 0");
20 |
21 | data.addSchema(schema1);
22 | var maxProperties = data.schemas().maxProperties();
23 | this.assert(maxProperties == 10, "maxProperties == 10");
24 | var minProperties = data.schemas().minProperties();
25 | this.assert(minProperties == 1, "minProperties == 1");
26 |
27 | data.addSchema(schema2);
28 | var maxProperties = data.schemas().maxProperties();
29 | this.assert(maxProperties == 5, "maxProperties == 5");
30 | var minProperties = data.schemas().minProperties();
31 | this.assert(minProperties == 1, "minProperties stil == 1");
32 |
33 | data.addSchema(schema3);
34 | var maxProperties = data.schemas().maxProperties();
35 | this.assert(maxProperties == 5, "maxProperties still == 5");
36 | var minProperties = data.schemas().minProperties();
37 | this.assert(minProperties == 5, "minProperties == 5");
38 |
39 | return true;
40 | });
41 |
42 |
--------------------------------------------------------------------------------
/tests/render-tests/02 - Link-handlers in renderers/01 - render and test link handler.js:
--------------------------------------------------------------------------------
1 | tests.add("Link handler called", function() {
2 | var thisTest = this;
3 | // Simple read-only boolean
4 | var data = Jsonary.create(true, null, true);
5 | var linkRel = "whatever" + Math.random();
6 | data.addLink({href: "wherever", rel: linkRel});
7 |
8 | var savedContext = null;
9 | var actionCalled = false;
10 | var handlerCalled = false;
11 |
12 | var renderer = Jsonary.render.register({
13 | name: "Test renderer",
14 | renderHtml: function (data, context) {
15 | savedContext = context;
16 | return ":)";
17 | },
18 | action: {
19 | "follow-link": function (data, context, param1) {
20 | actionCalled = true;
21 | thisTest.assert(Jsonary.isData(data));
22 | thisTest.assert(param1 === "test", "param1 === test");
23 | var link = data.getLink(linkRel);
24 | link.follow();
25 | }
26 | },
27 | linkHandler: function (data, context, link, submissionData, request) {
28 | thisTest.assert(this === renderer, "this === renderer");
29 | thisTest.assert(Jsonary.isData(data), "data is data");
30 | handlerCalled = true;
31 | },
32 | filter: function (d) {
33 | return d === data;
34 | }
35 | });
36 |
37 | Jsonary.asyncRenderHtml(data, null, function (error, html) {
38 | thisTest.assert(savedContext, "savedContext not empty");
39 | savedContext.action('follow-link', 'test');
40 | thisTest.assert(actionCalled, "action called");
41 | thisTest.assert(handlerCalled, "handler called");
42 | thisTest.pass();
43 | });
44 | });
--------------------------------------------------------------------------------
/tests/tests/03 - Interaction/04 - Constraints.js:
--------------------------------------------------------------------------------
1 | var exampleSchemaMinimal = {
2 | };
3 | var exampleSchemaFull = {
4 | "enum": [null, true, 2, "3", [4], {5:5}],
5 | "type": "integer"
6 | };
7 | var exampleSchemaFull2 = {
8 | "type": ["string", "boolean", {}]
9 | };
10 |
11 | tests.add("enumData()", function () {
12 | var schemaMinimal = Jsonary.createSchema(exampleSchemaMinimal);
13 | var schemaFull = Jsonary.createSchema(exampleSchemaFull);
14 | var expected;
15 |
16 | expected = Jsonary.create(exampleSchemaFull["enum"]);
17 | this.assert(schemaFull.enumData().equals(expected), "enumList() should return the contents of enum when present");
18 |
19 | this.assert(!schemaMinimal.enumData().defined(), "enumList() should not be defined");
20 |
21 | return true;
22 | });
23 |
24 | tests.add("basicTypes()", function () {
25 | var schemaMinimal = Jsonary.createSchema(exampleSchemaMinimal);
26 | var schemaFull = Jsonary.createSchema(exampleSchemaFull);
27 | var schemaFull2 = Jsonary.createSchema(exampleSchemaFull2);
28 | var expected;
29 | this.assert(schemaMinimal.basicTypes().length == 7, "basicTypes() should return a complete list when not present");
30 |
31 | expected = ["integer"];
32 | this.assert(recursiveCompare(schemaFull.basicTypes(), expected), "basicTypes() should return a list, even when only one type is specified");
33 |
34 | // Does the order matter? Perhaps it shouldn't.
35 | this.assert(schemaFull2.basicTypes().length == 7, "basicTypes() should return a full list of the basic types in the list when object is in types array");
36 |
37 | return true;
38 | });
--------------------------------------------------------------------------------
/tests/tests/06 - Live data and requests/01 - Schema hints.js:
--------------------------------------------------------------------------------
1 | tests.add("Schema hint (test.json)", function () {
2 | var thisTest = this;
3 | Jsonary.addToCache("http://example.com/schema", {
4 | "title": "Test"
5 | });
6 | var request = Jsonary.getData("test.json?test=Schema_hint", function (data, req) {
7 | var schemas = data.schemas();
8 | thisTest.assert(schemas.length == 1, "schemas.length == 1, not " + schemas.length);
9 | thisTest.assert(schemas[0].title() == "Test", "schemas[0].title() == \"Test\", not " + JSON.stringify(schemas[0].title));
10 | thisTest.pass();
11 | }, "http://example.com/schema");
12 | setTimeout(function () {
13 | thisTest.fail("Timeout");
14 | }, 50);
15 | });
16 |
17 | tests.add("Schema hint from link (test.json)", function () {
18 | var thisTest = this;
19 | Jsonary.addToCache("http://example.com/schema", {
20 | "title": "Test"
21 | });
22 | var linkDefinition = Jsonary.create({
23 | "href": "test.json?test=Schema_hint_from_link",
24 | "rel": "test",
25 | "targetSchema": {
26 | "$ref": "http://example.com/schema"
27 | }
28 | });
29 | var link = linkDefinition.asLink();
30 |
31 | var request = link.follow();
32 | request.getData(function (data, req) {
33 | var schemas = data.schemas();
34 | thisTest.assert(schemas.length == 1, "schemas.length == 1, not " + schemas.length);
35 | thisTest.assert(schemas[0].title() == "Test", "schemas[0].title() == \"Test\", not " + JSON.stringify(schemas[0].title));
36 | thisTest.pass();
37 | }, "http://example.com/schema");
38 | setTimeout(function () {
39 | thisTest.fail("Timeout");
40 | }, 50);
41 | });
42 |
--------------------------------------------------------------------------------
/tests/tests/03 - Interaction/05 - Batch editing.js:
--------------------------------------------------------------------------------
1 | tests.add("batch() and endBatch", function () {
2 | var callbackCount = 0;
3 | Jsonary.registerChangeListener(function (patch, document) {
4 | callbackCount++;
5 | });
6 |
7 | var data = Jsonary.create("");
8 | Jsonary.batch();
9 | data.setValue({});
10 | data.property("key").setValue("value");
11 | this.assert(callbackCount == 0, "callbackCount == 0");
12 | Jsonary.batchDone();
13 | this.assert(callbackCount == 1, "callbackCount == 1");
14 | return true;
15 | });
16 |
17 | tests.add("batch(fn)", function () {
18 | var callbackCount = 0;
19 | Jsonary.registerChangeListener(function (patch, document) {
20 | callbackCount++;
21 | });
22 |
23 | var data = Jsonary.create("");
24 | Jsonary.batch(function () {
25 | data.setValue({});
26 | data.property("key").setValue("value");
27 | });
28 | this.assert(callbackCount == 1, "callbackCount == 1");
29 | return true;
30 | });
31 |
32 | tests.add("patch.inverse()", function () {
33 | var callbackCount = 0;
34 | var patch = null;
35 | Jsonary.registerChangeListener(function (p, document) {
36 | callbackCount++;
37 | patch = p;
38 | });
39 |
40 | var data = Jsonary.create({key1: "value 1"});
41 | Jsonary.batch(function () {
42 | data.property("key1").setValue("value 2");
43 | data.property("key2").setValue("blah");
44 | });
45 | var inversePatch = patch.inverse();
46 | data.document.patch(inversePatch);
47 | this.assert(data.propertyValue("key1") == "value 1", "data.key1 == value 1");
48 | this.assert(data.property("key2").defined() == false, "data.key2 not defined");
49 | return true;
50 | });
51 |
52 |
--------------------------------------------------------------------------------
/renderers/string-formats.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | // Display string
3 | Jsonary.render.register({
4 | renderHtml: function (data, context) {
5 | var date = new Date(data.value());
6 | if (isNaN(date.getTime())) {
7 | return '
' + Jsonary.escapeHtml(data.value()) + ' ';
8 | } else {
9 | return '
' + date.toLocaleString() + ' ';
10 | }
11 | },
12 | filter: {
13 | type: 'string',
14 | readOnly: true,
15 | filter: function (data, schemas) {
16 | return schemas.formats().indexOf("date-time") != -1;
17 | }
18 | }
19 | });
20 |
21 | // Display string
22 | Jsonary.render.register({
23 | renderHtml: function (data, context) {
24 | if (data.readOnly()) {
25 | if (context.uiState.showPassword) {
26 | return Jsonary.escapeHtml(data.value());
27 | } else {
28 | return context.actionHtml('(show password)', 'show-password');
29 | }
30 | } else {
31 | var inputName = context.inputNameForAction('update');
32 | return '
';
33 | }
34 | },
35 | action: function (context, actionName, arg1) {
36 | if (actionName == "show-password") {
37 | context.uiState.showPassword = true;
38 | return true;
39 | } else if (actionName == "update") {
40 | context.data.setValue(arg1);
41 | }
42 | },
43 | filter: {
44 | type: 'string',
45 | filter: function (data, schemas) {
46 | return schemas.formats().indexOf("password") != -1;
47 | }
48 | }
49 | });
50 | })();
51 |
--------------------------------------------------------------------------------
/tests/tests/07 - Plugins/01 - Extending Jsonary.js:
--------------------------------------------------------------------------------
1 | tests.add("Adding nothing", function () {
2 | Jsonary.extend({});
3 | return true;
4 | });
5 |
6 | tests.add("Adding function to main object", function () {
7 | Jsonary.extend({
8 | extendingJsonaryTest1: function() {return "test string"}
9 | });
10 | return Jsonary.extendingJsonaryTest1() == "test string";
11 | });
12 |
13 | tests.add("Adding function to data objects", function () {
14 | var data = Jsonary.create("test");
15 | Jsonary.extendData({
16 | annotatedValue: function() {
17 | return this.value() + " (" + this.basicType() + ")";
18 | }
19 | });
20 | return data.annotatedValue() == "test (string)";
21 | });
22 |
23 | tests.add("Adding function to schema objects", function () {
24 | var schema = Jsonary.createSchema({"title": "Test Schema"});
25 | Jsonary.extendSchema({
26 | annotatedTitle: function() {
27 | return this.title() + " (schema)";
28 | }
29 | });
30 | return schema.annotatedTitle() == "Test Schema (schema)";
31 | });
32 |
33 |
34 | tests.add("Adding function to schema-set objects", function () {
35 | var schemas = Jsonary.createSchemaList([
36 | Jsonary.createSchema({"title": "Title 1"}),
37 | Jsonary.createSchema({"title": "Title 2"})
38 | ]);
39 | Jsonary.extendSchemaList({
40 | combinedTitle: function() {
41 | var titles = [];
42 | this.each(function (index, schema) {
43 | if (schema.title()) {
44 | titles.push(schema.title());
45 | }
46 | });
47 | return titles.join(",");
48 | }
49 | });
50 | this.assert(schemas.combinedTitle() == "Title 1,Title 2", "value not correct: " + schemas.combinedTitle());
51 | return true;
52 | });
53 |
--------------------------------------------------------------------------------
/site/pages/index.json:
--------------------------------------------------------------------------------
1 | {"title":"What is Jsonary?","blocks":[{"content":["Jsonary is a library that deals with [JSON (Hyper-)Schema](http:\/\/json-schema.org\/).\n\nIt takes away all the detail of interpreting schemas and hyper-links, leaving you with a full hypermedia interpretation of your JSON data.\n\nJsonary also includes a rendering system, so you can embed your JSON data into web pages in a fully customisable way.\n",{"title":"Why should I use it?","content":["[JSON Schema](http:\/\/json-schema.org\/) can describe your existing JSON format\/API as hypermedia: links, forms, multimedia interpretations, the whole deal.\n\nBy building your JSON client using Jsonary, your client can take all its hypertext cues from the schema - and less hard-coded knowledge in the client means more flexibility.\n"]}]},{"title":"Examples","content":["Here is some example code that fetches a JSON document via AJAX and inspects it:",{"gist":"geraintluff\/6f5b7bc104f2f2bac9d6"},"And here is an example of rendering some editable JSON data, according to some [schema constraints](examples\/basic\/schema.json):",{"demoId":"example-block","initialText":"Something's wrong - there should be an example here","run-button":false,"javascript":"Jsonary.getData(SITE_ROOT + 'examples\/basic\/', function (data) {\n\tvar target = document.getElementById('example-block');\n\tif (target) {\n\t\tJsonary.render(target, data.editableCopy());\n\t}\n});"}]},{"title":"Get started!","content":["Just download our [get-started bundle](get-started-bundle.zip) and [read the guide](?page=get-started-guide) to get going with Jsonary and JSON Schemas.\n\nAlternatively, you can browse our [interactive guide to JSON Schema](?page=schema-guide) to get a feel for how JSON Schema works.\n"]}]}
--------------------------------------------------------------------------------
/renderers/list-schemas.js:
--------------------------------------------------------------------------------
1 | (function (Jsonary) {
2 |
3 | Jsonary.render.Components.add("LIST_SCHEMAS");
4 |
5 | Jsonary.render.register({
6 | name: "Jsonary list clickable schema titles",
7 | component: Jsonary.render.Components.LIST_SCHEMAS,
8 | update: function (element, data, context, operation) {
9 | // We don't care about data changes - when the schemas change, a re-render is forced anyway.
10 | return false;
11 | },
12 | renderHtml: function (data, context) {
13 | var result = "";
14 | data.schemas().each(function (index, schema) {
15 | if (schema.title() == null) {
16 | return;
17 | }
18 | var html = '
' + Jsonary.escapeHtml(schema.title()) + ' ';
19 | result += context.actionHtml(html, 'view-schema', index);
20 | });
21 | if (context.uiState.viewSchema != undefined) {
22 | var schema = data.schemas()[context.uiState.viewSchema];
23 | result += '
';
24 | result += context.actionHtml('
', 'hide-schema');
25 | result += '
' + Jsonary.escapeHtml(schema.title()) + ' ' + schema.referenceUrl() + ' '
26 | + Jsonary.escapeHtml(JSON.stringify(schema.data.value(), null, 4))
27 | + ' ';
28 | result += '
';
29 | }
30 | result += context.renderHtml(data);
31 | return result;
32 | },
33 | action: function (context, actionName, arg1) {
34 | if (actionName == "view-schema") {
35 | context.uiState.viewSchema = arg1;
36 | return true;
37 | } else {
38 | delete context.uiState.viewSchema;
39 | return true;
40 | }
41 | }
42 | });
43 | })(Jsonary);
44 |
--------------------------------------------------------------------------------
/tests/tests/Bugs/Recursive dependencies.js:
--------------------------------------------------------------------------------
1 | tests.add("Recursive dependency check", function () {
2 | var data = Jsonary.create({
3 | properties: {
4 | arbitrary: {
5 | dependencyKey: true
6 | }
7 | }
8 | }, null, true);
9 | var schema = Jsonary.createSchema({
10 | properties: {
11 | "properties": {
12 | additionalProperties: {"$ref": "#"}
13 | }
14 | },
15 | dependencies: {
16 | "dependencyKey": {"title": "Dependency"}
17 | }
18 | });
19 | data.addSchema(schema);
20 |
21 | this.assert(data.schemas().length == 1, "data.schemas().length == 1, was " + data.schemas().length);
22 | var innerSchemas = data.property("properties").property("arbitrary").schemas();
23 | this.assert(innerSchemas.length == 2, "innerSchemas.length == 2, was " + innerSchemas.length);
24 |
25 | return true;
26 | });
27 |
28 | tests.add("Recursive dependency check 2", function () {
29 | var data = Jsonary.create({
30 | "properties":{
31 | "createSchema":{
32 | "arguments":true
33 | }
34 | }
35 | }, null, true);
36 | var schema = Jsonary.createSchema({
37 | "title": "API documentation",
38 | "properties": {
39 | "properties": {
40 | "title": "Object properties",
41 | "additionalProperties": {"$ref": "#"}
42 | }
43 | },
44 | "dependencies": {
45 | "arguments": {"title": "Dependency schema"}
46 | }
47 | });
48 | data.addSchema(schema);
49 |
50 | this.assert(data.schemas().length == 1, "data.schemas().length == 1, was " + data.schemas().length);
51 | var innerSchemas = data.property("properties").property("createSchema").schemas();
52 | this.assert(innerSchemas.length == 2, "innerSchemas.length == 2, was " + innerSchemas.length);
53 |
54 | return true;
55 | });
56 |
--------------------------------------------------------------------------------
/tests/tests/01 - Basic tests/04 - Base URLs.js:
--------------------------------------------------------------------------------
1 | var exampleData = [0,{"test":2},2,3,4];
2 |
3 | tests.add("Base url", function() {
4 | var baseUrl = "http://example.com/test.json";
5 | var data = Jsonary.create(exampleData, baseUrl, true);
6 | var data2 = Jsonary.create(exampleData, baseUrl + "#", true);
7 |
8 | var expectedUrl = baseUrl + "#";
9 | this.assert(data.referenceUrl() === expectedUrl, "data.referenceUrl(): " + JSON.stringify(data.referenceUrl()) + " does not match " + JSON.stringify(expectedUrl));
10 | this.assert(data2.referenceUrl() === expectedUrl, "data2.referenceUrl(): " + JSON.stringify(data2.referenceUrl()) + " does not match " + JSON.stringify(expectedUrl));
11 | return true;
12 | });
13 |
14 | tests.add("Sub-data fragments", function() {
15 | var expectedUrl, actual;
16 | var baseUrl = "http://example.com/test.json";
17 | var data = Jsonary.create(exampleData, baseUrl, true);
18 | var data2 = Jsonary.create(exampleData, baseUrl + "#not_a_pointer", true);
19 |
20 | expectedUrl = baseUrl + "#/0";
21 | actual = data.index(0).referenceUrl();
22 | this.assert(actual === expectedUrl, "data.index(0).referenceUrl(): " + JSON.stringify(actual) + " does not match " + JSON.stringify(expectedUrl));
23 |
24 | expectedUrl = baseUrl + "#/1/test";
25 | actual = data.index(1).property("test").referenceUrl();
26 | this.assert(actual === expectedUrl, "data.index(1).property(\"test\").referenceUrl(): " + JSON.stringify(actual) + " does not match " + JSON.stringify(expectedUrl));
27 |
28 | actual = data2.index(0).referenceUrl();
29 | this.assert(actual === undefined, "data2.index(0).referenceUrl() should be undefined, not " + actual);
30 | return true;
31 | });
32 |
33 |
--------------------------------------------------------------------------------
/renderers/contributed/json-schema.css:
--------------------------------------------------------------------------------
1 | .json-schema-obj {
2 | background-color: #F0F0E8;
3 | border: 1px solid black;
4 | font-size: 12px;
5 | padding: 0;
6 | font-family: Trebuchet MS, sans;
7 | border-radius: 3px;
8 | }
9 |
10 | .json-schema-obj .expand {
11 | float: right;
12 | font-weight: bold;
13 | margin-right: 1em;
14 | }
15 |
16 | .json-schema-obj > h1 {
17 | font-size: 1.1em;
18 | font-weight: bold;
19 | margin: 0;
20 | padding: 0.3em;
21 | background-color: #E0E0E8;
22 | border-bottom: 1px solid #BBB;
23 | border-radius: 3px;
24 | }
25 |
26 | .json-schema-obj > .content > h2, .json-schema-tab-content > h2 {
27 | font-size: 1.1em;
28 | font-weight: bold;
29 | margin: 0;
30 | padding-left: 0.3em;
31 | }
32 |
33 | .json-schema-obj > .content > .section, .json-schema-tab-content > .section {
34 | padding-left: 2em;
35 | }
36 |
37 | .json-schema-tab-bar {
38 | display: block;
39 | position: relative;
40 | padding-left: 0.5em;
41 | border-bottom: 1px solid black;
42 | }
43 |
44 | .json-schema-tab-button {
45 | position: relative;
46 | top: 1px;
47 | display: block;
48 | float: left;
49 | border: 1px solid black;
50 | border-top-left-radius: 3px;
51 | border-top-right-radius: 3px;
52 | background-color: #F2F2ED;
53 | padding: 0.3em;
54 | padding-left: 1em;
55 | padding-right: 1em;
56 | margin-right: -1px;
57 | }
58 |
59 | .json-schema-tab-button.current {
60 | top: 2px;
61 | border-bottom: none;
62 | background-color: #FFF;
63 | }
64 |
65 | .json-schema-tab-content {
66 | clear: left;
67 | padding: 0.3em;
68 | background-color: #FFF;
69 | border-radius: 3px;
70 | }
71 |
72 | .json-schema-ref {
73 | border: 1px solid black;
74 | border-radius: 3px;
75 | border-bottom: none;
76 | background-color: #F0F0E8;
77 | padding-left: 0.2em;
78 | }
79 |
--------------------------------------------------------------------------------
/blog/post.php:
--------------------------------------------------------------------------------
1 | postId = $postId;
9 |
10 | $commentDir = COMMENT_DIR.$postId."/";
11 | if (!file_exists($commentDir)) {
12 | mkdir($commentDir, 0777, TRUE);
13 | }
14 |
15 | if ($_SERVER['REQUEST_METHOD'] == "POST") {
16 | $submittedData = json_decode(file_get_contents("php://input"));
17 | if ($submittedData != null) {
18 | $author = $submittedData->author;
19 | $message = $submittedData->message;
20 | if (!is_string($author) || !is_string($message) || strlen($author) > 25 || strlen($message) > 200 || strlen($author) == 0 || strlen($message) == 0) {
21 | die('{"error": "Invalid parameters"}');
22 | } else {
23 | $newComment = array(
24 | "author" => $author,
25 | "message" => $message,
26 | "date" => date(DATE_ISO8601)
27 | );
28 | $safeAuthor = rawurlencode(substr($author, 0, 10));
29 | $newCommentFilename = $commentDir.$newComment['date'].$safeAuthor.".json";
30 | if (!file_put_contents($newCommentFilename, json_encode($newComment))) {
31 | die('{"error": "Error saving comment"}');
32 | }
33 | }
34 | }
35 | }
36 |
37 | $comments = array();
38 | $commentFilenames = scandir($commentDir);
39 | sort($commentFilenames);
40 | foreach ($commentFilenames as $filename) {
41 | if ($filename[0] == "." || is_dir($filename)) {
42 | continue;
43 | }
44 | $commentData = json_decode(file_get_contents($commentDir.$filename));
45 | $comments[] = $commentData;
46 | }
47 | $postData->comments = $comments;
48 |
49 | header("Content-Type: application/json; profile=schemas/post.json");
50 | echo (json_encode($postData));
51 | ?>
52 |
--------------------------------------------------------------------------------
/blog/schemas/post.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Blog post",
3 | "type": "object",
4 | "properties": {
5 | "title": {
6 | "type": "string"
7 | },
8 | "author": {
9 | "type": "string"
10 | },
11 | "date": {
12 | "title": "Posted on",
13 | "type": "string",
14 | "format": "date-time"
15 | },
16 | "content": {
17 | "type": "array",
18 | "items": {"$ref": "#/definitions/contentEntry"}
19 | },
20 | "comments": {
21 | "type": "array",
22 | "items": {"$ref": "#/definitions/comment"}
23 | }
24 | },
25 | "definitions": {
26 | "reference": {
27 | "links": [
28 | {"rel": "full", "href": "post.php{?postId*}"}
29 | ]
30 | },
31 | "submitPost": {
32 | "allOf": [{"$ref": "#"}],
33 | "properties": {
34 | "author": {},
35 | "title": {},
36 | "content": {}
37 | },
38 | "required": ["author", "title", "content"],
39 | "additionalProperties": false
40 | },
41 | "comment": {
42 | "title": "Comment",
43 | "type": "object",
44 | "properties": {
45 | "author": {"type": "string"},
46 | "message": {"type": "string"},
47 | "date": {"type": "string", "format": "date-time"}
48 | }
49 | },
50 | "submitComment": {
51 | "title": "Comment",
52 | "type": "object",
53 | "properties": {
54 | "author": {"type": "string", "maxLength": 25},
55 | "message": {"type": "string", "maxLength": 200}
56 | },
57 | "required": ["author", "message"],
58 | "additionalProperties": false
59 | },
60 | "contentEntry": {
61 | "oneOf": [
62 | {
63 | "title": "Paragraph",
64 | "type": "string"
65 | }
66 | ]
67 | }
68 | },
69 | "links": [
70 | {
71 | "rel": "comment",
72 | "href": "{?postId*}",
73 | "method": "POST",
74 | "schema": {"$ref": "#/definitions/submitComment"}
75 | }
76 | ]
77 | }
78 |
--------------------------------------------------------------------------------
/tests/tests/04 - Default values and data creation/07 - selecting from 'oneOf'.js:
--------------------------------------------------------------------------------
1 | tests.add("Uses original value if matches oneOf (first)", function () {
2 | var schema1 = Jsonary.createSchema({
3 | "oneOf": [
4 | {"type": "number"},
5 | {"type": "string"}
6 | ]
7 | });
8 | var createdData = schema1.createValue(5);
9 |
10 | this.assert(createdData == 5, "value matches");
11 | return true;
12 | });
13 |
14 | tests.add("Uses original value if matches oneOf (second)", function () {
15 | var schema1 = Jsonary.createSchema({
16 | "oneOf": [
17 | {"type": "number"},
18 | {"type": "string"}
19 | ]
20 | });
21 | var createdData = schema1.createValue("test");
22 |
23 | this.assert(createdData == "test", "value matches");
24 | return true;
25 | });
26 |
27 | tests.add("Uses original value if matches oneOf (first option, in property)", function () {
28 | var schema1 = Jsonary.createSchema({
29 | "type": "object",
30 | "oneOf": [
31 | {
32 | "properties": {
33 | "myProp": {"type": "number"}
34 | }
35 | },
36 | {
37 | "properties": {
38 | "myProp": {"type": "string"}
39 | }
40 | }
41 | ]
42 | });
43 | var createdData = schema1.createValue({myProp: 10});
44 |
45 | this.assert(createdData.myProp === 10, "value matches: " + JSON.stringify(createdData));
46 | return true;
47 | });
48 |
49 | tests.add("Uses original value if matches oneOf (second option, in property)", function () {
50 | var schema1 = Jsonary.createSchema({
51 | "type": "object",
52 | "oneOf": [
53 | {
54 | "properties": {
55 | "myProp": {"type": "number"}
56 | }
57 | },
58 | {
59 | "properties": {
60 | "myProp": {"type": "string"}
61 | }
62 | }
63 | ]
64 | });
65 | var createdData = schema1.createValue({myProp: "10"});
66 |
67 | this.assert(createdData.myProp === "10", "value matches: " + JSON.stringify(createdData));
68 | return true;
69 | });
70 |
--------------------------------------------------------------------------------
/tests/tests/04 - Default values and data creation/03 - Enum selection.js:
--------------------------------------------------------------------------------
1 | tests.add("enum()", function () {
2 | var schema1 = Jsonary.createSchema({
3 | "enum": ["A", "B", "C"]
4 | });
5 | var schemaList = Jsonary.createSchemaList(schema1);
6 |
7 | var enums = schemaList.enumValues();
8 | this.assert(enums.length == 3, "length == 3: " + JSON.stringify(enums));
9 |
10 | return true;
11 | });
12 |
13 | tests.add("enum() combination", function () {
14 | var schema1 = Jsonary.createSchema({
15 | "enum": ["A", "B", "C"]
16 | });
17 | var schema2 = Jsonary.createSchema({
18 | "enum": ["B", "C", "D"]
19 | });
20 | var schemaList = Jsonary.createSchemaList([schema1, schema2]);
21 |
22 | var enums = schemaList.enumValues();
23 | this.assert(enums.length == 2, "length == 2: " + JSON.stringify(enums));
24 |
25 | return true;
26 | });
27 |
28 | tests.add("select an enum", function () {
29 | var schema1 = Jsonary.createSchema({
30 | "enum": ["A", "B", "C"]
31 | });
32 | var schemaList = Jsonary.createSchemaList(schema1);
33 |
34 | var value = schemaList.createValue();
35 | this.assert(value === "A" || value === "B" || value === "C", "value in [A, B, C], was " + JSON.stringify(value));
36 |
37 | return true;
38 | });
39 |
40 | tests.add("default affects enum choice (A)", function () {
41 | var schema1 = Jsonary.createSchema({
42 | "enum": ["A", "B", "C"],
43 | "default": "A"
44 | });
45 | var schemaList = Jsonary.createSchemaList(schema1);
46 |
47 | var value = schemaList.createValue();
48 | this.assert(value === "A", "value === 'A'");
49 |
50 | return true;
51 | });
52 |
53 | tests.add("default affects enum choice (B)", function () {
54 | var schema1 = Jsonary.createSchema({
55 | "enum": ["A", "B", "C"],
56 | "default": "B"
57 | });
58 | var schemaList = Jsonary.createSchemaList(schema1);
59 |
60 | var value = schemaList.createValue();
61 | this.assert(value === "B", "value === 'B'");
62 |
63 | return true;
64 | });
65 |
--------------------------------------------------------------------------------
/renderers/contributed/tag-list.js:
--------------------------------------------------------------------------------
1 | Jsonary.render.register({
2 | renderHtml: function (data, context) {
3 | var enums = data.schemas().enumDataList();
4 | var result = '
';
5 | result += '
';
6 | data.items(function (index, item) {
7 | result += '';
8 | if (!data.readOnly()) {
9 | result += '';
10 | result += context.actionHtml('X ', 'remove', index);
11 | result += context.renderHtml(item.readOnlyCopy(), 'current' + index) + ' ';
12 | result += ' ';
13 | } else {
14 | result += context.renderHtml(item.readOnlyCopy(), 'current' + index) + '';
15 | }
16 | });
17 | result += '
';
18 | if (!data.readOnly()) {
19 | result += '
';
20 | result += context.actionHtml('add ', 'add');
21 | if (!context.uiState.addData) {
22 | var undefinedItem = data.item(data.length());
23 | var itemSchema = undefinedItem.schemas(true);
24 | context.uiState.addData = itemSchema.createData(undefinedItem, true);
25 | }
26 | result += context.withoutComponent('LIST_LINKS').renderHtml(context.uiState.addData, 'add');
27 | result += '
';
28 | }
29 | return result + '
';
30 | },
31 | action: {
32 | add: function (data, context) {
33 | var addData = context.uiState.addData;
34 | if (data.schemas().uniqueItems()) {
35 | for (var i = 0; i < data.length(); i++) {
36 | if (data.item(i).equals(addData)) {
37 | return false;
38 | }
39 | }
40 | }
41 | data.item(data.length()).setValue(addData.value());
42 | },
43 | remove: function (data, context, index) {
44 | data.item(index).remove();
45 | }
46 | },
47 | filter: {
48 | type: 'array',
49 | filter: function (data, schemas) {
50 | return schemas.unordered();
51 | }
52 | }
53 | });
--------------------------------------------------------------------------------
/tests/render-tests/01 - Basic tests/03 - Each renderer only once.js:
--------------------------------------------------------------------------------
1 | tests.add("Each renderer only once", function() {
2 | var thisTest = this;
3 | var value = "foo bar baz";
4 | var data = Jsonary.create(value, null, true);
5 |
6 | var calledCount = 0;
7 |
8 | var renderer = Jsonary.render.register({
9 | component: Jsonary.render.Components.LIST_LINKS,
10 | renderHtml: function (data, context) {
11 | calledCount++;
12 | if (calledCount == 1) {
13 | return context.withComponent('LIST_LINKS').renderHtml(data);
14 | } else {
15 | thisTest.fail("Called back recursively");
16 | return ":(";
17 | }
18 | return Jsonary.escapeHtml(data.value().toUpperCase());
19 | },
20 | filter: {
21 | type: 'string',
22 | filter: function (d) {
23 | return d === data;
24 | }
25 | }
26 | });
27 |
28 | Jsonary.asyncRenderHtml(data, null, function (error, html) {
29 | Jsonary.render.deregister(renderer);
30 | thisTest.assert(calledCount === 1, 'calledCount === 1');
31 | thisTest.pass();
32 | });
33 | });
34 |
35 | tests.add("Same renderer is used with children", function() {
36 | var thisTest = this;
37 | var value = {"foo": "bar"};
38 | var data = Jsonary.create(value, null, true);
39 |
40 | var calledCount = 0;
41 |
42 | var renderer = Jsonary.render.register({
43 | component: Jsonary.render.Components.LIST_LINKS,
44 | renderHtml: function (data, context) {
45 | calledCount++;
46 | if (calledCount <= 2) {
47 | return context.withComponent('LIST_LINKS').renderHtml(data);
48 | } else {
49 | thisTest.fail("Called back recursively");
50 | return ":(";
51 | }
52 | return Jsonary.escapeHtml(data.value().toUpperCase());
53 | },
54 | filter: {
55 | filter: function (d) {
56 | return d === data || d === data.property('foo');
57 | }
58 | }
59 | });
60 |
61 | Jsonary.asyncRenderHtml(data, null, function (error, html) {
62 | Jsonary.render.deregister(renderer);
63 | thisTest.assert(calledCount === 2, 'calledCount === 2');
64 | thisTest.pass();
65 | });
66 | });
--------------------------------------------------------------------------------
/assemble-package.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var fs = require('fs');
3 | var wrench = require('wrench');
4 |
5 | var licenseText = fs.readFileSync('LICENSE.txt', {enc: 'utf-8'});
6 |
7 | var bundle = require('./node-package/create-bundle.js');
8 | var masterBundle = bundle.base(__dirname)
9 | .code('/* ' + licenseText + ' */')
10 | .js([
11 | // Replacement for jsonary.js, assembled from individual files
12 | 'jsonary/_compatability.js',
13 | 'jsonary/_header.js',
14 | 'jsonary/uri.js',
15 | 'jsonary/uri-templates/uri-templates.js',
16 | 'jsonary/utils.js',
17 | 'jsonary/monitors.js',
18 | 'jsonary/request.js',
19 | 'jsonary/patch.js',
20 | 'jsonary/data.js',
21 | 'jsonary/schema.js',
22 | 'jsonary/schemamatch.js',
23 | 'jsonary/schemaset.js',
24 | 'jsonary/main.js',
25 | 'jsonary/_footer.js',
26 | 'jsonary/jsonary.render.js'
27 | ])
28 | .code('var Jsonary = this.Jsonary;')
29 |
30 | // http://json-schema.org/ meta-schemas
31 | .js('jsonary/_cache-json-schema-org.js')
32 |
33 | // Renderers
34 | .js('renderers/list-links.js')
35 | .css('renderers/common.css')
36 |
37 | .js('renderers/plain.jsonary.js')
38 | .css('renderers/plain.jsonary.css')
39 |
40 | .js('renderers/string-formats.js');
41 |
42 | console.log("Writing jsonary-core");
43 | masterBundle.compileJs('node-package/core/jsonary-core.js', true);
44 | masterBundle.compileCss('node-package/core/jsonary-core.css');
45 |
46 | console.log("Writing jsonary-super-bundle");
47 | var superBundle = require('./node-package').superBundle();
48 | superBundle.writeJs('node-package/super-bundle/jsonary-super-bundle.js', true, true);
49 | console.log("Jsonary bundles complete");
50 |
51 | console.log("Copying files");
52 | // copy license
53 | fs.writeFileSync('node-package/LICENSE.txt', licenseText, {enc: 'utf-8'});
54 | // copy plugins
55 | wrench.copyDirSyncRecursive('plugins', 'node-package/plugins', {
56 | forceDelete: true,
57 | excludeHiddenUnix: true
58 | });
59 | // copy renderers
60 | wrench.copyDirSyncRecursive('renderers/contributed', 'node-package/renderers', {
61 | forceDelete: true,
62 | excludeHiddenUnix: true
63 | });
--------------------------------------------------------------------------------
/api/jsonary-link.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Jsonary Link object",
3 | "type": "object",
4 | "properties": {
5 | "createSubmissionData": {
6 | "description": "Construct data appropriate for submitting along with the link (asynchronous).\n\nThe callback is called with the new data, with all appropriate schemas already applied.",
7 | "arguments": [
8 | {
9 | "title": "callback",
10 | "arguments": [
11 | {"title": "data", "$ref": "jsonary-data.json"}
12 | ]
13 | }
14 | ]
15 | },
16 | "definition": {"$ref": "jsonary-link-definition.json"},
17 | "encType": {
18 | "title": "Encoding type",
19 | "type": "string"
20 | },
21 | "follow": {
22 | "description": "Follow the link (with optional submission data)",
23 | "arguments": [
24 | {"title": "[submissionData]"},
25 | {
26 | "title": "handler",
27 | "description": "This is called first, before any other link handlers",
28 | "arguments": [
29 | {"title": "link", "$ref": "jsonary-link.json"},
30 | {"title": "submittedData", "$ref": "data.json"},
31 | {"title": "request", "$ref": "request.json"}
32 | ],
33 | "return": {
34 | "title": "handled",
35 | "description": "\"false\" means the link was handled, and no further handlers will be called. Default is \"true\"",
36 | "type": "boolean",
37 | "default": true
38 | }
39 | }
40 | ]
41 | },
42 | "href": {"title": "Hypertext Reference URL", "type": "string", "format": "uri"},
43 | "hrefBase": {"title": "\"href\" without the fragment part", "type": "string", "format": "uri"},
44 | "hrefFragment": {"title": "The fragment part of \"href\" (does not include the \"#\")", "type": "string"},
45 | "href": {"title": "Hypertext method", "description": "e.g. \"GET\", \"POST\", \"DELETE\"", "type": "string"},
46 | "rawLink": {"$ref": "raw-link.json"},
47 | "rel": {"title": "Link relation", "type": "string"},
48 | "submissionSchemas": {"$ref": "jsonary-schema-list.json"},
49 | "targetSchema": {
50 | "title": "Target schema (or undefined)",
51 | "oneOf": [
52 | {"$ref": "jsonary-schema.json"},
53 | {"title": "undefined", "type": "null"}
54 | ]
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/blog/index.php:
--------------------------------------------------------------------------------
1 | $postData->author,
9 | "title" => $postData->title,
10 | "content" => $postData->content,
11 | "date" => date(DATE_ISO8601)
12 | );
13 | $safeAuthor = rawurlencode(substr($newPostData['author'], 0, 10));
14 | $newPostFilename = POST_DIR.$newPostData['date'].$safeAuthor.".json";
15 | if (!file_put_contents($newPostFilename, json_encode($newPostData))) {
16 | die('{"error": "Error saving post"}');
17 | }
18 | }
19 | }
20 |
21 | $page = 0;
22 | if (isset($_GET['page'])) {
23 | $page = (int)$_GET['page'];
24 | if ($page < 0) {
25 | $page = 0;
26 | }
27 | }
28 |
29 | $jsonData = array(
30 | "title" => "The Jsonary Blog",
31 | "page" => $page
32 | );
33 | if ($page > 0) {
34 | $jsonData['prev'] = "?page=".($page - 1);
35 | }
36 |
37 | $posts = array();
38 | $filenames = scandir(POST_DIR);
39 | sort($filenames);
40 | $filenames = array_reverse($filenames);
41 |
42 | $remainingToSkip = POSTS_PER_PAGE*$page;
43 | $remainingToShow = POSTS_PER_PAGE;
44 | foreach ($filenames as $filename) {
45 | if ($filename[0] == "." || is_dir($filename)) {
46 | continue;
47 | }
48 | if ($remainingToSkip-- > 0) {
49 | continue;
50 | }
51 | if ($remainingToShow-- <= 0) {
52 | break;
53 | }
54 | $postData = json_decode(file_get_contents(POST_DIR.$filename));
55 | $posts[] = array(
56 | "postId" => substr($filename, 0, strlen($filename) - 5),
57 | "title" => $postData->title,
58 | "author" => $postData->author,
59 | "date" => $postData->date
60 | );
61 | }
62 | $jsonData['posts'] = $posts;
63 |
64 | if (count($posts) == POSTS_PER_PAGE) {
65 | $jsonData["next"] = "?page=".($page + 1);
66 | }
67 |
68 | if ($isAdministrator) {
69 | header("Content-Type: application/json; profile=schemas/index-administrator.json");
70 | } else {
71 | header("Content-Type: application/json; profile=schemas/index.json");
72 | }
73 | echo (json_encode($jsonData));
74 | ?>
75 |
--------------------------------------------------------------------------------
/tests/tests/.meta-tests/recursiveCompare.js:
--------------------------------------------------------------------------------
1 | var arrayData = [1, "b", false, null, {1:2}, [5]];
2 | var arrayChanged = [1, "b", true, null, {1:2}, [5]];
3 | var arrayMissing = [1, "b", false, null, [5]];
4 | var arraySubDataChanged = [1, "b", false, null, {1:3}, [5]];
5 |
6 | var objectData = {
7 | "null key": null,
8 | "boolean key": true,
9 | "number key": 4,
10 | "string key": "string",
11 | "array key": [0, 1, 2, 3, 4],
12 | "object key": {
13 | "key": "value"
14 | }
15 | };
16 | var objectDataAdded = {
17 | "ADDED": "ADDED",
18 | "null key": null,
19 | "boolean key": true,
20 | "number key": 4,
21 | "string key": "string",
22 | "array key": [0, 1, 2, 3, 4],
23 | "object key": {
24 | "key": "value"
25 | }
26 | };
27 | var objectDataMissing = {
28 | "null key": null,
29 | "boolean key": true,
30 | "number key": 4,
31 | "string key": "string",
32 | "array key": [0, 1, 2, 3, 4],
33 | };
34 |
35 | var objectSubDataChanged = {
36 | "null key": null,
37 | "boolean key": true,
38 | "number key": 4,
39 | "string key": "string",
40 | "array key": [0, 1, 2, false, 4],
41 | "object key": {
42 | "key": "value"
43 | }
44 | };
45 |
46 | tests.add("Array identical", function() {
47 | return recursiveCompare(arrayData, arrayData) == true;
48 | });
49 |
50 | tests.add("Array changed", function() {
51 | return recursiveCompare(arrayData, arrayChanged) == false;
52 | });
53 |
54 | tests.add("Array missing index", function() {
55 | return recursiveCompare(arrayData, arrayMissing) == false;
56 | });
57 |
58 | tests.add("Array sub-data changed", function() {
59 | return recursiveCompare(arrayData, arraySubDataChanged) == false;
60 | });
61 |
62 | tests.add("Object identical", function() {
63 | return recursiveCompare(objectData, objectData) == true;
64 | });
65 |
66 | tests.add("Object added key", function() {
67 | return recursiveCompare(objectData, objectDataAdded) == false;
68 | });
69 |
70 | tests.add("Object missing key", function() {
71 | return recursiveCompare(objectData, objectDataMissing) == false;
72 | });
73 |
74 | tests.add("Object sub-data changed", function() {
75 | return recursiveCompare(objectData, objectSubDataChanged) == false;
76 | });
77 |
--------------------------------------------------------------------------------
/plugins/jsonary.render.table.css:
--------------------------------------------------------------------------------
1 | .json-array-table {
2 | border-spacing: 0;
3 | border-collapse: collapse;
4 | }
5 |
6 | .json-array-table .json-array-table {
7 | width: 100%;
8 | margin: -4px;
9 | width: calc(100% + 8px);
10 | }
11 |
12 | .json-array-table > thead > tr > th {
13 | background-color: #EEE;
14 | border-bottom: 1px solid #666;
15 | padding: 0.3em;
16 | font-size: 0.9em;
17 | font-weight: bold;
18 | text-align: center;
19 | }
20 |
21 | .json-array-table > thead {
22 | border: 1px solid #BBB;
23 | }
24 |
25 | .json-array-table > thead > tr > th.json-array-table-pages {
26 | border-bottom: 1px solid #BBB;
27 | background-color: #DDD;
28 | }
29 |
30 | .json-array-table > thead > tr > th.json-array-table-pages .button {
31 | font-family: Courier New, monospace;
32 | }
33 |
34 | .json-array-table > tbody > tr > td {
35 | border: 1px solid #CCC;
36 | border-top-color: #DDD;
37 | border-bottom-color: #DDD;
38 | padding: 3px;
39 | font-size: inherit;
40 | text-align: left;
41 | }
42 |
43 | .json-array-table > tbody > tr > td.json-array-table-full {
44 | padding: 0.3em;
45 | background-color: #EEE;
46 | }
47 |
48 | .json-array-table > tbody > tr > td.json-array-table-add {
49 | text-align: center;
50 | background-color: #F8F8F8;
51 | border: 1px solid #DDD;
52 | }
53 |
54 | .json-array-table-full-buttons {
55 | text-align: center;
56 | }
57 |
58 | .json-array-table-full-title {
59 | text-align: center;
60 | margin: -0.3em;
61 | margin-bottom: 0.5em;
62 | background-color: #CCC;
63 | border-bottom: 1px solid #BBB;
64 | font-weight: bold;
65 | padding: 0.2em;
66 | }
67 |
68 | /* Sorting */
69 |
70 | .json-array-table-sort, .json-array-table-sort-asc, .json-array-table-sort-desc {
71 | padding-left: 15px;
72 | padding-right: 15px;
73 | margin-left: -5px;
74 | margin-right: -5px;
75 | }
76 |
77 | .json-array-table-sort-asc, .json-array-table-sort-desc {
78 | background-position: right center;
79 | background-repeat: no-repeat;
80 | }
81 |
82 | .json-array-table-sort-text {
83 | display: block;
84 | float: right;
85 | width: 0px;
86 | overflow: hidden;
87 | }
88 |
89 | .json-array-table-sort-asc {
90 | background-image: url('images/sort-asc.png');
91 | }
92 | .json-array-table-sort-desc {
93 | background-image: url('images/sort-desc.png');
94 | }
95 |
96 |
--------------------------------------------------------------------------------
/site/schemas/page.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Website page",
3 | "type": "object",
4 | "properties": {
5 | "title": {"type": "string"},
6 | "blocks": {
7 | "type": "array",
8 | "items": {"$ref": "#/definitions/block"}
9 | }
10 | },
11 | "required": ["title", "blocks"],
12 | "definitions": {
13 | "block": {
14 | "title": "Block",
15 | "type": "object",
16 | "properties": {
17 | "title": {"type": "string"},
18 | "content": {
19 | "type": "array",
20 | "items": {"$ref": "#/definitions/item"}
21 | }
22 | },
23 | "required": ["content"]
24 | },
25 | "item": {
26 | "title": "Item",
27 | "oneOf": [
28 | {"$ref": "#/definitions/markdown"},
29 | {"$ref": "#/definitions/section"},
30 | {"$ref": "#/definitions/gist"},
31 | {"$ref": "#/definitions/demo"},
32 | {"$ref": "#/definitions/keyValue"}
33 | ]
34 | },
35 | "markdown": {
36 | "title": "Markdown",
37 | "type": "string",
38 | "format": "markdown"
39 | },
40 | "section": {
41 | "title": "Section",
42 | "type": "object",
43 | "properties": {
44 | "title": {"type": "string"},
45 | "content": {
46 | "type": "array",
47 | "items": {"$ref": "#/definitions/item"}
48 | }
49 | },
50 | "required": ["title", "content"]
51 | },
52 | "gist": {
53 | "title": "Gist",
54 | "type": "object",
55 | "properties": {
56 | "gist": {"type": "string"}
57 | },
58 | "required": ["gist"]
59 | },
60 | "demo": {
61 | "title": "Demo",
62 | "type": "object",
63 | "properties": {
64 | "demoId": {"type": ["string", "number"]},
65 | "initialText": {"type": "string"},
66 | "run-button": {"type": "boolean", "default": "false"},
67 | "javascript": {
68 | "type": "string",
69 | "media": {
70 | "type": "application/javascript"
71 | }
72 | }
73 | },
74 | "required": ["demoId", "javascript"]
75 | },
76 | "keyValue": {
77 | "title": "Key-Value",
78 | "type": "object",
79 | "properties": {
80 | "keyValue": {
81 | "type": "array",
82 | "items": {
83 | "type": "object",
84 | "properties": {
85 | "key": {"type": "string"},
86 | "value": {"$ref": "#/definitions/item"}
87 | },
88 | "required": ["key", "value"]
89 | }
90 | }
91 | },
92 | "required": ["keyValue"]
93 | }
94 | }
95 | }
--------------------------------------------------------------------------------
/demos/view-schema.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
JSON Schema Viewer
5 |
6 |
7 |
8 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | JSON Schema:
33 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/plugins/jsonary.route.js:
--------------------------------------------------------------------------------
1 | (function (Jsonary) {
2 | if (typeof window === 'undefined') {
3 | return;
4 | }
5 |
6 | function Route(templateStr, handlerFunction) {
7 | this.template = Jsonary.UriTemplate(templateStr);
8 | this.templateString = templateStr;
9 | this.run = handlerFunction;
10 | }
11 | Route.prototype = {
12 | test: function (url) {
13 | var params = this.template.fromUri(url);
14 | if (params && this.template.fillFromObject(params) === url) {
15 | return params;
16 | }
17 | },
18 | url: function (params) {
19 | return this.template.fillFromObject(params);
20 | }
21 | };
22 |
23 | function getCurrent() {
24 | return Jsonary.location.base.replace(/^[^:]*:\/\/[^/]*/, '').replace(/[?#].*$/, '');
25 | }
26 |
27 | var routes = [];
28 | var extraData = {};
29 | function runRoutes() {
30 | var url = getCurrent(), query = Jsonary.location.query;
31 | var params;
32 | for (var i = 0; i < routes.length; i++) {
33 | var route = routes[i];
34 | if (params = route.test(url)) {
35 | var result = route.run(params, query, extraData);
36 | if (result !== false) {
37 | return;
38 | }
39 | }
40 | }
41 | }
42 | var pending = false;
43 | function runRoutesLater() {
44 | extraData = {};
45 | if (pending) return;
46 | pending = true;
47 | setTimeout(function () {
48 | pending = false;
49 | runRoutes();
50 | }, 25);
51 | }
52 |
53 | var locationMonitor = Jsonary.location.onChange(runRoutesLater, false);
54 |
55 | var api = Jsonary.route = function (template, handler) {
56 | var route = new Route(template, handler);
57 | routes.push(route);
58 | runRoutesLater();
59 | return route;
60 | };
61 | api.shortUrl = function (url) {
62 | var shortUrl = url.replace(/#$/, "");
63 | var urlBase = Jsonary.baseUri;
64 | if (url.substring(0, urlBase.length) == urlBase) {
65 | shortUrl = url.substring(urlBase.length) || "./";
66 | }
67 | return shortUrl;
68 | };
69 | api.set = function (path, query, extra) {
70 | query = query ||Jsonary.location.query.get();
71 | var newHref = (path || '').replace(/\?$/, '');
72 | if (Object.keys(query).length) {
73 | newHref += (newHref.indexOf('?') !== -1) ? '&' : '?';
74 | newHref += Jsonary.encodeData(query, 'application/x-www-form-urlencoded', Jsonary.location.queryVariant);
75 | }
76 | Jsonary.location.replace(newHref);
77 | extraData = extra || {};
78 | };
79 |
80 | })(Jsonary);
--------------------------------------------------------------------------------
/api/api-schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "API documentation",
3 | "allOf": [{"$ref": "#/objectDefinition"}],
4 | "objectDefinition": {
5 | "title": "API definition",
6 | "type": "object",
7 | "allOf": [
8 | {"$ref": "http://json-schema.org/hyper-schema"}
9 | ],
10 | "properties": {
11 | "title": {"title": "Title", "type": "string"},
12 | "description": {"title": "Description", "type": "string"},
13 | "properties": {
14 | "title": "Object properties",
15 | "additionalProperties": {"$ref": "#/objectDefinition"}
16 | },
17 | "patternProperties": {
18 | "additionalProperties": {"$ref": "#/objectDefinition"}
19 | },
20 | "additionalProperties": {"$ref": "#/objectDefinition"},
21 | "definitions": {
22 | "additionalProperties": {"$ref": "#/objectDefinition"}
23 | },
24 | "dependencies": {
25 | "additionalProperties": {
26 | "oneOf": [
27 | {"type": "string"},
28 | {"type": "array"},
29 | {"$ref": "#/objectDefinition"}
30 | ]
31 | }
32 | },
33 | "items": {
34 | "oneOf": [
35 | {"$ref": "#/objectDefinition"},
36 | {
37 | "type": "array",
38 | "items": {"$ref": "#/objectDefinition"}
39 | }
40 | ]
41 | },
42 | "additionalItems": {"$ref": "#/objectDefinition"},
43 | "allOf": {
44 | "type": "array",
45 | "items": {"$ref": "#/objectDefinition"}
46 | },
47 | "anyOf": {
48 | "type": "array",
49 | "items": {"$ref": "#/objectDefinition"}
50 | },
51 | "oneOf": {
52 | "type": "array",
53 | "items": {"$ref": "#/objectDefinition"}
54 | },
55 | "not": {"$ref": "#/objectDefinition"},
56 | "$ref": {"title": "Reference URI", "type": "string"}
57 | },
58 | "links": [
59 | {
60 | "href": "{+$ref}",
61 | "rel": "full",
62 | "targetSchema": {"$ref": "#"}
63 | }
64 | ],
65 | "dependencies": {
66 | "arguments": {"$ref": "#/functionDefinition"},
67 | "return": {"$ref": "#/functionDefinition"}
68 | }
69 | },
70 | "functionDefinition": {
71 | "title": "Function definition",
72 | "type": "object",
73 | "properties": {
74 | "title": {"title": "Title", "type": "string"},
75 | "description": {"title": "Description", "type": "string"},
76 | "arguments": {
77 | "title": "Function arguments",
78 | "type": "array",
79 | "items": {"$ref": "#/objectDefinition"}
80 | },
81 | "return": {"$ref": "http://json-schema.org/hyper-schema"}
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/plugins/jsonary.render.generate.js:
--------------------------------------------------------------------------------
1 | (function (Jsonary) {
2 |
3 | Jsonary.plugins.Generator = function (obj) {
4 | if (!obj.rendererForData) {
5 | throw "Generator must have method rendererForData";
6 | }
7 |
8 | obj.name = obj.name || "Generated (unknown)";
9 |
10 | function substituteContext(context) {
11 | var replacement = Object.create(context);
12 |
13 | replacement.subContext = function () {
14 | var result = context.subContext.apply(this, arguments);
15 | result.set('generated', context.get('generated'));
16 | return substituteContext(result);
17 | };
18 |
19 | return replacement;
20 | }
21 |
22 | obj.renderHtml = function (data, context) {
23 | var generatedRenderer = context.get('generated') || obj.rendererForData(data);
24 | context.set('generated', generatedRenderer);
25 | return generatedRenderer.renderHtml(data, substituteContext(context));
26 | };
27 | obj.enhance = function (element, data, context) {
28 | var generatedRenderer = context.get('generated');
29 | if (!generatedRenderer) {
30 | throw new Error("Generated renderer: cannot enhance without rendering first");
31 | }
32 | if (generatedRenderer.enhance) {
33 | return generatedRenderer.enhance(element, data, substituteContext(context));
34 | } else if (generatedRenderer.render) {
35 | return generatedRenderer.render(element, data, substituteContext(context));
36 | }
37 | };
38 | obj.action = function (context) {
39 | var generatedRenderer = context.get('generated');
40 | if (!generatedRenderer) {
41 | throw new Error("Generated renderer: cannot run action without rendering first");
42 | }
43 | var args = Array.prototype.slice.call(arguments, 0);
44 | args[0] = substituteContext(context);
45 | return generatedRenderer.action.apply(generatedRenderer, arguments);
46 | };
47 | obj.update = function (element, data, context) {
48 | var generatedRenderer = context.get('generated');
49 | if (!generatedRenderer) {
50 | throw new Error("Generated renderer: cannot update without rendering first");
51 | }
52 | generatedRenderer.defaultUpdate = this.defaultUpdate;
53 |
54 | var args = Array.prototype.slice.call(arguments, 0);
55 | args[2] = substituteContext(context);
56 | if (generatedRenderer.update) {
57 | return generatedRenderer.update.apply(generatedRenderer, args);
58 | } else {
59 | return this.defaultUpdate.apply(this, args);
60 | }
61 | };
62 |
63 | return obj;
64 | };
65 |
66 | })(Jsonary);
--------------------------------------------------------------------------------
/renderers/api.jsonary.js:
--------------------------------------------------------------------------------
1 | (function (Jsonary) {
2 | Jsonary.render.register({
3 | renderHtml: function (data, context) {
4 | var result = '
';
5 | if (!context.uiState.expanded) {
6 | result += context.actionHtml('
show ', 'expand');
7 | } else {
8 | result += context.actionHtml('
hide ', 'collapse');
9 | }
10 | result += '
';
11 | result += 'function ';
12 | var title = "";
13 | if (data.parent() != null && data.parent().basicType() == "object") {
14 | title = data.parentKey();
15 | }
16 | result += '' + title + ' ';
17 | result += '(';
18 | data.property("arguments").items(function (index, subData) {
19 | if (index > 0) {
20 | result += ', ';
21 | }
22 | var title = subData.propertyValue("title") || ("arg" + index);
23 | result += '' + title + ' ';
24 | });
25 | result += ')';
26 | result += '
';
27 |
28 | if (context.uiState.expanded) {
29 | result += '
';
30 | result += context.renderHtml(data.property("description"));
31 | result += '
';
32 |
33 | result += '
Arguments: ';
34 | result += '
';
35 | result += context.renderHtml(data.property("arguments"));
36 | result += '
';
37 |
38 | result += '
Return value: ';
39 | result += '
';
40 | result += context.renderHtml(data.property("return"));
41 | if (data.readOnly() && !data.property("return").defined()) {
42 | result += 'undefined';
43 | }
44 | result += '
';
45 | }
46 | return result + '
';
47 | },
48 | action: function (context, actionName, tabKey) {
49 | if (actionName == "expand") {
50 | context.uiState.expanded = true;
51 | } else {
52 | context.uiState.expanded = false;
53 | }
54 | return true;
55 | },
56 | filter: function (data, schemas) {
57 | return schemas.containsUrl('api-schema.json#/functionDefinition');
58 | },
59 | update: function (element, data, context, operation) {
60 | if (operation.hasPrefix(data.property("arguments")) && operation.depthFrom(data.property("arguments")) <= 2) {
61 | return true;
62 | }
63 | return this.defaultUpdate(element, data, context, operation);
64 | }
65 | });
66 | })(Jsonary);
67 |
--------------------------------------------------------------------------------
/api/jsonary-link-definition.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Jsonary link definition",
3 | "type": "object",
4 | "properties": {
5 | "addHandler": {
6 | "description": "Adds a link handler specific to links derived from this definition.\n\nLink handlers registered to particular link definitions like this are called before the global link handlers.",
7 | "arguments": [
8 | {
9 | "title": "handler",
10 | "arguments": [
11 | {"title": "link", "$ref": "jsonary-link.json"},
12 | {"title": "submittedData", "$ref": "data.json"},
13 | {"title": "request", "$ref": "request.json"}
14 | ],
15 | "return": {
16 | "title": "handled",
17 | "description": "\"false\" means the link was handled, and no further handlers will be called. Default is \"true\"",
18 | "type": "boolean",
19 | "default": true
20 | }
21 | }
22 | ],
23 | "return": {
24 | "$ref": "#"
25 | }
26 | },
27 | "addPreHandler": {
28 | "description": "Adds a link pre-handlers specific to links derived from this definition.\n\nLink pre-handlers registered here are called after the global link handlers.",
29 | "arguments": [
30 | {
31 | "title": "preHandler",
32 | "arguments": [
33 | {"title": "link", "$ref": "jsonary-link.json"},
34 | {"title": "submissionData", "$ref": "data.json"}
35 | ],
36 | "return": {
37 | "title": "handled",
38 | "description": "\"false\" cancels the link - no request is made, and no further (pre-)handlers will be called. Default is \"true\".",
39 | "type": "boolean",
40 | "default": true
41 | }
42 | }
43 | ],
44 | "return": {
45 | "$ref": "#"
46 | }
47 | },
48 | "canApplyTo": {
49 | "description": "Returns whether the specified data has the required properties defined to be able to fill out the URI template in \"href\"",
50 | "arguments": [
51 | {"title": "candidateData", "$ref": "jsonary-data.json"}
52 | ],
53 | "return": {
54 | "type": "boolean"
55 | }
56 | },
57 | "linkForData": {
58 | "description": "Returns the link, as applied to the supplied data",
59 | "arguments": [
60 | {"title": "subjectData", "$ref": "jsonary-data.json"}
61 | ],
62 | "return": {"$ref": "jsonary-link.json"}
63 | },
64 | "rel": {"title": "Link relation", "type": "string"},
65 | "submissionSchemas": {"$ref": "jsonary-schema-list.json"},
66 | "linkForData": {
67 | "description": "Whether the URI template for this link definition uses a given property key from the subject data",
68 | "arguments": [
69 | {"title": "key", "type": "string"}
70 | ],
71 | "return": {"type": "boolean"}
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/tests/tests/10 - Schema matching (validation)/02 - Test suite.js:
--------------------------------------------------------------------------------
1 | if (typeof require === 'undefined' || typeof __dirname === 'undefined') {
2 | tests.add("TODO: Full test suite not available in browser", function () {
3 | return true;
4 | })
5 | } else {
6 | var fs = require('fs');
7 | var path = require('path');
8 |
9 | var remoteDir = 'test-suite/remotes';
10 | var remoteUrl = 'http://localhost:1234';
11 | function addRemotes(dir) {
12 | var files = fs.readdirSync(path.join(__dirname, remoteDir, dir));
13 | for (var i = 0; i < files.length; i++) {
14 | var filename = files[i];
15 | var stats = fs.statSync(path.join(__dirname, remoteDir, dir, filename));
16 | if (stats.isDirectory()) {
17 | addRemotes(dir + '/' + filename);
18 | } else if (/\.json$/i.test(filename)) {
19 | var fileContents = JSON.parse(fs.readFileSync(path.join(__dirname, remoteDir, dir, filename)));
20 | Jsonary.addToCache(remoteUrl + dir + '/' + filename, fileContents);
21 | }
22 | }
23 | }
24 | addRemotes('');
25 |
26 | var testDirs = ['test-suite/tests/draft3', 'test-suite/tests/draft4'];
27 | var ignoreTests = ['test-suite/tests/draft3/ref.json']; // Relies on draft-03 meta-schema, which we don't have
28 | for (var i = 0; i < ignoreTests.length; i++) {
29 | ignoreTests[i] = path.join(__dirname, ignoreTests[i]);
30 | }
31 | for (var i = 0; i < testDirs.length; i++) {
32 | var testDir = path.join(__dirname, testDirs[i]);
33 | var files = fs.readdirSync(testDir);
34 | files.sort();
35 | for (var j = 0; j < files.length; j++) {
36 | var filename = path.join(testDir, files[j]);
37 | if (!filename.match(/\.json$/i)) {
38 | continue;
39 | }
40 | if (ignoreTests.indexOf(filename) !== -1) {
41 | continue;
42 | }
43 | addTests(filename);
44 | }
45 | }
46 |
47 | function addTests(filename) {
48 | var jsonTests = JSON.parse(fs.readFileSync(filename));
49 | for (var i = 0; i < jsonTests.length; i++) {
50 | (function (schemaTest) {
51 | tests.add(path.basename(filename) + ": " + schemaTest.description, function () {
52 | var schema = Jsonary.createSchema(schemaTest.schema);
53 |
54 | for (var i = 0; i < schemaTest.tests.length; i++) {
55 | var test = schemaTest.tests[i];
56 | var data = Jsonary.create(test.data).addSchema(schema);
57 | var failText = "Schema: " + JSON.stringify(schemaTest.schema, null, 4) + "\nData: " + JSON.stringify(test.data, null, 4);
58 | if (test.valid) {
59 | this.assert(data.valid() === true, "Should be valid: " + test.description + "\n" + failText);
60 | } else {
61 | this.assert(data.valid() === false, "Should be invalid: " + test.description + "\n" + failText);
62 | }
63 | }
64 | return true;
65 | });
66 | })(jsonTests[i]);
67 | }
68 | }
69 | }
--------------------------------------------------------------------------------
/tests/tests/01 - Basic tests/03 - Data comparison.js:
--------------------------------------------------------------------------------
1 | var arrayData = [1, "b", false, null, {1:2}, [5]];
2 | var arrayChanged = [1, "b", true, null, {1:2}, [5]];
3 | var arrayMissing = [1, "b", false, null, [5]];
4 | var arraySubDataChanged = [1, "b", false, null, {1:3}, [5]];
5 |
6 | var objectData = {
7 | "null key": null,
8 | "boolean key": true,
9 | "number key": 4,
10 | "string key": "string",
11 | "array key": [0, 1, 2, 3, 4],
12 | "object key": {
13 | "key": "value"
14 | }
15 | };
16 | var objectDataAdded = {
17 | "ADDED": "ADDED",
18 | "null key": null,
19 | "boolean key": true,
20 | "number key": 4,
21 | "string key": "string",
22 | "array key": [0, 1, 2, 3, 4],
23 | "object key": {
24 | "key": "value"
25 | }
26 | };
27 | var objectDataMissing = {
28 | "null key": null,
29 | "boolean key": true,
30 | "number key": 4,
31 | "string key": "string",
32 | "array key": [0, 1, 2, 3, 4],
33 | };
34 |
35 | var objectSubDataChanged = {
36 | "null key": null,
37 | "boolean key": true,
38 | "number key": 4,
39 | "string key": "string",
40 | "array key": [0, 1, 2, false, 4],
41 | "object key": {
42 | "key": "value"
43 | }
44 | };
45 |
46 | tests.add("Array identical", function() {
47 | var data1 = Jsonary.create(arrayData);
48 | var data2 = Jsonary.create(arrayData);
49 | return data1.equals(data2) == true;
50 | });
51 |
52 | tests.add("Array changed", function() {
53 | var data1 = Jsonary.create(arrayData);
54 | var data2 = Jsonary.create(arrayChanged);
55 | return data1.equals(data2) == false;
56 | });
57 |
58 | tests.add("Array missing index", function() {
59 | var data1 = Jsonary.create(arrayData);
60 | var data2 = Jsonary.create(arrayMissing);
61 | return data1.equals(data2) == false;
62 | });
63 |
64 | tests.add("Array sub-data changed", function() {
65 | var data1 = Jsonary.create(arrayData);
66 | var data2 = Jsonary.create(arraySubDataChanged);
67 | return data1.equals(data2) == false;
68 | });
69 |
70 | tests.add("Object identical", function() {
71 | var data1 = Jsonary.create(objectData);
72 | var data2 = Jsonary.create(objectData);
73 | return data1.equals(data2) == true;
74 | });
75 |
76 | tests.add("Object added key", function() {
77 | var data1 = Jsonary.create(objectData);
78 | var data2 = Jsonary.create(objectDataAdded);
79 | return data1.equals(data2) == false;
80 | });
81 |
82 | tests.add("Object missing key", function() {
83 | var data1 = Jsonary.create(objectData);
84 | var data2 = Jsonary.create(objectDataMissing);
85 | return data1.equals(data2) == false;
86 | });
87 |
88 | tests.add("Object sub-data changed", function() {
89 | var data1 = Jsonary.create(objectData);
90 | var data2 = Jsonary.create(objectSubDataChanged);
91 | return data1.equals(data2) == false;
92 | });
93 |
--------------------------------------------------------------------------------
/site/renderers/site-backup.js:
--------------------------------------------------------------------------------
1 | function jstpl(template) {
2 | var constants = [];
3 | var variables = [];
4 |
5 | var substitutionFunctionName = "subFunc" + Math.floor(Math.random()*1000000000);
6 | var resultVariableName = "result" + Math.floor(Math.random()*1000000000);
7 | var jscode = '(function (' + substitutionFunctionName + ') {\n';
8 | jscode += ' var ' + resultVariableName + ' = "";\n';
9 |
10 | var parts = template.split("{{");
11 | var initialString = parts.shift();
12 | while (parts.length > 0) {
13 | var part = parts.shift();
14 | var endIndex = part.indexOf("}}");
15 | var variable = part.substring(0, endIndex);
16 | var constant = part.substring(endIndex + 2);
17 | jscode += ' ' + resultVariableName + ' += ' + substitutionFunctionName + '(' + JSON.stringify(variable) + ');\n';
18 | jscode += ' ' + resultVariableName + ' += ' + JSON.stringify(constant) + ';\n';
19 | }
20 | jscode += ' return ' + resultVariableName + ';\n';
21 | jscode += '})';
22 |
23 | return eval(jscode);
24 | }
25 |
26 | var templateMap = {};
27 | function loadTemplates() {
28 | var scripts = document.getElementsByTagName("script");
29 | var lastScript = scripts[scripts.length - 1];
30 | var url = lastScript.getAttribute("src");
31 |
32 | var xhr = new XMLHttpRequest();
33 | xhr.open("GET", url, false);
34 | xhr.send();
35 | processTemplates(xhr.responseText);
36 | }
37 | function getTemplate(key) {
38 | var template = templateMap[key];
39 | if (template == null) {
40 | throw new Exception("Could not locate template: " + key);
41 | }
42 | var templateFunction = jstpl(template);
43 | return function (data, context) {
44 | return templateFunction(function (variableName) {
45 | return context.renderHtml(data.subPath(variableName));
46 | });
47 | };
48 | }
49 | function processTemplates(code) {
50 | var result = {};
51 | var parts = code.split(/\/\*\s*[Tt]emplate:/);
52 | parts.shift();
53 | for (var i = 0; i < parts.length; i++) {
54 | var part = parts[i];
55 | part = part.substring(0, part.indexOf("*/"));
56 | var endOfLine = part.indexOf("\n");
57 | var key = part.substring(0, endOfLine).trim();
58 | var template = part.substring(endOfLine + 1);
59 | templateMap[key] = template;
60 | }
61 | return result;
62 | }
63 | loadTemplates();
64 |
65 | /* Template: schemas/page.json
66 |
{{/title}}
67 | {{/blocks}}
68 | */
69 | Jsonary.render.register({
70 | renderHtml: getTemplate("schemas/page.json"),
71 | filter: function (data, schemas) {
72 | return schemas.containsUrl("schemas/page.json");
73 | }
74 | });
75 |
76 | /* Template: schemas/page.json#/definitions/block
77 |
78 |
{{/title}}
79 | {{/content}}
80 |
81 | */
82 | Jsonary.render.register({
83 | renderHtml: getTemplate("schemas/page.json#/definitions/block"),
84 | filter: function (data, schemas) {
85 | return schemas.containsUrl("schemas/page.json#/definitions/block");
86 | }
87 | });
--------------------------------------------------------------------------------
/renderers/contributed/full-instances.js:
--------------------------------------------------------------------------------
1 | (function (Jsonary) {
2 | Jsonary.render.register({
3 | component: [Jsonary.render.Components.RENDERER, Jsonary.render.Components.LIST_LINKS],
4 | renderHtml: function (data, context) {
5 | var result = '
';
6 | var options = {};
7 | var optionOrder = [];
8 | var optionValues = {};
9 | var renderData = {};
10 |
11 | var links = data.links('instances');
12 | var fullLink = data.getLink('full');
13 | var previewPath = "";
14 |
15 | var fullPreviewLink = data.getLink('full-preview');
16 | if (fullPreviewLink && Jsonary.Uri.resolve(fullPreviewLink.href, '#') == Jsonary.Uri.resolve(fullLink.href, '#')) {
17 | var fullFragment = fullLink.href.split('#').slice(1).join('#');
18 | var previewFragment = fullPreviewLink.href.split('#').slice(1).join('#');
19 | var previewPath = decodeURIComponent(previewFragment.substring(fullFragment.length));
20 | }
21 |
22 | var rerender = false;
23 | for (var i = 0; i < links.length; i++) {
24 | var link = links[i];
25 | link.follow(null, false).getData(function (data) {
26 | data.items(function (index, subData) {
27 | var url = subData.getLink('self') ? subData.getLink('self').href : subData.referenceUrl();
28 | if (!options[url]) {
29 | options[url] = subData;
30 |
31 | var value = fullLink.valueForUrl(url);
32 | if (value !== undefined) {
33 | optionOrder.push(url);
34 | optionValues[url] = value;
35 | renderData[url] = subData.subPath(previewPath);
36 | }
37 | }
38 | });
39 | if (rerender) {
40 | context.rerender();
41 | rerender = false;
42 | }
43 | });
44 | }
45 | rerender = true;
46 | var optionsHtml = "";
47 | var foundSelected = false;
48 | for (var i = 0; i < optionOrder.length; i++) {
49 | var url = optionOrder[i];
50 | var selected = '';
51 | if (data.equals(Jsonary.create(optionValues[url]))) {
52 | foundSelected = true;
53 | selected = ' selected';
54 | }
55 | optionsHtml += '' + context.renderHtml(renderData[url]) + ' ';
56 | }
57 | if (!foundSelected) {
58 | optionsHtml = '' + context.renderHtml(fullPreviewLink.follow(null, false), 'current') + ' ' + optionsHtml;
59 | }
60 | result += optionsHtml;
61 | return result + ' ';
62 | },
63 | action: function (context, actionName, arg1) {
64 | var data = context.data;
65 | if (actionName == 'select-url') {
66 | var url = arg1;
67 | var fullLink = data.getLink('full');
68 | var value = fullLink.valueForUrl(url);
69 | data.setValue(value);
70 | }
71 | },
72 | filter: function (data, schemas) {
73 | return !data.readOnly() && data.getLink('instances') && data.getLink('full');
74 | }
75 | });
76 | })(Jsonary);
--------------------------------------------------------------------------------
/tests/page.test.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: Verdana, sans-serif;
3 | font-size: 12px;
4 | }
5 |
6 | table {
7 | font-size: inherit;
8 | }
9 | #main {
10 | width: 100%;
11 | background-color: #DDD;
12 | background-color: rgba(255, 255, 255, 0.6);
13 | }
14 |
15 | td {
16 | padding: 5px;
17 | vertical-align: top;
18 | }
19 |
20 | #test-set-cell {
21 | width: 300px;
22 | }
23 |
24 | #test-set-list {
25 | width: 300px;
26 | float: left;
27 | padding: 10px;
28 | margin: 20px;
29 | margin-top: 40px;
30 | margin-right: 40px;
31 | border: 2px solid #666;
32 | border-radius: 10px;
33 | background-color: #ACE;
34 | }
35 |
36 | #test-set-list a {
37 | text-decoration: none;
38 | }
39 |
40 | #test-set-list a:hover {
41 | color: #024;
42 | }
43 |
44 | .test-set, .test-set-group-name {
45 | display: block;
46 | margin: 0.5em;
47 | color: #234;
48 | }
49 |
50 | .test-set.selected, .test-set-group-name.selected {
51 | color: #000;
52 | text-decoration: underline;
53 | }
54 |
55 |
56 | .test-set-group {
57 | padding-left: 2em;
58 | }
59 | .test-set-group-name {
60 | color: #444;
61 | font-weight: bold;
62 | }
63 |
64 | .test-set-group-all {
65 | float: right;
66 | color: #444;
67 | font-weight: bold;
68 | }
69 |
70 | h1 {
71 | border-bottom: 1px solid #444;
72 | margin-right: 10%;
73 | font-size: 1.5em;
74 | letter-spacing: 0.2ex;
75 | font-family: Trebuchet MS;
76 | background-color: inherit;
77 | }
78 |
79 | /**********************/
80 |
81 | .test-results-table {
82 | margin: 10px;
83 | padding-left: 40px;
84 | font-size: 0.9em;
85 | }
86 |
87 | .test-results-table td {
88 | text-align: left;
89 | }
90 |
91 | td.test-name {
92 | text-align: right;
93 | padding: 5px;
94 | padding-left: 20px;
95 | padding-right: 20px;
96 | width: 150px;
97 | color: 0;
98 |
99 | background-color: #EEE;
100 | border-right: 2px solid #666;
101 | border-radius: 10px;
102 |
103 | letter-spacing: 0.1ex;
104 | font-size: ;
105 | }
106 |
107 | .test-result {
108 | border: 0px;
109 |
110 | text-align: center;
111 | width: 70px;
112 | padding: 5px;
113 | padding-left: 15px;
114 | }
115 |
116 | .test-detail {
117 | color: #888;
118 | font-style: italic;
119 | }
120 |
121 | .test-running .test-result {
122 | font-style: italic;
123 | color: #888;
124 | }
125 |
126 | .test-passed .test-result {
127 | color: #280;
128 | font-weight: bold;
129 | }
130 |
131 | .test-failed .test-result {
132 | color: #820;
133 | font-weight: bold;
134 | }
135 |
136 | .test-failed .test-detail {
137 | white-space: pre;
138 | color: #000;
139 | font-style: normal;
140 | padding: 5px;
141 | padding-left: 10px;
142 | padding-right: 10px;
143 |
144 | background-color: #F76;
145 | border: 1px solid #F00;
146 | border-radius: 10px;
147 | }
148 |
149 |
--------------------------------------------------------------------------------
/plugins/jsonary.undo.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | if (typeof window == 'undefined') {
3 | return;
4 | }
5 |
6 | var modKeyDown = false;
7 | var shiftKeyDown = false;
8 | var otherKeys = {};
9 |
10 | // Register key down/up listeners to catch undo/redo key combos
11 | document.onkeydown = function (e) {
12 | var keyCode = (window.event != null) ? window.event.keyCode : e.keyCode;
13 | if (keyCode == 17 || keyCode == 91) {
14 | modKeyDown = true;
15 | } else if (keyCode == 16) {
16 | shiftKeyDown = true;
17 | } else {
18 | otherKeys[keyCode] = true;
19 | }
20 | var otherKeyCount = 0;
21 | for (var otherKeyCode in otherKeys) {
22 | if (otherKeyCode != 90 && otherKeyCode != 89) {
23 | otherKeyCount++;
24 | }
25 | }
26 | if (otherKeyCount == 0) {
27 | if (keyCode == 90) { // Z
28 | if (modKeyDown) {
29 | if (shiftKeyDown) {
30 | Jsonary.redo();
31 | } else {
32 | Jsonary.undo();
33 | }
34 | }
35 | } else if (keyCode == 89) { // Y
36 | if (modKeyDown && !shiftKeyDown) {
37 | Jsonary.redo();
38 | }
39 | }
40 | }
41 | };
42 | document.onkeyup = function (e) {
43 | var keyCode = (window.event != null) ? window.event.keyCode : e.keyCode;
44 | if (keyCode == 17 || keyCode == 91) {
45 | modKeyDown = false;
46 | } else if (keyCode == 16) {
47 | shiftKeyDown = false;
48 | } else {
49 | delete otherKeys[keyCode];
50 | }
51 | };
52 |
53 | var undoList = [];
54 | var redoList = [];
55 | var ignoreChanges = 0;
56 |
57 | Jsonary.registerChangeListener(function (patch, document) {
58 | if (ignoreChanges > 0) {
59 | ignoreChanges--;
60 | return;
61 | }
62 | if (document.readOnly) {
63 | return;
64 | }
65 | var rendered = false;
66 | for (var i = 0; !rendered && i < patch.operations.length; i++) {
67 | var operation = patch.operations[i];
68 | var affectedData = document.affectedData(operation);
69 | for (var j = 0; j < affectedData.length; j++) {
70 | var data = affectedData[j];
71 | if (Jsonary.render.rendered(data)) {
72 | rendered = true;
73 | break;
74 | }
75 | }
76 | }
77 | if (!rendered) {
78 | return;
79 | }
80 | undoList.push({patch: patch, document: document});
81 | while (undoList.length > Jsonary.undo.historyLength) {
82 | undoList.shift();
83 | }
84 | if (redoList.length > 0) {
85 | redoList = [];
86 | }
87 | });
88 |
89 | Jsonary.extend({
90 | undo: function () {
91 | var lastChange = undoList.pop();
92 | if (lastChange != undefined) {
93 | ignoreChanges++;
94 | redoList.push(lastChange);
95 | lastChange.document.patch(lastChange.patch.inverse());
96 | }
97 | },
98 | redo: function () {
99 | var nextChange = redoList.pop();
100 | if (nextChange != undefined) {
101 | ignoreChanges++;
102 | undoList.push(nextChange);
103 | nextChange.document.patch(nextChange.patch);
104 | }
105 | }
106 | });
107 | Jsonary.undo.historyLength = 10;
108 | })();
109 |
--------------------------------------------------------------------------------
/tests/tests/01 - Basic tests/05 - Requests.js:
--------------------------------------------------------------------------------
1 | tests.add("Inserting into cache", function() {
2 | var thisTest = this;
3 | var url = "http://example.com/test.json";
4 | var testData = {"key":"value"};
5 | Jsonary.addToCache(url, testData);
6 | var request = Jsonary.getData(url, function(data, req) {
7 | thisTest.assert(recursiveCompare(testData, data.value()), "Returned data does not match: " + JSON.stringify(testData) + " vs " + JSON.stringify(data.value()));
8 | thisTest.pass();
9 | });
10 | setTimeout(function() {
11 | thisTest.fail("Timeout");
12 | }, 50);
13 | });
14 |
15 | tests.add("encodeData()", function() {
16 | var data = [
17 | "plain string",
18 | 1,
19 | {"key": "value"},
20 | {"array":[null, true, 3, "4"]},
21 | {"object":{"key": "value"}},
22 | {"array":[[0,1],[2,3]]},
23 | {}
24 | ];
25 | var expectedForm = [
26 | "plain+string",
27 | "1",
28 | "key=value",
29 | "array%5B%5D=null&array%5B%5D=true&array%5B%5D=3&array%5B%5D=4",
30 | "object%5Bkey%5D=value",
31 | "array%5B0%5D%5B%5D=0&array%5B0%5D%5B%5D=1&array%5B1%5D%5B%5D=2&array%5B1%5D%5B%5D=3",
32 | ""
33 | ];
34 | var expectedJson = [
35 | "\"plain string\"",
36 | "1",
37 | "{\"key\":\"value\"}",
38 | '{"array":[null,true,3,"4"]}',
39 | '{"object":{"key":"value"}}',
40 | '{"array":[[0,1],[2,3]]}',
41 | '{}'
42 | ];
43 | for (var i = 0; i < data.length; i++) {
44 | var formResult = Jsonary.encodeData(data[i]);
45 | this.assert(formResult == expectedForm[i], "Expected form:\n" + expectedForm[i] + ", got:\n" + formResult);
46 | if (expectedJson[i] != undefined) {
47 | var jsonResult = Jsonary.encodeData(data[i], "application/json");
48 | this.assert(jsonResult == expectedJson[i], "Expected JSON:\n" + expectedJson[i] + ", got:\n" + formResult);
49 | }
50 | }
51 | return true;
52 | });
53 |
54 | tests.add("decodeData()", function() {
55 | var data = [
56 | {"key": "value"},
57 | {"array":[null, true, 3, "four"]},
58 | {"object":{"key": "value"}},
59 | {"array":[[0,1],[2,3]]},
60 | {}
61 | ];
62 | var expectedForm = [
63 | "key=value",
64 | "array%5B%5D=null&array%5B%5D=true&array%5B%5D=3&array%5B%5D=4",
65 | "object%5Bkey%5D=value",
66 | "array%5B0%5D%5B%5D=0&array%5B0%5D%5B%5D=1&array%5B1%5D%5B%5D=2&array%5B1%5D%5B%5D=3",
67 | ""
68 | ];
69 | var expectedJson = [
70 | "\"plain string\"",
71 | "1",
72 | "{\"key\":\"value\"}",
73 | '{"array":[null,true,3,"4"]}',
74 | '{"object":{"key":"value"}}',
75 | '{"array":[[0,1],[2,3]]}',
76 | ''
77 | ];
78 | for (var i = 0; i < data.length; i++) {
79 | var formResult = Jsonary.encodeData(data[i]);
80 | this.assert(recursiveCompare(Jsonary.decodeData(formResult), data[i]), "data did not decode to itself: " + formResult);
81 | if (expectedJson[i] != undefined) {
82 | var jsonResult = Jsonary.encodeData(data[i], "application/json");
83 | this.assert(recursiveCompare(Jsonary.decodeData(jsonResult, 'application/json'), data[i]), "data did not decode to itself: " + jsonResult);
84 | }
85 | }
86 | return true;
87 | });
88 |
--------------------------------------------------------------------------------
/demos/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
JSON browser
5 |
52 |
53 |
54 |
55 |
56 |
57 |
Go
58 |
59 |
60 |
61 |
112 |
113 |
--------------------------------------------------------------------------------
/tests/tests/08 - URI Templating (RFC 6570)/02 - Use in links.js:
--------------------------------------------------------------------------------
1 | var exampleData = Jsonary.create({
2 | "var": "value",
3 | "hello": "Hello World!",
4 | "path": "/foo/bar",
5 | "empty": "",
6 | "x": "1024",
7 | "y": "768",
8 | "list": ["red", "green", "blue"],
9 | "keys": {"semi": ";", "dot": ".", "comma": ","}
10 | });
11 | var examples = {
12 | "{+var}": "value",
13 | "{+hello}": "Hello%20World!",
14 | "{+path}/here": "/foo/bar/here",
15 | "here?ref={+path}": "here?ref=/foo/bar",
16 | "X{#var}": "X#value",
17 | "X{#hello}": "X#Hello%20World!",
18 | "map?{x,y}": "map?1024,768",
19 | "{x,hello,y}": "1024,Hello%20World%21,768",
20 | "{#x,hello,y}": "#1024,Hello%20World!,768",
21 | "{+path,x}/here": "/foo/bar,1024/here",
22 | "X{.var}": "X.value",
23 | "X{.x,y}": "X.1024.768",
24 | "{/var}": "/value",
25 | "{/var,x}/here": "/value/1024/here",
26 | "{;x,y}": ";x=1024;y=768",
27 | "{;x,y,empty}": ";x=1024;y=768;empty",
28 | "{?x,y}": "?x=1024&y=768",
29 | "{?x,y,empty}": "?x=1024&y=768&empty=",
30 | "?fixed=yes{&x}": "?fixed=yes&x=1024",
31 | "{&x,y,empty}": "&x=1024&y=768&empty=",
32 | "{var:3}": "val",
33 | "{var:30}": "value",
34 | "{list}": "red,green,blue",
35 | "{list*}": "red,green,blue",
36 | "{keys}": "semi,%3B,dot,.,comma,%2C",
37 | "{keys*}": "semi=%3B,dot=.,comma=%2C",
38 | "{+path:6}/here": "/foo/b/here",
39 | "{+list}": "red,green,blue",
40 | "{+list*}": "red,green,blue",
41 | "{+keys}": "semi,;,dot,.,comma,,",
42 | "{+keys*}": "semi=;,dot=.,comma=,",
43 | "{#path:6}/here": "#/foo/b/here",
44 | "{#list}": "#red,green,blue",
45 | "{#list*}": "#red,green,blue",
46 | "{#keys}": "#semi,;,dot,.,comma,,",
47 | "{#keys*}": "#semi=;,dot=.,comma=,",
48 | "X{.var:3}": "X.val",
49 | "X{.list}": "X.red,green,blue",
50 | "X{.list*}": "X.red.green.blue",
51 | "X{.keys}": "X.semi,%3B,dot,.,comma,%2C",
52 | "X{.keys*}": "X.semi=%3B.dot=..comma=%2C",
53 | "{/var:1,var}": "/v/value",
54 | "{/list}": "/red,green,blue",
55 | "{/list*}": "/red/green/blue",
56 | "{/list*,path:4}": "/red/green/blue/%2Ffoo",
57 | "{/keys}": "/semi,%3B,dot,.,comma,%2C",
58 | "{/keys*}": "/semi=%3B/dot=./comma=%2C",
59 | "{;hello:5}": ";hello=Hello",
60 | "{;list}": ";list=red,green,blue",
61 | "{;list*}": ";list=red;list=green;list=blue",
62 | "{;keys}": ";keys=semi,%3B,dot,.,comma,%2C",
63 | "{;keys*}": ";semi=%3B;dot=.;comma=%2C",
64 | "{?var:3}": "?var=val",
65 | "{?list}": "?list=red,green,blue",
66 | "{?list*}": "?list=red&list=green&list=blue",
67 | "{?keys}": "?keys=semi,%3B,dot,.,comma,%2C",
68 | "{?keys*}": "?semi=%3B&dot=.&comma=%2C",
69 | "{&var:3}": "&var=val",
70 | "{&list}": "&list=red,green,blue",
71 | "{&list*}": "&list=red&list=green&list=blue",
72 | "{&keys}": "&keys=semi,%3B,dot,.,comma,%2C",
73 | "{&keys*}": "&semi=%3B&dot=.&comma=%2C"
74 | };
75 | for (var sub in examples) {
76 | (function (sub, expected) {
77 | tests.add(sub, function () {
78 | var link = Jsonary.create({rel:"test", "href": sub}).asLink(exampleData);
79 | this.assert(link.href == expected, JSON.stringify(link.href) + " != " + JSON.stringify(expected));
80 | return true;
81 | });
82 | })(sub, examples[sub]);
83 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #################
2 | ## Eclipse
3 | #################
4 |
5 | *.pydevproject
6 | .project
7 | .metadata
8 | bin/
9 | tmp/
10 | *.tmp
11 | *.bak
12 | *.swp
13 | *~.nib
14 | local.properties
15 | .classpath
16 | .settings/
17 | .loadpath
18 |
19 | # External tool builders
20 | .externalToolBuilders/
21 |
22 | # Locally stored "Eclipse launch configurations"
23 | *.launch
24 |
25 | # CDT-specific
26 | .cproject
27 |
28 | # PDT-specific
29 | .buildpath
30 |
31 |
32 | #################
33 | ## Visual Studio
34 | #################
35 |
36 | ## Ignore Visual Studio temporary files, build results, and
37 | ## files generated by popular Visual Studio add-ons.
38 |
39 | # User-specific files
40 | *.suo
41 | *.user
42 | *.sln.docstates
43 |
44 | # Build results
45 | [Dd]ebug/
46 | [Rr]elease/
47 | *_i.c
48 | *_p.c
49 | *.ilk
50 | *.meta
51 | *.obj
52 | *.pch
53 | *.pdb
54 | *.pgc
55 | *.pgd
56 | *.rsp
57 | *.sbr
58 | *.tlb
59 | *.tli
60 | *.tlh
61 | *.tmp
62 | *.vspscc
63 | .builds
64 | *.dotCover
65 |
66 | ## TODO: If you have NuGet Package Restore enabled, uncomment this
67 | #packages/
68 |
69 | # Visual C++ cache files
70 | ipch/
71 | *.aps
72 | *.ncb
73 | *.opensdf
74 | *.sdf
75 |
76 | # Visual Studio profiler
77 | *.psess
78 | *.vsp
79 |
80 | # ReSharper is a .NET coding add-in
81 | _ReSharper*
82 |
83 | # Installshield output folder
84 | [Ee]xpress
85 |
86 | # DocProject is a documentation generator add-in
87 | DocProject/buildhelp/
88 | DocProject/Help/*.HxT
89 | DocProject/Help/*.HxC
90 | DocProject/Help/*.hhc
91 | DocProject/Help/*.hhk
92 | DocProject/Help/*.hhp
93 | DocProject/Help/Html2
94 | DocProject/Help/html
95 |
96 | # Click-Once directory
97 | publish
98 |
99 | # Others
100 | [Bb]in
101 | [Oo]bj
102 | sql
103 | TestResults
104 | *.Cache
105 | ClientBin
106 | stylecop.*
107 | ~$*
108 | *.dbmdl
109 | Generated_Code #added for RIA/Silverlight projects
110 |
111 | # Backup & report files from converting an old project file to a newer
112 | # Visual Studio version. Backup files are not needed, because we have git ;-)
113 | _UpgradeReport_Files/
114 | Backup*/
115 | UpgradeLog*.XML
116 |
117 |
118 |
119 | ############
120 | ## Windows
121 | ############
122 |
123 | # Windows image file caches
124 | Thumbs.db
125 |
126 | # Folder config file
127 | Desktop.ini
128 |
129 |
130 | #############
131 | ## Python
132 | #############
133 |
134 | *.py[co]
135 |
136 | # Packages
137 | *.egg
138 | *.egg-info
139 | dist
140 | build
141 | eggs
142 | parts
143 | bin
144 | var
145 | sdist
146 | develop-eggs
147 | .installed.cfg
148 |
149 | # Installer logs
150 | pip-log.txt
151 |
152 | # Unit test / coverage reports
153 | .coverage
154 | .tox
155 |
156 | #Translations
157 | *.mo
158 |
159 | #Mr Developer
160 | .mr.developer.cfg
161 |
162 | # Mac crap
163 | .DS_Store
164 |
165 | php/common.php
166 |
167 | test/bundle.css
168 | test/bundle.min.css
169 | test/bundle.js
170 | test/bundle.min.js
171 |
172 | node_modules
173 |
174 | bower_components
175 |
176 | demos/json/data.json
177 |
178 | node-package/public-suffix-list.txt
179 |
--------------------------------------------------------------------------------
/demos/index-input.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
JSON browser
5 |
6 |
7 |
8 |
9 |
27 |
28 |
29 |
30 |
31 |
32 | JSON data:
33 |
39 |
40 |
41 | JSON Schema:
42 |
60 |
61 |
62 |
63 |
64 |
65 | Editable
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/tests/tests/08 - URI Templating (RFC 6570)/05 - Reverse-engineering (more).js:
--------------------------------------------------------------------------------
1 | // Same examples from the spec as in "Basic tests"
2 | var examples = {
3 | "{+var}": "value",
4 | "{+hello}": "Hello%20World!",
5 | "{+path}/here": "/foo/bar/here",
6 | "here?ref={+path}": "here?ref=/foo/bar",
7 | "X{#var}": "X#value",
8 | "X{#hello}": "X#Hello%20World!",
9 | "map?{x,y}": "map?1024,768",
10 | "{x,hello,y}": "1024,Hello%20World%21,768",
11 | "{#x,hello,y}": "#1024,Hello%20World!,768",
12 | "{+path,x}/here": "/foo/bar,1024/here",
13 | "X{.var}": "X.value",
14 | "X{.x,y}": "X.1024.768",
15 | "{/var}": "/value",
16 | "{/var,x}/here": "/value/1024/here",
17 | "{;x,y}": ";x=1024;y=768",
18 | "{;x,y,empty}": ";x=1024;y=768;empty",
19 | "{?x,y}": "?x=1024&y=768",
20 | "{?x,y,empty}": "?x=1024&y=768&empty=",
21 | "?fixed=yes{&x}": "?fixed=yes&x=1024",
22 | "{&x,y,empty}": "&x=1024&y=768&empty=",
23 | "{var:3}": "val",
24 | "{var:30}": "value",
25 | "{list}": "red,green,blue",
26 | "{list*}": "red,green,blue",
27 | "{keys}": "semi,%3B,dot,.,comma,%2C",
28 | "{keys*}": "semi=%3B,dot=.,comma=%2C",
29 | "{+path:6}/here": "/foo/b/here",
30 | "{+list}": "red,green,blue",
31 | "{+list*}": "red,green,blue",
32 | "{+keys}": "semi,;,dot,.,comma,,",
33 | "{+keys*}": "semi=;,dot=.,comma=,",
34 | "{#path:6}/here": "#/foo/b/here",
35 | "{#list}": "#red,green,blue",
36 | "{#list*}": "#red,green,blue",
37 | "{#keys}": "#semi,;,dot,.,comma,,",
38 | "{#keys*}": "#semi=;,dot=.,comma=,",
39 | "X{.var:3}": "X.val",
40 | "X{.list}": "X.red,green,blue",
41 | "X{.list*}": "X.red.green.blue",
42 | "X{.keys}": "X.semi,%3B,dot,.,comma,%2C",
43 | "X{.keys*}": "X.semi=%3B.dot=..comma=%2C",
44 | "{/var:1,var}": "/v/value",
45 | "{/list}": "/red,green,blue",
46 | "{/list*}": "/red/green/blue",
47 | "{/list*,path:4}": "/red/green/blue/%2Ffoo",
48 | "{/keys}": "/semi,%3B,dot,.,comma,%2C",
49 | "{/keys*}": "/semi=%3B/dot=./comma=%2C",
50 | "{;hello:5}": ";hello=Hello",
51 | "{;list}": ";list=red,green,blue",
52 | "{;list*}": ";list=red;list=green;list=blue",
53 | "{;keys}": ";keys=semi,%3B,dot,.,comma,%2C",
54 | "{;keys*}": ";semi=%3B;dot=.;comma=%2C",
55 | "{?var:3}": "?var=val",
56 | "{?list}": "?list=red,green,blue",
57 | "{?list*}": "?list=red&list=green&list=blue",
58 | "{?keys}": "?keys=semi,%3B,dot,.,comma,%2C",
59 | "{?keys*}": "?semi=%3B&dot=.&comma=%2C",
60 | "{&var:3}": "&var=val",
61 | "{&list}": "&list=red,green,blue",
62 | "{&list*}": "&list=red&list=green&list=blue",
63 | "{&keys}": "&keys=semi,%3B,dot,.,comma,%2C",
64 | "{&keys*}": "&semi=%3B&dot=.&comma=%2C"
65 | };
66 | for (var sub in examples) {
67 | (function (sub, expected) {
68 | tests.add(sub, function () {
69 | var template = new Jsonary.UriTemplate(sub);
70 | var value = template.fromUri(expected);
71 |
72 | // We don't have checks for the original values - but we *can* check that it re-templates into the same thing
73 |
74 | var reconstructed = template.fillFromObject(value);
75 |
76 | this.assert(reconstructed == expected, JSON.stringify(reconstructed) + " != " + JSON.stringify(expected));
77 | return true;
78 | });
79 | })(sub, examples[sub]);
80 | }
--------------------------------------------------------------------------------
/tests/tests/11 - Utilities/01 - ResultCollector.js:
--------------------------------------------------------------------------------
1 | tests.add("ResultCollector sync use", function () {
2 | var thisTest = this;
3 |
4 | var resultArr = [];
5 | var results = Jsonary.ResultCollector(function (value) {
6 | resultArr.push(value);
7 | });
8 |
9 | var inputObj = {
10 | a: "A",
11 | b: "B"
12 | };
13 | for (var key in inputObj) {
14 | (function (key, value) {
15 | results.wait();
16 | results.result(value);
17 | })(key, inputObj[key]);
18 | }
19 |
20 | var called = false;
21 | results.whenDone(function () {
22 | called = true;
23 | thisTest.assert(resultArr.length == 2, "should have two results");
24 | });
25 | this.assert(called, "callback called");
26 | this.assert(results.done === true, "results.done === true");
27 | return true;
28 | });
29 |
30 | tests.add("ResultCollector async use", function () {
31 | var thisTest = this;
32 |
33 | var resultArr = [];
34 | var results = Jsonary.ResultCollector(function (value) {
35 | resultArr.push(value);
36 | });
37 |
38 | var inputObj = {
39 | a: "A",
40 | b: "B"
41 | };
42 | for (var key in inputObj) {
43 | (function (key, value) {
44 | results.wait();
45 | setTimeout(function () {
46 | results.result(value);
47 | }, 10);
48 | })(key, inputObj[key]);
49 | }
50 |
51 | results.whenDone(function () {
52 | thisTest.assert(resultArr.length == 2, "should have two results");
53 | thisTest.pass();
54 | });
55 |
56 | this.assert(results.done === false, "results.done should be false");
57 |
58 | setTimeout(function () {
59 | thisTest.fail('timeout');
60 | }, 200)
61 | });
62 |
63 | tests.add("ResultCollector async use with keys (1)s", function () {
64 | var thisTest = this;
65 |
66 | var results = Jsonary.ResultCollector();
67 |
68 | var inputObj = {
69 | a: "A",
70 | b: "B"
71 | };
72 | for (var key in inputObj) {
73 | (function (key, value) {
74 | results.wait();
75 | setTimeout(function () {
76 | results.resultForKey(key, value);
77 | }, 10);
78 | })(key, inputObj[key]);
79 | }
80 |
81 | results.whenDone(function (obj) {
82 | thisTest.assert(obj.a === 'A', 'A match');
83 | thisTest.assert(obj.b === 'B', 'B match');
84 | thisTest.pass();
85 | });
86 |
87 | setTimeout(function () {
88 | thisTest.fail('timeout');
89 | }, 200)
90 | });
91 |
92 | tests.add("ResultCollector async use with keys (2)", function () {
93 | var thisTest = this;
94 |
95 | var results = Jsonary.ResultCollector();
96 |
97 | var inputObj = {
98 | a: "A",
99 | b: "B"
100 | };
101 | for (var key in inputObj) {
102 | (function (key, value) {
103 | results.wait();
104 | var callback = results.forKey(key);
105 | setTimeout(function () {
106 | callback(value);
107 | }, 10);
108 | })(key, inputObj[key]);
109 | }
110 |
111 | results.whenDone(function (obj) {
112 | thisTest.assert(obj.a === 'A', 'A match');
113 | thisTest.assert(obj.b === 'B', 'B match');
114 | thisTest.pass();
115 | });
116 |
117 | setTimeout(function () {
118 | thisTest.fail('timeout');
119 | }, 200)
120 | });
--------------------------------------------------------------------------------
/tests/tests/05 - Tertiary tests/02 - Arrays.js:
--------------------------------------------------------------------------------
1 | tests.add("tupleTypingLength()", function() {
2 | var schema1 = Jsonary.createSchema({
3 | items: {}
4 | });
5 | var schema2 = Jsonary.createSchema({
6 | items: [{}]
7 | });
8 | var schema3 = Jsonary.createSchema({
9 | items: [{}, {}]
10 | });
11 | var data = Jsonary.create([]);
12 |
13 | var tupleTypingLength = data.schemas().tupleTypingLength();
14 | this.assert(tupleTypingLength == 0, "tupleTypingLength == 0");
15 |
16 | data.addSchema(schema1);
17 | var tupleTypingLength = data.schemas().tupleTypingLength();
18 | this.assert(tupleTypingLength == 0, "tupleTypingLength still == 0");
19 |
20 | data.addSchema(schema2);
21 | var tupleTypingLength = data.schemas().tupleTypingLength();
22 | this.assert(tupleTypingLength == 1, "tupleTypingLength == 1");
23 |
24 | data.addSchema(schema3);
25 | var tupleTypingLength = data.schemas().tupleTypingLength();
26 | this.assert(tupleTypingLength == 2, "tupleTypingLength == 2");
27 |
28 | return true;
29 | });
30 |
31 | tests.add('uniqueItems()', function () {
32 | var schema1 = Jsonary.createSchema({
33 | type: 'array'
34 | });
35 | var schema2 = Jsonary.createSchema({
36 | type: 'array',
37 | uniqueItems: false
38 | });
39 | var schema3 = Jsonary.createSchema({
40 | type: 'array',
41 | uniqueItems: true
42 | });
43 |
44 | var schemaList;
45 | schemaList = Jsonary.createSchemaList([schema1]);
46 | this.assert(schemaList.uniqueItems() === false, '1: false');
47 | schemaList = Jsonary.createSchemaList([schema1, schema2]);
48 | this.assert(schemaList.uniqueItems() === false, '2: false');
49 | schemaList = Jsonary.createSchemaList([schema3]);
50 | this.assert(schemaList.uniqueItems() === true, '3: true');
51 | schemaList = Jsonary.createSchemaList([schema1, schema3]);
52 | this.assert(schemaList.uniqueItems() === true, '4: true');
53 | schemaList = Jsonary.createSchemaList([schema2, schema3]);
54 | this.assert(schemaList.uniqueItems() === true, '5: true');
55 | return true;
56 | });
57 |
58 | tests.add('unordered()', function () {
59 | var schema1 = Jsonary.createSchema({
60 | type: 'array'
61 | });
62 | var schema2 = Jsonary.createSchema({
63 | type: 'array',
64 | unordered: false
65 | });
66 | var schema3 = Jsonary.createSchema({
67 | type: 'array',
68 | unordered: true
69 | });
70 | var schema4 = Jsonary.createSchema({
71 | items: [{type: 'integer'}]
72 | });
73 |
74 | var schemaList;
75 | schemaList = Jsonary.createSchemaList([schema1]);
76 | this.assert(schemaList.unordered() === false, '1: false');
77 | schemaList = Jsonary.createSchemaList([schema1, schema2]);
78 | this.assert(schemaList.unordered() === false, '2: false');
79 | schemaList = Jsonary.createSchemaList([schema3]);
80 | this.assert(schemaList.unordered() === true, '3: true');
81 | schemaList = Jsonary.createSchemaList([schema1, schema3]);
82 | this.assert(schemaList.unordered() === true, '4: true');
83 | schemaList = Jsonary.createSchemaList([schema2, schema3]);
84 | this.assert(schemaList.unordered() === true, '5: true');
85 | schemaList = Jsonary.createSchemaList([schema2, schema3, schema4]);
86 | this.assert(schemaList.unordered() === false, '6: false');
87 | return true;
88 | });
--------------------------------------------------------------------------------
/jsonary/monitors.js:
--------------------------------------------------------------------------------
1 | function MonitorSet(context) {
2 | this.contents = {};
3 | this.keyOrder = [];
4 | this.context = context;
5 | }
6 | MonitorSet.prototype = {
7 | add: function (monitorKey, monitor) {
8 | if (typeof monitorKey != "string" && typeof monitorKey != "number") {
9 | throw new Error("First argument must be a monitorKey, obtained using getMonitorKey()");
10 | }
11 | this.contents[monitorKey] = monitor;
12 | this.addKey(monitorKey);
13 | },
14 | addKey: function (monitorKey) {
15 | var i;
16 | for (i = 0; i < this.keyOrder.length; i++) {
17 | var key = this.keyOrder[i];
18 | if (key == monitorKey) {
19 | return;
20 | }
21 | if (Utils.keyIsVariant(monitorKey, key)) {
22 | this.keyOrder.splice(i, 0, monitorKey);
23 | return;
24 | }
25 | }
26 | this.keyOrder.push(monitorKey);
27 | },
28 | remove: function (monitorKey) {
29 | delete this.contents[monitorKey];
30 | this.removeKey(monitorKey);
31 | var prefix = monitorKey + ".";
32 | for (var key in this.contents) {
33 | if (key.substring(0, prefix.length) == prefix) {
34 | this.removeKey(key);
35 | delete this.contents[key];
36 | }
37 | }
38 | },
39 | removeKey: function (monitorKey) {
40 | var index = this.keyOrder.indexOf(monitorKey);
41 | if (index >= 0) {
42 | this.keyOrder.splice(index, 1);
43 | }
44 | },
45 | notify: function () {
46 | var notifyArgs = arguments;
47 | for (var i = 0; i < this.keyOrder.length; i++) {
48 | var key = this.keyOrder[i];
49 | var monitor = this.contents[key];
50 | monitor.apply(this.context, notifyArgs);
51 | }
52 | },
53 | isEmpty: function () {
54 | var key;
55 | for (key in this.contents) {
56 | if (this.contents[key].length !== 0) {
57 | return false;
58 | }
59 | }
60 | return true;
61 | }
62 | };
63 |
64 | function ListenerSet(context) {
65 | this.listeners = [];
66 | this.context = context;
67 | }
68 | ListenerSet.prototype = {
69 | add: function (listener) {
70 | this.listeners[this.listeners.length] = listener;
71 | },
72 | notify: function () {
73 | var listenerArgs = arguments;
74 | while (this.listeners.length > 0) {
75 | var listener = this.listeners.shift();
76 | listener.apply(this.context, listenerArgs);
77 | }
78 | },
79 | isEmpty: function () {
80 | return this.listeners.length === 0;
81 | }
82 | };
83 |
84 | // DelayedCallbacks is used for notifications that might be external to the library
85 | // The callbacks are still executed synchronously - however, they are not executed while the system is in a transitional state.
86 | var DelayedCallbacks = {
87 | depth: 0,
88 | callbacks: [],
89 | increment: function () {
90 | this.depth++;
91 | },
92 | decrement: function () {
93 | this.depth--;
94 | if (this.depth < 0) {
95 | throw new Error("DelayedCallbacks.depth cannot be < 0");
96 | }
97 | while (this.depth == 0 && this.callbacks.length > 0) {
98 | var callback = this.callbacks.shift();
99 | this.depth++;
100 | callback();
101 | this.depth--
102 | }
103 | },
104 | add: function (callback) {
105 | this.depth++;
106 | this.callbacks.push(callback);
107 | this.decrement();
108 | }
109 | };
110 |
--------------------------------------------------------------------------------
/tests/tests/08 - URI Templating (RFC 6570)/03 - Pre-processing.js:
--------------------------------------------------------------------------------
1 | tests.add("Bracket escaping", function () {
2 | var data = Jsonary.create({
3 | "a/b": "#",
4 | "(a/b)": "wrong"
5 | });
6 | var link = Jsonary.create({
7 | "rel": "test",
8 | "href": "prefix_{(a/b)}_suffix"
9 | }).asLink(data);
10 | var expected = "prefix_%23_suffix";
11 | this.assert(link.href == expected, JSON.stringify(link.href) + " != " + JSON.stringify(expected));
12 | return true;
13 | });
14 |
15 | tests.add("Bracket escaping escaping: ))", function () {
16 | var data = Jsonary.create({
17 | "a/b)": "#",
18 | "a/b": "wrong"
19 | });
20 | var link = Jsonary.create({
21 | "rel": "test",
22 | "href": "prefix_{(a/b)))}_suffix"
23 | }).asLink(data);
24 | var expected = "prefix_%23_suffix";
25 | this.assert(link.href == expected, JSON.stringify(link.href) + " != " + JSON.stringify(expected));
26 | return true;
27 | });
28 |
29 | tests.add("Extra character escaping: *", function () {
30 | var data = Jsonary.create({
31 | "a*": "value",
32 | "a": ["wrong", "wrong"]
33 | });
34 | var link = Jsonary.create({
35 | "rel": "test",
36 | "href": "prefix_{(a*)}_suffix"
37 | }).asLink(data);
38 | var expected = "prefix_value_suffix";
39 | this.assert(link.href == expected, JSON.stringify(link.href) + " != " + JSON.stringify(expected));
40 | return true;
41 | });
42 |
43 | tests.add("Bracket escaping with modifiers", function () {
44 | var data = Jsonary.create({
45 | "a/b": "#",
46 | "(a/b)": "wrong"
47 | });
48 | var link = Jsonary.create({
49 | "rel": "test",
50 | "href": "prefix_{+(a/b)}_suffix"
51 | }).asLink(data);
52 | var expected = "prefix_#_suffix";
53 | this.assert(link.href == expected, JSON.stringify(link.href) + " != " + JSON.stringify(expected));
54 | return true;
55 | });
56 |
57 | tests.add("$ == self", function () {
58 | var data = Jsonary.create("value#");
59 | var link = Jsonary.create({
60 | "rel": "test",
61 | "href": "prefix_{$}_suffix"
62 | }).asLink(data);
63 | var expected = "prefix_value%23_suffix";
64 | this.assert(link.href == expected, JSON.stringify(link.href) + " != " + JSON.stringify(expected));
65 | return true;
66 | });
67 |
68 | tests.add("$ == self", function () {
69 | var data = Jsonary.create("value#");
70 | var link = Jsonary.create({
71 | "rel": "test",
72 | "href": "prefix_{+$}_suffix"
73 | }).asLink(data);
74 | var expected = "prefix_value#_suffix";
75 | this.assert(link.href == expected, JSON.stringify(link.href) + " != " + JSON.stringify(expected));
76 | return true;
77 | });
78 |
79 | tests.add("$ escaped", function () {
80 | var data = Jsonary.create({
81 | "$": "value"
82 | });
83 | var link = Jsonary.create({
84 | "rel": "test",
85 | "href": "prefix_{($)}_suffix"
86 | }).asLink(data);
87 | var expected = "prefix_value_suffix";
88 | this.assert(link.href == expected, JSON.stringify(link.href) + " != " + JSON.stringify(expected));
89 | return true;
90 | });
91 |
92 | tests.add("() == empty", function () {
93 | var data = Jsonary.create({
94 | "empty": "wrong",
95 | "%65mpty": "wronger",
96 | "": "right"
97 | });
98 | var link = Jsonary.create({
99 | "rel": "test",
100 | "href": "prefix_{()}_suffix"
101 | }).asLink(data);
102 | var expected = "prefix_right_suffix";
103 | this.assert(link.href == expected, JSON.stringify(link.href) + " != " + JSON.stringify(expected));
104 | return true;
105 | });
106 |
--------------------------------------------------------------------------------
/tests/tests/10 - Schema matching (validation)/01 - Match applicable schemas.js:
--------------------------------------------------------------------------------
1 | tests.add("Match all fixed schemas for data", function () {
2 | var schema = Jsonary.createSchema({
3 | "title": "Example schema",
4 | "type": "array",
5 | "items": {"type": "string"}
6 | });
7 |
8 | var data = Jsonary.create(["string", false]).addSchema(schema);
9 |
10 | var match = data.validate();
11 | this.assert(!!match, "match should be defined");
12 | this.assert(!!match.errors, "match.errors should be defined");
13 | this.assert(match.valid === false, "match.valid should be false");
14 | this.assert(typeof match.errors[0].message === 'string', "Error message should be string");
15 |
16 | return true;
17 | });
18 |
19 | tests.add("Available as data.valid()", function () {
20 | var schema = Jsonary.createSchema({
21 | "title": "Example schema",
22 | "type": "array",
23 | "items": {"type": "string"}
24 | });
25 |
26 | var data = Jsonary.create(["string", "foo"]).addSchema(schema);
27 |
28 | this.assert(data.valid() === true, "data.valid() should be true");
29 |
30 | return true;
31 | });
32 |
33 | tests.add("Works for read-only as well", function () {
34 | var schema = Jsonary.createSchema({
35 | "title": "Example schema",
36 | "type": "array",
37 | "items": {"type": "string"}
38 | });
39 |
40 | var data = Jsonary.create(["string", false], null, true).addSchema(schema);
41 |
42 | var match = data.validate();
43 | this.assert(match.valid === false, "match.valid should be false");
44 |
45 | return true;
46 | });
47 |
48 | tests.add("Tracks data changes", function () {
49 | var schema = Jsonary.createSchema({
50 | "title": "Example schema",
51 | "type": "array",
52 | "items": {"type": "string"}
53 | });
54 |
55 | var data = Jsonary.create(["string", false]).addSchema(schema);
56 |
57 | var match = data.validate();
58 | this.assert(match.valid === false, "match.valid should be false");
59 |
60 | data.set('/1', 'foo');
61 |
62 | this.assert(match.valid === true, "match.valid should be true after change");
63 |
64 | return true;
65 | });
66 |
67 | tests.add("Callback when changed", function () {
68 | var thisTest = this;
69 | var schema = Jsonary.createSchema({
70 | "title": "Example schema",
71 | "type": "array",
72 | "items": {"type": "string"}
73 | });
74 |
75 | var data = Jsonary.create(["string", false]).addSchema(schema);
76 |
77 | var callbackCounter = 0;
78 | data.validate().onChange(function (match) {
79 | callbackCounter++;
80 | if (callbackCounter == 1) {
81 | thisTest.assert(match.valid === false, 'initial is false');
82 | } else if (callbackCounter == 2) {
83 | thisTest.assert(match.valid === true, 'second is true');
84 | } else {
85 | thisTest.assert(match.valid === false, 'remaining is false');
86 | }
87 | });
88 |
89 | data.set('/1', 'foo');
90 | data.set('/0', 5);
91 |
92 | this.assert(callbackCounter === 3, 'Should have three callbacks by end');
93 | return true;
94 | });
95 |
96 | tests.add("Child also has validation", function () {
97 | var schema = Jsonary.createSchema({
98 | "title": "Example schema",
99 | "type": "array",
100 | "items": {"type": "string"}
101 | });
102 |
103 | var data = Jsonary.create(["string", false]).addSchema(schema);
104 |
105 | this.assert(data.item(0).valid() === true, "first child should be valid");
106 | this.assert(data.item(1).valid() === false, "second child should be invalid");
107 |
108 | return true;
109 | });
110 |
--------------------------------------------------------------------------------
/tests/tests/04 - Default values and data creation/01 - Default values.js:
--------------------------------------------------------------------------------
1 | tests.add("Basic default", function () {
2 | var schema = Jsonary.createSchema({
3 | "default": "Default value"
4 | });
5 |
6 | this.assert(schema.hasDefault() == true, "hasDefault() should be true");
7 | this.assert(schema.defaultValue() == "Default value", "defaultValue() should provide a default when one exists");
8 |
9 | return true;
10 | });
11 |
12 | tests.add("Assembling data (default)", function () {
13 | var schema = Jsonary.createSchema({
14 | "default": "Test string"
15 | });
16 | var schemaList = schema.asList();
17 |
18 | var value = schemaList.createValue();
19 | this.assert(value == "Test string");
20 |
21 | return true;
22 | });
23 |
24 | tests.add("Schema list items (from default)", function () {
25 | var schema = Jsonary.createSchema({
26 | "items": {
27 | "default": "New item"
28 | }
29 | });
30 |
31 | var data = Jsonary.create([]);
32 | data.addSchema(schema);
33 |
34 | this.assert(schema.hasDefault() == false, "hasDefault() should be false");
35 |
36 | var schemas = data.schemas();
37 | this.assert(schemas.length == 1, "schemas should have exactly one item");
38 |
39 | var assembledData = schemas.createValueForIndex(0);
40 | this.assert(assembledData == "New item", "assembledData == \"New item\"");
41 |
42 | return true;
43 | });
44 |
45 | tests.add("Schema properties (from default)", function () {
46 | var schema = Jsonary.createSchema({
47 | "properties": {
48 | "key1": {
49 | "default": "value1"
50 | },
51 | "key2": {
52 | "default": "value2"
53 | }
54 | },
55 | "additionalProperties": {
56 | "default": "Additional properties"
57 | }
58 | });
59 |
60 | var data = Jsonary.create([]);
61 | data.addSchema(schema);
62 |
63 | this.assert(schema.hasDefault() == false, "hasDefault() should be false");
64 |
65 | var schemas = data.schemas();
66 | this.assert(schemas.length == 1, "schemas should have exactly one item");
67 |
68 | var assembledData = schemas.createValueForProperty("key1");
69 | this.assert(assembledData == "value1", "key1 == \"value1\"");
70 |
71 | var assembledData = schemas.createValueForProperty("key2");
72 | this.assert(assembledData == "value2", "key2 == \"value2\"");
73 |
74 | var assembledData = schemas.createValueForProperty("key3");
75 | this.assert(assembledData == "Additional properties", "key3 == \"Additional Properties1\"");
76 |
77 | return true;
78 | });
79 |
80 |
81 | tests.add("Schema list tuple items (from default)", function () {
82 | var schema = Jsonary.createSchema({
83 | "items": [
84 | {
85 | "default": "item0"
86 | },
87 | {
88 | "default": "item1"
89 | }
90 | ],
91 | "additionalItems": {
92 | "default": "Additional items"
93 | }
94 | });
95 |
96 | var data = Jsonary.create([]);
97 | data.addSchema(schema);
98 |
99 | this.assert(schema.hasDefault() == false, "hasDefault() should be false");
100 |
101 | var schemas = data.schemas();
102 | this.assert(schemas.length == 1, "schemas should have exactly one item");
103 |
104 | var assembledData = schemas.createValueForIndex(0);
105 | this.assert(assembledData == "item0", "assembledData == \"New item\"");
106 |
107 | var assembledData = schemas.createValueForIndex(1);
108 | this.assert(assembledData == "item1", "assembledData == \"item1\"");
109 |
110 | var assembledData = schemas.createValueForIndex(2);
111 | this.assert(assembledData == "Additional items", "assembledData == \"Additional items\"");
112 |
113 | return true;
114 | });
115 |
116 |
--------------------------------------------------------------------------------
/documentation/plugins/undo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Jsonary Undo/Redo Plugin
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
Jsonary Undo/Redo Plugin
18 |
19 |
20 |
24 |
25 |
26 | The demos on this page use the following files:
27 |
28 | jsonary.js - the core Jsonary library
29 | plain.jsonary.js - a default set of renderers, that look similar to plain JSON
30 | plain.jsonary.css - the stylesheet used by the above renderers
31 | jsonary.undo.js - the undo/redo plugin
32 |
33 |
34 |
35 |
How it works
36 |
37 |
Internally, Jsonary uses the "JSON Patch" format to communicate changes to the data. When a change listener is registered, and data changes are made, the listener is passed an object representing the JSON Patch that was applied, along with the document it was applied to.
38 |
So to obtain an "undo history", we simply have to collect these patch/document pairs.
39 |
40 | Jsonary.registerChangeListener(function (patch, document) {
41 | undoList.push({patch: patch, document: document});
42 | });
43 |
44 |
We can then create a method Jsonary.undo() that pops off the latest change from this list, reverses it, and applies the resulting patch to the appropriate document.
45 |
46 | Jsonary.extend({
47 | undo: function () {
48 | var lastChange = undoList.pop();
49 | if (lastChange != undefined) {
50 | lastChange.document.patch(lastChange.patch.inverse());
51 | }
52 | }
53 | });
54 |
55 |
Actually, the full code is slightly more complicated than that, because when the patch is applied, the change listener callback is called again. It also maintains a "redo" list.
56 |
However, it is still extremely short: have a look!
57 |
58 |
59 |
Working example
60 |
61 |
Try editing this item, and then pressing CTRL+Z, CTRL+SHIFT+Z or CTRL+Y (Mac users should be able to use CMD instead).
62 |
63 |
83 |
84 |
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/tests/tests/02 - Secondary tests/07 - Advanced schema matching - version 4.js:
--------------------------------------------------------------------------------
1 | tests.add("Dependencies (array)", function() {
2 | var data = Jsonary.create({"key1": 1});
3 | var schema = Jsonary.createSchema({
4 | "dependencies": {
5 | "key1": ["key2", "key3"]
6 | }
7 | });
8 |
9 | var dependencies = schema.propertyDependencies("key1");
10 | this.assert(dependencies.length == 2, "dependencies.length == 2");
11 |
12 | var match = null;
13 | var failReason = null;
14 | var notificationCount = 0;
15 | var schemaKey = Jsonary.getMonitorKey();
16 | data.addSchemaMatchMonitor(schemaKey, schema, function(m, fr) {
17 | notificationCount++;
18 | match = m;
19 | failReason = fr;
20 | });
21 |
22 | this.assert(!match, "should not match initially");
23 | this.assert(notificationCount == 1, "notificationCount == 1, not " + notificationCount);
24 |
25 | data.property("key2").setValue("needed");
26 | if (notificationCount == 1) {
27 | notificationCount = 2;
28 | }
29 | this.assert(notificationCount == 2, "notificationCount == 2, not " + notificationCount);
30 | this.assert(!match, "should still not match: " + failReason);
31 |
32 | data.property("key3").setValue("needed");
33 | this.assert(notificationCount == 3, "notificationCount == 3, not " + notificationCount);
34 | this.assert(match, "should match now: " + failReason);
35 |
36 | data.removeProperty("key1");
37 | this.assert(notificationCount == 3, "notificationCount == 3, not " + notificationCount);
38 | this.assert(match, "should still match after key1 has been removed");
39 |
40 | return true;
41 | });
42 |
43 | tests.add("Required properties (v4-style)", function () {
44 | var data = Jsonary.create({
45 | "key1": 1
46 | });
47 | var schema = Jsonary.createSchema({
48 | "properties": {
49 | "key1": {
50 | },
51 | "key2": {
52 | }
53 | },
54 | "required": ["key1", "key2"]
55 | });
56 |
57 | var requiredKeys = schema.requiredProperties();
58 | this.assert(requiredKeys.length == 2, "There should be two required keys");
59 |
60 | var match = null;
61 | var schemaKey = Jsonary.getMonitorKey();
62 | data.addSchemaMatchMonitor(schemaKey, schema, function (m) {
63 | match = m;
64 | });
65 |
66 | this.assert(!match, "should not match initially");
67 |
68 | data.property("key3").setValue("not needed");
69 | this.assert(!match, "should still not match");
70 |
71 | data.property("key2").setValue("needed");
72 | this.assert(match, "should match, now we have both keys");
73 |
74 | data.removeProperty("key1");
75 | this.assert(!match, "should not match, when we remove key1");
76 |
77 | return true;
78 | });
79 |
80 | tests.add("\"not\" match (not keyword)", function () {
81 | var data = Jsonary.create("other value");
82 | var schema = Jsonary.createSchema({
83 | "not": {
84 | "enum": ["value"]
85 | }
86 | });
87 |
88 | var match = null;
89 | var failReason = null;
90 | var notificationCount = 0;
91 | var schemaKey = Jsonary.getMonitorKey();
92 | data.addSchemaMatchMonitor(schemaKey, schema, function (m, fr) {
93 | notificationCount++;
94 | match = m;
95 | failReason = fr;
96 | });
97 |
98 | this.assert(match, "should match initially");
99 | this.assert(notificationCount == 1, "notificationCount == 1, not " + notificationCount);
100 |
101 | data.setValue("value");
102 | this.assert(!match, "should not match after stage 2");
103 | this.assert(notificationCount == 2, "notificationCount == 2, not " + notificationCount);
104 |
105 | data.setValue(5);
106 | this.assert(match, "should match after stage 3");
107 | this.assert(notificationCount == 3, "notificationCount == 3, not " + notificationCount);
108 |
109 | return true;
110 | });
111 |
--------------------------------------------------------------------------------
/demos/css/basic.render.jsonary.css:
--------------------------------------------------------------------------------
1 | .json-schema, .json-link {
2 | margin-right: 0.5em;
3 | margin-left: 0.5em;
4 | border: 1px solid #DD3;
5 | background-color: #FFB;
6 | padding-left: 0.5em;
7 | padding-right: 0.5em;
8 | color: #880;
9 | font-size: 0.85em;
10 | font-style: italic;
11 | text-decoration: none;
12 | }
13 |
14 | .json-link {
15 | border: 1px solid #88F;
16 | background-color: #DDF;
17 | color: #008;
18 | font-style: normal;
19 | }
20 |
21 | .json-raw {
22 | display: inline;
23 | white-space: pre;
24 | }
25 |
26 | .valid {
27 | background-color: #DFD;
28 | }
29 |
30 | .invalid {
31 | background-color: #FDD;
32 | }
33 |
34 | textarea {
35 | vertical-align: middle;
36 | }
37 |
38 | /**** JSON Object ****/
39 |
40 | .json-object {
41 | }
42 |
43 | .json-object-pair {
44 | display: block;
45 | padding-left: 2em;
46 | }
47 |
48 | .json-object-key {
49 | font-style: italic;
50 | }
51 |
52 | .json-object-delete {
53 | font-family: monospace;
54 | font-style: normal;
55 | font-weight: bold;
56 | color: #F00;
57 | text-decoration: none;
58 | margin-right: 1em;
59 | }
60 |
61 | .json-object-add {
62 | display: block;
63 | padding-left: 2.2em;
64 | color: #888;
65 | font-size: 0.9em;
66 | }
67 |
68 | .json-object-add-key, .json-object-add-key-new {
69 | text-decoration: none;
70 | margin-left: 1em;
71 | color: #000;
72 | border: 1px solid #888;
73 | background-color: #EEE;
74 | }
75 |
76 | .json-object-add-key-new {
77 | border: 1px dotted #BBB;
78 | background-color: #EEF;
79 | font-style: italic;
80 | }
81 |
82 | /**** JSON Array ****/
83 |
84 | .json-array {
85 | }
86 |
87 | .json-array-item {
88 | display: block;
89 | padding-left: 2em;
90 | }
91 |
92 | .json-array-delete {
93 | font-family: monospace;
94 | font-style: normal;
95 | font-weight: bold;
96 | color: #F00;
97 | text-decoration: none;
98 | margin-right: 1em;
99 | }
100 |
101 | .json-array-add {
102 | display: block;
103 | padding-left: 2.2em;
104 | color: #000;
105 |
106 | font-family: monospace;
107 | font-style: normal;
108 | font-weight: bold;
109 | color: #00F;
110 | text-decoration: none;
111 | margin-right: 1em;
112 | }
113 |
114 | /**** String ****/
115 |
116 | .json-string {
117 | white-space: pre;
118 | color: #000;
119 | }
120 |
121 | .json-string:before, .json-string:after {
122 | content: "\"";
123 | color: #888;
124 | }
125 |
126 | /**** Number ****/
127 |
128 | .json-number {
129 | font-family: monospace;
130 | color: #000;
131 | font-weight: bold;
132 | text-decoration: none;
133 | }
134 |
135 | .json-number-increment, .json-number-decrement {
136 | font-family: monospace;
137 | color: #808;
138 | font-weight: bold;
139 | text-decoration: none;
140 | margin-left: 0.5em;
141 | margin-right: 0.5em;
142 | padding-left: 0.5em;
143 | padding-right: 0.5em;
144 | border: 1px solid #888;
145 | background-color: #DDD;
146 | }
147 |
148 | /**** Boolean ****/
149 |
150 | .json-boolean-true, .json-boolean-false {
151 | font-family: monospace;
152 | color: #080;
153 | font-weight: bold;
154 | text-decoration: none;
155 | }
156 |
157 | .json-boolean-false {
158 | color: #800;
159 | }
160 |
161 | /**** Prompt ****/
162 |
163 | .prompt-overlay {
164 | position: absolute;
165 | top: 0;
166 | left: 0;
167 | width: 70%;
168 | height: 100%;
169 | background-color: #000;
170 | padding-left: 15%;
171 | padding-right: 15%;
172 | background-color: rgba(100, 100, 100, 0.5);
173 | }
174 |
175 | .prompt-buttons {
176 | background-color: #EEE;
177 | border: 2px solid black;
178 | text-align: center;
179 | position: relative;
180 | }
181 |
182 | .prompt-data {
183 | background-color: white;
184 | border: 2px solid black;
185 | border-radius: 10px;
186 | position: relative;
187 | }
188 |
--------------------------------------------------------------------------------