├── api ├── blank.json ├── raw-link.json ├── jsonary-document.json ├── jsonary-link.json ├── api-schema.json └── jsonary-link-definition.json ├── Procfile ├── tests ├── test.json ├── tests │ ├── 06 - Live data and requests │ │ ├── 05 - data URLs.js │ │ ├── 03 - Extra headers.js │ │ ├── 04 - beforeAjax.js │ │ ├── 02 - Updating values.js │ │ └── 01 - Schema hints.js │ ├── 01 - Basic tests │ │ ├── 08 - Fetching fragments.js │ │ ├── 01 - Existence.js │ │ ├── 04 - Base URLs.js │ │ ├── 03 - Data comparison.js │ │ └── 05 - Requests.js │ ├── 04 - Default values and data creation │ │ ├── 06 - custom creation functions.js │ │ ├── 08 - baseUri.js │ │ ├── 07 - selecting from 'oneOf'.js │ │ ├── 03 - Enum selection.js │ │ └── 01 - Default values.js │ ├── 09 - Hyper-text │ │ └── 01 - Inline addressing.js │ ├── .meta-tests │ │ ├── Timer test.js │ │ └── recursiveCompare.js │ ├── 05 - Tertiary tests │ │ ├── 01 - Numbers.js │ │ ├── 03 - Strings.js │ │ ├── 04 - Objects.js │ │ └── 02 - Arrays.js │ ├── 03 - Interaction │ │ ├── 04 - Constraints.js │ │ └── 05 - Batch editing.js │ ├── 07 - Plugins │ │ └── 01 - Extending Jsonary.js │ ├── Bugs │ │ └── Recursive dependencies.js │ ├── 10 - Schema matching (validation) │ │ ├── 02 - Test suite.js │ │ └── 01 - Match applicable schemas.js │ ├── 08 - URI Templating (RFC 6570) │ │ ├── 02 - Use in links.js │ │ ├── 05 - Reverse-engineering (more).js │ │ └── 03 - Pre-processing.js │ ├── 11 - Utilities │ │ └── 01 - ResultCollector.js │ └── 02 - Secondary tests │ │ └── 07 - Advanced schema matching - version 4.js ├── render-tests │ ├── 01 - Basic tests │ │ ├── 01 - Simple asyncRenderHtml.js │ │ ├── 02 - custom renderers.js │ │ └── 03 - Each renderer only once.js │ ├── 00 - Bugs │ │ └── 01 - re-rendering remote items.js │ └── 02 - Link-handlers in renderers │ │ └── 01 - render and test link handler.js ├── todo.html ├── js │ └── render.test-set.js └── page.test.css ├── blog ├── .gitignore ├── schemas │ ├── index-administrator.json │ ├── index.json │ └── post.json ├── config.php ├── login.php ├── post.php └── index.php ├── demos ├── json │ ├── data.json │ ├── view.php │ ├── update.php │ └── index.php ├── css │ ├── loading.gif │ ├── page.index.css │ ├── page.facebook.css │ ├── facebook.render.jsonary.css │ └── basic.render.jsonary.css ├── hints │ └── facebook │ │ ├── objects │ │ ├── common.json │ │ ├── links │ │ │ ├── like.json │ │ │ ├── photo.json │ │ │ └── user.json │ │ ├── comment.json │ │ ├── post.json │ │ ├── photo.json │ │ ├── album.json │ │ └── user.json │ │ ├── old │ │ ├── user-feed.json │ │ ├── user-friends.json │ │ ├── base.json │ │ ├── post.json │ │ ├── item-guess.json │ │ └── result-array.json │ │ ├── actions │ │ ├── like.json │ │ ├── upload-photo.json │ │ ├── post.json │ │ └── comment.json │ │ ├── me.json │ │ └── arrays │ │ ├── posts.json │ │ ├── albums.json │ │ ├── likes.json │ │ ├── photos.json │ │ ├── comments.json │ │ └── user-links.json ├── q-a │ ├── questions │ │ ├── 3.json │ │ ├── 2.json │ │ └── 1.json │ ├── schema-question.json │ ├── schema-answer.json │ └── index.html ├── schemas │ └── simple-post.json ├── js │ ├── facebook-credentials.js │ ├── facebook-credentials-template.js │ ├── page.index-edit.js │ └── page.index.js ├── facebook.html ├── index-plain.html ├── index-edit.html ├── view-schema.html ├── index.html └── index-input.html ├── Jsonary.png ├── favicon.png ├── Jsonary-glow.png ├── Jsonary-large.png ├── renderers ├── images │ ├── error.png │ ├── delete.png │ ├── loading.gif │ ├── move-up.png │ ├── warning.png │ ├── move-down.png │ ├── move-cancel.png │ ├── move-to-here.png │ ├── move-up-down.png │ └── background-stripe.png ├── contributed │ ├── image-picker.css │ ├── tag-list.css │ ├── validation.js │ ├── full-preview.js │ ├── markdown.js │ ├── json-schema.css │ ├── tag-list.js │ └── full-instances.js ├── api.jsonary.css ├── string-formats.js ├── list-schemas.js └── api.jsonary.js ├── plugins ├── images │ ├── sort-asc.png │ └── sort-desc.png ├── jsonary.render.table.css ├── jsonary.route.js ├── jsonary.render.generate.js └── jsonary.undo.js ├── documentation ├── json-schema │ └── keywords │ │ ├── Meta-data │ │ ├── 02 - description.json │ │ ├── 01 - title.json │ │ ├── 03 - $schema.json │ │ └── 04 - default.json │ │ ├── Array validation │ │ ├── 05 - uniqueItems.json │ │ ├── 03 - minItems.json │ │ ├── 04 - maxItems.json │ │ ├── 01 - items.json │ │ └── 02 - additionalItems.json │ │ ├── Object validation │ │ ├── 05 - maxProperties.json │ │ ├── 06 - minProperties.json │ │ ├── 01 - properties.json │ │ ├── 02 - patternProperties.json │ │ ├── 04 - required.json │ │ └── 03 - additionalProperties.json │ │ ├── General keywords │ │ ├── 06 - not.json │ │ ├── 01 - enum.json │ │ ├── 08 - extends.json │ │ ├── 09 - disallow.json │ │ ├── 03 - allOf.json │ │ ├── 07 - format.json │ │ ├── 02 - type.json │ │ ├── 04 - anyOf.json │ │ └── 05 - oneOf.json │ │ ├── Numeric validation │ │ ├── 01 - minimum.json │ │ ├── 03 - maximum.json │ │ ├── 05 - divisibleBy.json │ │ ├── 02 - exclusiveMinimum.json │ │ └── 04- exclusiveMaximum.json │ │ ├── String validation │ │ ├── 02- minLength.json │ │ ├── 01 - maxLength.json │ │ └── 03 - pattern.json │ │ └── Referencing │ │ ├── $ref.json │ │ └── id.json └── plugins │ └── undo.html ├── jsonary ├── _footer.js ├── _header.js ├── main.js └── monitors.js ├── site ├── site.json ├── schemas │ ├── site.json │ └── page.json ├── pages │ └── index.json └── renderers │ └── site-backup.js ├── .gitmodules ├── .gitattributes ├── package.json ├── LICENSE.txt ├── assemble-package.js └── .gitignore /api/blank.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node express-site/site.js 2 | -------------------------------------------------------------------------------- /tests/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "test": "Test data" 3 | } 4 | -------------------------------------------------------------------------------- /blog/.gitignore: -------------------------------------------------------------------------------- 1 | password.php 2 | comments 3 | posts 4 | -------------------------------------------------------------------------------- /demos/json/data.json: -------------------------------------------------------------------------------- 1 | {"message":"message text","dangerRating":0} -------------------------------------------------------------------------------- /Jsonary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geraintluff/jsonary/HEAD/Jsonary.png -------------------------------------------------------------------------------- /favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geraintluff/jsonary/HEAD/favicon.png -------------------------------------------------------------------------------- /Jsonary-glow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geraintluff/jsonary/HEAD/Jsonary-glow.png -------------------------------------------------------------------------------- /Jsonary-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geraintluff/jsonary/HEAD/Jsonary-large.png -------------------------------------------------------------------------------- /demos/css/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geraintluff/jsonary/HEAD/demos/css/loading.gif -------------------------------------------------------------------------------- /renderers/images/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geraintluff/jsonary/HEAD/renderers/images/error.png -------------------------------------------------------------------------------- /plugins/images/sort-asc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geraintluff/jsonary/HEAD/plugins/images/sort-asc.png -------------------------------------------------------------------------------- /plugins/images/sort-desc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geraintluff/jsonary/HEAD/plugins/images/sort-desc.png -------------------------------------------------------------------------------- /renderers/images/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geraintluff/jsonary/HEAD/renderers/images/delete.png -------------------------------------------------------------------------------- /renderers/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geraintluff/jsonary/HEAD/renderers/images/loading.gif -------------------------------------------------------------------------------- /renderers/images/move-up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geraintluff/jsonary/HEAD/renderers/images/move-up.png -------------------------------------------------------------------------------- /renderers/images/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geraintluff/jsonary/HEAD/renderers/images/warning.png -------------------------------------------------------------------------------- /renderers/images/move-down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geraintluff/jsonary/HEAD/renderers/images/move-down.png -------------------------------------------------------------------------------- /renderers/images/move-cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geraintluff/jsonary/HEAD/renderers/images/move-cancel.png -------------------------------------------------------------------------------- /renderers/images/move-to-here.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geraintluff/jsonary/HEAD/renderers/images/move-to-here.png -------------------------------------------------------------------------------- /renderers/images/move-up-down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geraintluff/jsonary/HEAD/renderers/images/move-up-down.png -------------------------------------------------------------------------------- /api/raw-link.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Link Description Object", 3 | "description": "As defined in the JSON Hyper-Schema standard" 4 | } -------------------------------------------------------------------------------- /renderers/images/background-stripe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geraintluff/jsonary/HEAD/renderers/images/background-stripe.png -------------------------------------------------------------------------------- /demos/hints/facebook/objects/common.json: -------------------------------------------------------------------------------- 1 | { 2 | "links": [ 3 | { 4 | "href": "https://graph.facebook.com/{id}", 5 | "rel": "self" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /documentation/json-schema/keywords/Meta-data/02 - description.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "description", 3 | "description": "A brief description of the schema", 4 | "content": [ 5 | ] 6 | } -------------------------------------------------------------------------------- /jsonary/_footer.js: -------------------------------------------------------------------------------- 1 | publicApi.UriTemplate = UriTemplate; 2 | 3 | // Puts it in "exports" if it exists, otherwise create this.Jsonary (this == window, probably) 4 | })(this.Jsonary = {}); 5 | -------------------------------------------------------------------------------- /documentation/json-schema/keywords/Array validation/05 - uniqueItems.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "uniqueItems", 3 | "description": "whether all the values in the array must be distinct", 4 | "content": [ 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /demos/q-a/questions/3.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 3, 3 | "question": "What is your favourite food?", 4 | "answerSchema": { 5 | "properties": { 6 | "answer": {"minLength": 1, "maxLength": 100} 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /demos/hints/facebook/old/user-feed.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "User's Wall", 3 | "extends": {"$ref": "result-array.json"}, 4 | "properties": { 5 | "data": { 6 | "items": {"$ref": "post.json"} 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /demos/hints/facebook/old/user-friends.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Friends List", 3 | "extends": {"$ref": "result-array.json"}, 4 | "properties": { 5 | "data": { 6 | "items": {"$ref": "userlink.json"} 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /demos/q-a/questions/2.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 2, 3 | "question": "Whom do you serve?", 4 | "options": ["Sauron", "Saruman"], 5 | "answerSchema": { 6 | "properties": { 7 | "answer": {"enum": ["Sauron", "Saruman"]} 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /demos/q-a/questions/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "question": "What is your favourite container format?", 4 | "options": ["XML", "JSON"], 5 | "answerSchema": { 6 | "properties": { 7 | "answer": {"enum": ["XML", "JSON"]} 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /documentation/json-schema/keywords/Object validation/05 - maxProperties.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "maxProperties", 3 | "description": "version 4 only - specifies the maximum number of properties an instance can have defined", 4 | "content": [ 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /documentation/json-schema/keywords/Object validation/06 - minProperties.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "minProperties", 3 | "description": "version 4 only - specifies a minumum number of properties that an instance must define", 4 | "content": [ 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /demos/hints/facebook/old/base.json: -------------------------------------------------------------------------------- 1 | { 2 | "links": [ 3 | { 4 | "href": "http://graph.facebook.com/{id}", 5 | "rel": "self" 6 | }, 7 | { 8 | "href": "http://graph.facebook.com/{id}/picture", 9 | "rel": "picture" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /documentation/json-schema/keywords/General keywords/06 - not.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "not", 3 | "description": "version 4 only - schema negation", 4 | "content": [ 5 | "The instance must not be valid according to the schema specified in this property." 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /documentation/json-schema/keywords/Meta-data/01 - title.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "title", 3 | "description": "Provides a title for the schema", 4 | "content": [ 5 | ], 6 | "exampleSchema": { 7 | "title": "Example Schema" 8 | }, 9 | "exampleData": { 10 | "key": "value" 11 | } 12 | } -------------------------------------------------------------------------------- /documentation/json-schema/keywords/Numeric validation/01 - minimum.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "minimum", 3 | "description": "Minimum allowed numerical value", 4 | "content": [ 5 | ], 6 | "exampleSchema": { 7 | "type": "number", 8 | "minimum": 0 9 | }, 10 | "exampleData": 1 11 | } 12 | -------------------------------------------------------------------------------- /documentation/json-schema/keywords/Numeric validation/03 - maximum.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "maximum", 3 | "description": "Maximum allowed numerical value", 4 | "content": [ 5 | ], 6 | "exampleSchema": { 7 | "type": "number", 8 | "maximum": 10 9 | }, 10 | "exampleData": 5 11 | } 12 | -------------------------------------------------------------------------------- /blog/schemas/index-administrator.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Administrator", 3 | "allOf": [{"$ref": "index.json"}], 4 | "links": [ 5 | { 6 | "rel": "create", 7 | "href": "?", 8 | "method": "POST", 9 | "schema": {"$ref": "post.json#/definitions/submitPost"} 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /demos/hints/facebook/objects/links/like.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Like target", 3 | "properties": { 4 | }, 5 | "links": [ 6 | { 7 | "rel": "full", 8 | "href": "/{id}" 9 | }, 10 | { 11 | "href": "/{id}/likes", 12 | "rel": "unlike", 13 | "method": "DELETE" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /documentation/json-schema/keywords/String validation/02- minLength.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "minLength", 3 | "description": "Minimum allowed string length", 4 | "content": [ 5 | ], 6 | "exampleSchema": { 7 | "type": "string", 8 | "minLength":5 9 | }, 10 | "exampleData": "string value" 11 | } 12 | -------------------------------------------------------------------------------- /documentation/json-schema/keywords/String validation/01 - maxLength.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "maxLength", 3 | "description": "Maximum allowed string length", 4 | "content": [ 5 | ], 6 | "exampleSchema": { 7 | "type": "string", 8 | "maxLength": 20 9 | }, 10 | "exampleData": "string value" 11 | } 12 | -------------------------------------------------------------------------------- /jsonary/_header.js: -------------------------------------------------------------------------------- 1 | (function(publicApi) { // Global wrapper 2 | 3 | var Jsonary = publicApi; 4 | 5 | publicApi.toString = function() { 6 | return ""; 7 | }; 8 | publicApi.plugins = {}; 9 | 10 | function setTimeout(fn, t) { 11 | throw new Error("setTimeout() should not be used"); 12 | } 13 | 14 | -------------------------------------------------------------------------------- /demos/hints/facebook/objects/links/photo.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Photo", 3 | "type": "string", 4 | "links": [ 5 | { 6 | "rel": "full", 7 | "href": "/{@}", 8 | "targetSchema": {"$ref": "../photo.json"} 9 | }, 10 | { 11 | "href": "/{@}/picture", 12 | "rel": "picture" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /documentation/json-schema/keywords/Numeric validation/05 - divisibleBy.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "divisibleBy", 3 | "description": "Numeric values must be a multiple of this factor", 4 | "content": [ 5 | ], 6 | "exampleSchema": { 7 | "type": "number", 8 | "divisibleBy": 2 9 | }, 10 | "exampleData": 4 11 | } 12 | -------------------------------------------------------------------------------- /demos/hints/facebook/objects/links/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "User", 3 | "properties": { 4 | }, 5 | "links": [ 6 | { 7 | "rel": "full", 8 | "href": "/{id}", 9 | "targetSchema": {"$ref": "../user.json"} 10 | }, 11 | { 12 | "rel": "picture", 13 | "href": "/{id}/picture" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /blog/config.php: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /demos/schemas/simple-post.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "A simple post", 3 | "type": "object", 4 | "properties": { 5 | "title": {"type": "string"}, 6 | "content": {"type": "string"}, 7 | "mood": { 8 | "enum": ["happy", "neutral", "grumpy"], 9 | "default": "neutral" 10 | } 11 | }, 12 | "required": ["title", "content"] 13 | } -------------------------------------------------------------------------------- /demos/hints/facebook/old/post.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Post", 3 | "properties": { 4 | "from": {"$ref": "userlink.json"}, 5 | "to": { 6 | "items": {"$ref": "userlink.json"} 7 | } 8 | }, 9 | "links": [ 10 | { 11 | "href": "{picture}", 12 | "rel": "image" 13 | } 14 | ], 15 | "extends": {"$ref": "item-common.json"} 16 | } 17 | -------------------------------------------------------------------------------- /documentation/json-schema/keywords/Array validation/03 - minItems.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "minItems", 3 | "description": "minimum length of an array", 4 | "content": [ 5 | ], 6 | "exampleSchema": { 7 | "type": "array", 8 | "minItems": 3, 9 | "items": { 10 | "title": "Array item", 11 | "type": "integer" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /documentation/json-schema/keywords/Array validation/04 - maxItems.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "maxItems", 3 | "description": "maximum length of an array", 4 | "content": [ 5 | ], 6 | "exampleSchema": { 7 | "type": "array", 8 | "maxItems": 5, 9 | "items": { 10 | "title": "Array item", 11 | "type": "integer" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /demos/hints/facebook/old/item-guess.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Take a guess", 3 | "type": [ 4 | {"$ref": "user.json"}, 5 | { 6 | "title": "Results list (guess)", 7 | "extends": {"$ref": "result-array.json"}, 8 | "properties": { 9 | "data": { 10 | "items": {"$ref": "item-guess.json"} 11 | } 12 | } 13 | } 14 | ] 15 | } 16 | 17 | -------------------------------------------------------------------------------- /demos/css/page.index.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #F8F8F0; 3 | padding-bottom: 160px; 4 | } 5 | 6 | #url-bar { 7 | width: 80%; 8 | float: right; 9 | } 10 | 11 | #main { 12 | } 13 | 14 | .loading { 15 | background-image: url('loading.gif'); 16 | background-position: bottom; 17 | background-repeat: no-repeat; 18 | padding-bottom: 32px; 19 | } 20 | -------------------------------------------------------------------------------- /demos/js/facebook-credentials.js: -------------------------------------------------------------------------------- 1 | // Facebook auth 2 | var facebookAppId = 219375971524697; 3 | var facebookRedirectUri = "http://jsonary.com/demos/facebook.html"; 4 | var facebookPermissions = ["user_about_me", "user_relationships", "friends_about_me", "user_status", "friends_status", "read_stream", "publish_stream", "user_photos", "friends_photos", "user_likes", "friends_likes"]; 5 | 6 | -------------------------------------------------------------------------------- /demos/q-a/schema-question.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Quiz question", 3 | "type": "object", 4 | "properties": { 5 | "id": {"type": "integer"}, 6 | "question": {"type": "string"}, 7 | "options": { 8 | "type": "array", 9 | "items": {"type": "string"} 10 | }, 11 | "answerSchema": {"$ref": "http://json-schema.org/hyper-schema"} 12 | }, 13 | "required": ["id", "question"] 14 | } 15 | -------------------------------------------------------------------------------- /documentation/json-schema/keywords/General keywords/01 - enum.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "enum", 3 | "description": "limits the values an instance can take", 4 | "content": [ 5 | "The value of this property must be an array. Instances must be exactly equal to one of the values in the array." 6 | ], 7 | "exampleSchema": { 8 | "enum": [null, "JSON", "XML"], 9 | "default": "JSON" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /renderers/contributed/image-picker.css: -------------------------------------------------------------------------------- 1 | .base64-image-preview { 2 | width: 100px; 3 | height: 100px; 4 | overflow: hidden; 5 | text-align: center; 6 | padding: 2px; 7 | } 8 | 9 | .base64-image-preview img { 10 | max-height: 100%; 11 | max-width: 100%; 12 | vertical-align: middle; 13 | border: 1px solid #444; 14 | border-radius: 2px; 15 | box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.2); 16 | } -------------------------------------------------------------------------------- /demos/js/facebook-credentials-template.js: -------------------------------------------------------------------------------- 1 | // Facebook auth 2 | var facebookAppId = /* App ID, e.g. 1234567890 */ 3 | var facebookRedirectUri = /* URL where the Facebook demo page is held */ 4 | var facebookPermissions = ["user_about_me", "user_relationships", "friends_about_me", "user_status", "friends_status", "read_stream", "publish_stream", "user_photos", "friends_photos", "user_likes", "friends_likes"]; 5 | 6 | -------------------------------------------------------------------------------- /tests/tests/06 - Live data and requests/05 - data URLs.js: -------------------------------------------------------------------------------- 1 | tests.add("percent-encoded data URL", function () { 2 | var thisTest = this; 3 | 4 | Jsonary.getData("data:application/json,%22Hello!%22", function (data) { 5 | thisTest.assert(data.value() === "Hello!", 'Value mismatch: ' + data.json()); 6 | thisTest.pass(); 7 | }); 8 | 9 | setTimeout(function () { 10 | thisTest.fail('timeout'); 11 | }, 100); 12 | }); -------------------------------------------------------------------------------- /documentation/json-schema/keywords/String validation/03 - pattern.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "pattern", 3 | "description": "A regular expression (ECMA 262) that a string value must match", 4 | "content": [ 5 | "Because of variations in regular expression implementations between languages, if you expect to be validating in more than one language, it's probably best to stick to the basic features (for instance, no lookaheads)." 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /demos/hints/facebook/actions/like.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Likeable", 3 | "links": [ 4 | { 5 | "href": "/{id}/likes", 6 | "rel": "like", 7 | "method": "POST", 8 | "encType": "application/x-www-form-urlencoded", 9 | "schema": { 10 | "type": "object", 11 | "additionalProperties": false 12 | } 13 | }, 14 | { 15 | "href": "/{id}/like", 16 | "rel": "unlike", 17 | "method": "DELETE" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /documentation/json-schema/keywords/Numeric validation/02 - exclusiveMinimum.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "exclusiveMinimum", 3 | "description": "Whether numerical values are allowed to exactly equal the minimum", 4 | "content": [ 5 | "If this property is present, \"minimum\" must also be present." 6 | ], 7 | "exampleSchema": { 8 | "type": "number", 9 | "minimum": 0, 10 | "exclusiveMinimum": true 11 | }, 12 | "exampleData": 1 13 | } 14 | -------------------------------------------------------------------------------- /documentation/json-schema/keywords/Numeric validation/04- exclusiveMaximum.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "exclusiveMaximum", 3 | "description": "Whether numerical values are allowed to exactly equal the maximum", 4 | "content": [ 5 | "If this property is present, \"maximum\" must also be present." 6 | ], 7 | "exampleSchema": { 8 | "type": "number", 9 | "maximum": 10, 10 | "exclusiveMaximum": true 11 | }, 12 | "exampleData": 9.99 13 | } 14 | -------------------------------------------------------------------------------- /demos/hints/facebook/old/result-array.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Results list", 3 | "properties": { 4 | "data": { 5 | }, 6 | "paging": { 7 | "links": [ 8 | { 9 | "href": "{previous}", 10 | "rel": "prev", 11 | "targetSchema": {"$ref": "#"} 12 | }, 13 | { 14 | "href": "{next}", 15 | "rel": "next", 16 | "targetSchema": {"$ref": "#"} 17 | } 18 | ] 19 | } 20 | }, 21 | "required": ["data"] 22 | } 23 | -------------------------------------------------------------------------------- /demos/hints/facebook/me.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Current User", 3 | "links": [ 4 | { 5 | "rel": "home", 6 | "href": "/me/home", 7 | "targetSchema": {"$ref": "arrays/posts.json"} 8 | }, 9 | { 10 | "rel": "likes", 11 | "href": "/me/likes", 12 | "targetSchema": {"$ref": "arrays/likes.json"} 13 | } 14 | ], 15 | "extends": [{"$ref": "objects/user.json"}, {"$ref": "actions/post.json"}, {"$ref": "../actions/upload-photo.json"}] 16 | } 17 | -------------------------------------------------------------------------------- /site/site.json: -------------------------------------------------------------------------------- 1 | {"title":"Jsonary\n","tagLine":"download the [get-started bundle](get-started-bundle.zip) and [read the guide](?page=get-started-guide)","pages":[{"title":"What is Jsonary?","id":"index"},{"title":"Guide to JSON Schema","id":"schema-guide"},{"title":"Get started with Jsonary","id":"get-started-guide"}],"blocks":[{"title":"Info","content":[{"keyValue":[{"key":"License","value":"[MIT](LICENSE.TXT)"},{"key":"Source","value":"[view on GitHub](https:\/\/github.com\/geraintluff\/jsonary)"}]}]}]} -------------------------------------------------------------------------------- /demos/hints/facebook/arrays/posts.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Posts", 3 | "properties": { 4 | "count": {"title": "Count"}, 5 | "data": { 6 | "items": {"$ref": "../objects/post.json"} 7 | }, 8 | "paging": { 9 | "links": [ 10 | { 11 | "href": "{previous}", 12 | "rel": "previous", 13 | "targetSchema": {"$ref": "#"} 14 | }, 15 | { 16 | "href": "{next}", 17 | "rel": "next", 18 | "targetSchema": {"$ref": "#"} 19 | } 20 | ] 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /documentation/json-schema/keywords/Referencing/$ref.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "$ref", 3 | "description": "defines a reference to another JSON Schema", 4 | "content": [ 5 | "If present, this property defines a reference. The URI should be resolved relative to the URI of the current schema, and the result should be used in place of the current schema.", 6 | "As of version 4, the best way to refer to a sub-schema is with a JSON Pointer fragment (e.g. myschema.json#/properties/myKey)." 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /demos/hints/facebook/arrays/albums.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Albums", 3 | "properties": { 4 | "count": {"title": "Count"}, 5 | "data": { 6 | "items": {"$ref": "../objects/album.json"} 7 | }, 8 | "paging": { 9 | "links": [ 10 | { 11 | "href": "{previous}", 12 | "rel": "previous", 13 | "targetSchema": {"$ref": "#"} 14 | }, 15 | { 16 | "href": "{next}", 17 | "rel": "next", 18 | "targetSchema": {"$ref": "#"} 19 | } 20 | ] 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /demos/hints/facebook/arrays/likes.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Likes", 3 | "properties": { 4 | "count": {"title": "Count"}, 5 | "data": { 6 | "items": {"$ref": "../objects/links/like.json"} 7 | }, 8 | "paging": { 9 | "links": [ 10 | { 11 | "href": "{previous}", 12 | "rel": "previous", 13 | "targetSchema": {"$ref": "#"} 14 | }, 15 | { 16 | "href": "{next}", 17 | "rel": "next", 18 | "targetSchema": {"$ref": "#"} 19 | } 20 | ] 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /demos/hints/facebook/arrays/photos.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Photos", 3 | "properties": { 4 | "count": {"title": "Count"}, 5 | "data": { 6 | "items": {"$ref": "../objects/photo.json"} 7 | }, 8 | "paging": { 9 | "links": [ 10 | { 11 | "href": "{previous}", 12 | "rel": "previous", 13 | "targetSchema": {"$ref": "#"} 14 | }, 15 | { 16 | "href": "{next}", 17 | "rel": "next", 18 | "targetSchema": {"$ref": "#"} 19 | } 20 | ] 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /demos/hints/facebook/arrays/comments.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Comments", 3 | "properties": { 4 | "count": {"title": "Count"}, 5 | "data": { 6 | "items": {"$ref": "../objects/comment.json"} 7 | }, 8 | "paging": { 9 | "links": [ 10 | { 11 | "href": "{previous}", 12 | "rel": "previous", 13 | "targetSchema": {"$ref": "#"} 14 | }, 15 | { 16 | "href": "{next}", 17 | "rel": "next", 18 | "targetSchema": {"$ref": "#"} 19 | } 20 | ] 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /demos/hints/facebook/arrays/user-links.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Posts", 3 | "properties": { 4 | "count": {"title": "Count"}, 5 | "data": { 6 | "items": {"$ref": "../objects/links/user.json"} 7 | }, 8 | "paging": { 9 | "links": [ 10 | { 11 | "href": "{previous}", 12 | "rel": "previous", 13 | "targetSchema": {"$ref": "#"} 14 | }, 15 | { 16 | "href": "{next}", 17 | "rel": "next", 18 | "targetSchema": {"$ref": "#"} 19 | } 20 | ] 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /documentation/json-schema/keywords/Meta-data/03 - $schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "$schema", 3 | "description": "The URI of the JSON Schema meta-schema ", 4 | "content": [ 5 | "This can be used to specify a particular version/variant of the JSON Schema specification being used, by using the URI of the meta-schema for that version.", 6 | "The treatment of some properties may depend on the version of the specification being used, so tools can use this value (if present) to choose what behaviour to apply" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /demos/q-a/schema-answer.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Quiz answer", 3 | "type": "object", 4 | "properties": { 5 | "question": { 6 | "type": "integer", 7 | "links": [ 8 | {"rel": "full", "href": "/demos/q-a/questions/{@}.json"} 9 | ] 10 | }, 11 | "answer": {"type": "string"} 12 | }, 13 | "required": ["question", "answer"], 14 | "additionalProperties": false, 15 | "links": [ 16 | { 17 | "rel": "describedby", 18 | "href": "/demos/q-a/questions/{question}.json#/answerSchema" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /documentation/json-schema/keywords/General keywords/08 - extends.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "extends", 3 | "description": "version 3 only - basically equivalent to \"allOf\"", 4 | "content": [ 5 | "This property can either be a schema or a list of schemas, all of which describe the instance. It was renamed to \"allOf\" in version 4, as the name was causing confusion.", 6 | "Jsonary bundles the contents of this property (if present) up with the \"allOf\" property, for its andSchemas() method." 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /documentation/json-schema/keywords/General keywords/09 - disallow.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "disallow", 3 | "description": "version 3 only - old-style schema negation", 4 | "content": [ 5 | "This value is basically the inverse of version 3's \"type\". It can be a simple string (\"object\", \"array\" etc.), or an array of strings and schemas.", 6 | "Jsonary parses this property (along with \"not\") for the result of the notSchemas() method, allowing seamless use of version 3 and version 4 schemas." 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /demos/css/page.facebook.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #F0F0E0; 3 | padding: 0; 4 | margin: 0; 5 | padding-bottom: 160px; 6 | } 7 | 8 | .content-block { 9 | border: 2px solid #888; 10 | background-color: white; 11 | } 12 | 13 | .loading { 14 | background-image: url('loading.gif'); 15 | background-position: bottom; 16 | background-repeat: no-repeat; 17 | padding-bottom: 32px; 18 | } 19 | 20 | #header { 21 | height: 2em; 22 | width: 100%; 23 | background-color: #EEE; 24 | border-bottom: 2px solid #444; 25 | } 26 | -------------------------------------------------------------------------------- /tests/render-tests/01 - Basic tests/01 - Simple asyncRenderHtml.js: -------------------------------------------------------------------------------- 1 | tests.add("Jsonary.asyncRenderHtml read-only boolean", function() { 2 | var thisTest = this; 3 | // Simple read-only boolean 4 | var data = Jsonary.create(true, null, true); 5 | 6 | var expected = 'yes'; 7 | 8 | Jsonary.asyncRenderHtml(data, null, function (error, html) { 9 | thisTest.assert(error == null, 'No error'); 10 | thisTest.assert(html === expected, "html should be expected"); 11 | thisTest.pass(); 12 | }); 13 | }); -------------------------------------------------------------------------------- /blog/schemas/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Blog", 3 | "type": "object", 4 | "properties": { 5 | "posts": { 6 | "type": "array", 7 | "items": {"$ref": "post.json#/definitions/reference"} 8 | }, 9 | "page": { 10 | "type": "integer" 11 | }, 12 | "next": { 13 | "type": "string", 14 | "format": "uri" 15 | }, 16 | "prev": { 17 | "type": "string", 18 | "format": "uri" 19 | } 20 | }, 21 | "links": [ 22 | { 23 | "rel": "prev", 24 | "href": "{+prev}" 25 | }, 26 | { 27 | "rel": "next", 28 | "href": "{+next}" 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "tests/tests/10 - Schema matching (validation)/test-suite"] 2 | path = tests/tests/10 - Schema matching (validation)/test-suite 3 | url = https://github.com/json-schema/JSON-Schema-Test-Suite.git 4 | [submodule "jsonary/uri-templates"] 5 | path = jsonary/uri-templates 6 | url = https://github.com/geraintluff/uri-templates.git 7 | [submodule "node-package"] 8 | path = node-package 9 | url = https://github.com/jsonary-js/jsonary-release.git 10 | [submodule "express-site"] 11 | path = express-site 12 | url = https://github.com/jsonary-js/jsonary-site.git 13 | -------------------------------------------------------------------------------- /tests/tests/01 - Basic tests/08 - Fetching fragments.js: -------------------------------------------------------------------------------- 1 | tests.add("Basic fragment", function () { 2 | var thisTest = this; 3 | var url = "http://example.com/test.json"; 4 | var testData = { 5 | "key": "value" 6 | }; 7 | Jsonary.addToCache(url, testData); 8 | var request = Jsonary.getData(url + "#/key", function (data, req) { 9 | thisTest.assert(data.value() == "value", "Returned data does not match: " + JSON.stringify(data.value()) + " != \"value\""); 10 | thisTest.pass(); 11 | }); 12 | setTimeout(function () { 13 | thisTest.fail("Timeout"); 14 | }, 50); 15 | }); 16 | -------------------------------------------------------------------------------- /tests/tests/04 - Default values and data creation/06 - custom creation functions.js: -------------------------------------------------------------------------------- 1 | tests.add("custom function", function () { 2 | var customFormat = 'test-format-' + Math.random(); 3 | Jsonary.extendCreateValue(function (schemas) { 4 | if (schemas.containsFormat(customFormat)) { 5 | return "Custom string"; 6 | } 7 | }); 8 | var schema1 = Jsonary.createSchema({ 9 | "type": "string", 10 | "format": customFormat 11 | }); 12 | var createdData = schema1.createValue(); 13 | 14 | this.assert(createdData == "Custom string", "value matches"); 15 | return true; 16 | }); 17 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /documentation/json-schema/keywords/Meta-data/04 - default.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "default", 3 | "description": "A default value for any instances following this schema", 4 | "content": [ 5 | "In cases where a default value is required (editing, automated processing), this property provides a value that can be used.", 6 | "Jsonary uses this value when editing instances, to fill out new properties/items with appropriate values." 7 | ], 8 | "exampleSchema": { 9 | "type": "array", 10 | "items": { 11 | "type": "string", 12 | "default": "default item value" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /documentation/json-schema/keywords/General keywords/03 - allOf.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "allOf", 3 | "description": "version 4 only - provides a list of schemas, all of which apply to the instance", 4 | "content": [ 5 | "Jsonary makes these schemas (along with any schemas in \"extends\") available using the andSchemas() method of schemas / schema lists." 6 | ], 7 | "exampleSchema": { 8 | "title": "Container schema", 9 | "type": "string", 10 | "allOf": [ 11 | { 12 | "title": "First sub-schema" 13 | }, 14 | { 15 | "title": "Second sub-schema" 16 | } 17 | ] 18 | }, 19 | "exampleData": "test string" 20 | } 21 | -------------------------------------------------------------------------------- /demos/hints/facebook/objects/comment.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Comment", 3 | "description": "https://developers.facebook.com/docs/reference/api/comment/", 4 | "properties": { 5 | "from": {"$ref": "links/user.json"}, 6 | "cover_photo": {"$ref": "links.photo.json"} 7 | }, 8 | "links": [ 9 | { 10 | "href": "/{id}/likes", 11 | "rel": "likes", 12 | "targetSchema": {"$ref": "../arrays/likes.json"} 13 | }, 14 | { 15 | "href": "/{id}/picture", 16 | "rel": "picture" 17 | }, 18 | { 19 | "href": "{source}", 20 | "rel": "source" 21 | } 22 | ], 23 | "extends": [{"$ref": "common.json"}, {"$ref": "../actions/like.json"}] 24 | } 25 | 26 | -------------------------------------------------------------------------------- /demos/facebook.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Facebook Graph demo 5 | 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /documentation/json-schema/keywords/Referencing/id.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "id", 3 | "description": "defines the URI of the current instance", 4 | "content": [ 5 | "The value of this property is the URI that the schema is claiming to have. The client is not obliged to believe this value.", 6 | "In version 3, this was the only way to refer to sub-schemas (you had to define \"id\": \"#someFragment\" and then reference that using $ref elsewhere).", 7 | "As of version 4, the best way to reference sub-schemas is using a JSON Pointer fragment.", 8 | "Jsonary ignores this property, relying entirely on JSON Pointer fragments." 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /demos/json/view.php: -------------------------------------------------------------------------------- 1 | 5 | { 6 | "type": "object", 7 | "properties": { 8 | "message": { 9 | "type": "string", 10 | "default": "message text" 11 | }, 12 | "dangerRating": { 13 | "type": "integer", 14 | "default": 0 15 | } 16 | } 17 | } 18 | 28 | -------------------------------------------------------------------------------- /demos/hints/facebook/objects/post.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Post", 3 | "description": "https://developers.facebook.com/docs/reference/api/post/", 4 | "properties": { 5 | "from": {"$ref": "links/user.json"}, 6 | "to": {"$ref": "../arrays/user-links.json"} 7 | }, 8 | "links": [ 9 | { 10 | "rel": "comments", 11 | "href": "{id}/comments", 12 | "targetSchema": {"$ref": "../arrays/comments.json"} 13 | }, 14 | { 15 | "rel": "likes", 16 | "href": "{id}/likes", 17 | "targetSchema": {"$ref": "../arrays/likes.json"} 18 | } 19 | ], 20 | "extends": [{"$ref": "common.json"}, {"$ref": "../actions/comment.json"}, {"$ref": "../actions/like.json"}] 21 | } 22 | 23 | -------------------------------------------------------------------------------- /demos/hints/facebook/actions/upload-photo.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Upload photos", 3 | "links": [ 4 | { 5 | "href": "/{id}/photos", 6 | "method": "POST", 7 | "encType": "multipart/form-data", 8 | "schema": { 9 | "type": "object", 10 | "properties": { 11 | "source": { 12 | "title": "Photo", 13 | "type": "string", 14 | "mediaType": "image/*" 15 | }, 16 | "message": { 17 | "title": "Description", 18 | "type": "string", 19 | } 20 | }, 21 | "required": ["source"], 22 | "additionalProperties": false 23 | }, 24 | "targetSchema": {"$ref": "../objects/links/photo.json"} 25 | } 26 | ] 27 | } 28 | 29 | -------------------------------------------------------------------------------- /documentation/json-schema/keywords/Array validation/01 - items.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "items", 3 | "description": "specifies schema(s) for the items in an array", 4 | "content": [ 5 | "If this property is a schema, then all items in the instance must match that schema.", 6 | "If this property is an array, then it must contain schemas. In that case, all items in the instance must be valid according to the schema at the corresponding index - this is called \"tuple typing\". Any surplus items are described by the \"additionalItems\" schema property." 7 | ], 8 | "exampleSchema": { 9 | "type": "array", 10 | "items": { 11 | "title": "Array item", 12 | "type": "boolean" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsonary-dev", 3 | "description": "Jsonary development setup", 4 | "version": "0.0.1", 5 | "private": true, 6 | "devDependencies": { 7 | "grunt": "~0.4.1", 8 | "wrench": "~1.5.1", 9 | "mime": "~1.2.11", 10 | "cookie-client": "0.0.3", 11 | "clean-css": "~1.0.12", 12 | "uglify-js": "~2.3.6" 13 | }, 14 | "scripts": { 15 | "test": "grunt test" 16 | }, 17 | "dependencies": { 18 | "grunt-contrib-compress": "~0.5.2", 19 | "clean-css": "1.0.x", 20 | "uglify-js": "2.3.x", 21 | "mime": "1.2.x", 22 | "cookie-client": "0.0.x", 23 | "express": "3.x", 24 | "my-json": "~0.0.4", 25 | "mysql": "~2.0.0-alpha9" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/tests/09 - Hyper-text/01 - Inline addressing.js: -------------------------------------------------------------------------------- 1 | tests.add("Fragment inline addressing for schema", function () { 2 | var schema = Jsonary.createSchema({ 3 | "title": "Example schema", 4 | "type": "array", 5 | "items": {"$ref": "#arrayItems"}, 6 | "definitions": { 7 | "subSchema": { 8 | "id": "#arrayItems", 9 | "type": "boolean" 10 | } 11 | } 12 | }); 13 | 14 | var data = Jsonary.create([true, false, true]).addSchema(schema); 15 | var subData = data.item(0); 16 | var basicTypes = subData.schemas().types(); 17 | 18 | this.assert(basicTypes.length == 1 && basicTypes[0] == "boolean", "basicTypes should be [boolean], not " + JSON.stringify(basicTypes)); 19 | 20 | return true; 21 | }); -------------------------------------------------------------------------------- /renderers/contributed/tag-list.css: -------------------------------------------------------------------------------- 1 | .json-tag-list { 2 | width: 100%; 3 | overflow: auto; 4 | position: relative; 5 | } 6 | 7 | .json-tag-list-current { 8 | width: 100%; 9 | overflow: auto; 10 | position: relative; 11 | } 12 | 13 | .json-tag-list-entry { 14 | display: block; 15 | float: left; 16 | padding: 1px; 17 | padding-left: 3px; 18 | padding-right: 3px; 19 | background-color: #F0F2F8; 20 | border: 1px solid #CCD; 21 | border-bottom-color: #BBB; 22 | border-top-color: #DDDDE4; 23 | border-radius: 3px; 24 | } 25 | 26 | .json-tag-list-add { 27 | clear: both; 28 | float: left; 29 | border-radius: 4px; 30 | background-color: #EEE; 31 | border-bottom: 1px solid #DDD; 32 | border-top: 1px solid #F8F8F8; 33 | } -------------------------------------------------------------------------------- /tests/tests/06 - Live data and requests/03 - Extra headers.js: -------------------------------------------------------------------------------- 1 | tests.add("Extra headers from request options", function () { 2 | var thisTest = this; 3 | 4 | Jsonary.ajaxFunction = (function (oldFunction) { 5 | return function (params, callback) { 6 | thisTest.assert(params.headers, 'headers defined'); 7 | thisTest.assert(params.headers['x-test'] === 'test-value', '"X-Test" headers set to correct value'); 8 | thisTest.pass(); 9 | return oldFunction.call(null, params, callback); 10 | }; 11 | })(Jsonary.ajaxFunction); 12 | 13 | Jsonary.getData({ 14 | url: "test.json?test=extra_headers_1", 15 | headers: { 16 | "X-Test": "test-value" 17 | } 18 | }); 19 | 20 | setTimeout(function () { 21 | thisTest.fail('timeout'); 22 | }, 100); 23 | }); -------------------------------------------------------------------------------- /demos/hints/facebook/actions/post.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Postable", 3 | "links": [ 4 | { 5 | "href": "/{id}/feed", 6 | "rel": "update status", 7 | "method": "POST", 8 | "encType": "application/x-www-form-urlencoded", 9 | "schema": { 10 | "type": "object", 11 | "properties": { 12 | "message": { 13 | "title": "Status", 14 | "type": "string" 15 | } 16 | }, 17 | "additionalProperties": false, 18 | "default": {"message": "testing some exciting code"} 19 | }, 20 | "targetSchema": { 21 | "title": "Status ID", 22 | "links": [ 23 | { 24 | "rel": "full", 25 | "href": "{@}", 26 | "targetSchema": {"$ref": "../objects/post.json"} 27 | } 28 | ] 29 | } 30 | } 31 | ] 32 | } 33 | 34 | -------------------------------------------------------------------------------- /demos/hints/facebook/actions/comment.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Commentable", 3 | "links": [ 4 | { 5 | "href": "/{id}/comments", 6 | "rel": "add comment", 7 | "method": "POST", 8 | "encType": "application/x-www-form-urlencoded", 9 | "schema": { 10 | "type": "object", 11 | "properties": { 12 | "message": { 13 | "title": "Message", 14 | "type": "string", 15 | "default": "comment" 16 | } 17 | }, 18 | "additionalProperties": false, 19 | "default": {"message": "comment"} 20 | }, 21 | "targetSchema": { 22 | "title": "Comment link", 23 | "links": [ 24 | { 25 | "href": "/{@}", 26 | "rel": "full", 27 | "targetSchema": "../comment.json" 28 | } 29 | ] 30 | } 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /demos/json/update.php: -------------------------------------------------------------------------------- 1 | 5 | 6 | { 7 | "default": , 14 | "extends": { 15 | "$ref": "view.php?schema" 16 | } 17 | } 18 | 19 | 33 | 34 | -------------------------------------------------------------------------------- /tests/tests/06 - Live data and requests/04 - beforeAjax.js: -------------------------------------------------------------------------------- 1 | tests.add("Jsonary.beforeAjax(callback)", function () { 2 | var thisTest = this; 3 | 4 | Jsonary.beforeAjax(function (params) { 5 | params.headers['x-test-2'] = ':)'; 6 | }); 7 | Jsonary.ajaxFunction = (function (oldFunction) { 8 | return function (params, callback) { 9 | thisTest.assert(params.headers, 'headers defined'); 10 | thisTest.assert(params.headers['x-test-2'] === ':)', '"X-Test-2" headers set to correct value'); 11 | thisTest.pass(); 12 | return oldFunction.call(null, params, callback); 13 | }; 14 | })(Jsonary.ajaxFunction); 15 | 16 | Jsonary.getData({ 17 | url: "test.json?test=extra_headers_1" 18 | }); 19 | 20 | setTimeout(function () { 21 | thisTest.fail('timeout'); 22 | }, 100); 23 | }); -------------------------------------------------------------------------------- /demos/hints/facebook/objects/photo.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Photo", 3 | "description": "https://developers.facebook.com/docs/reference/api/photo/", 4 | "properties": { 5 | "from": {"$ref": "links/user.json"}, 6 | "cover_photo": {"$ref": "links.photo.json"} 7 | }, 8 | "links": [ 9 | { 10 | "href": "/{id}/likes", 11 | "rel": "likes", 12 | "targetSchema": {"$ref": "../arrays/likes.json"} 13 | }, 14 | { 15 | "href": "/{id}/comments", 16 | "rel": "comments", 17 | "targetSchema": {"$ref": "../arrays/comments.json"} 18 | }, 19 | { 20 | "href": "/{id}/picture", 21 | "rel": "picture" 22 | }, 23 | { 24 | "href": "{source}", 25 | "rel": "source" 26 | } 27 | ], 28 | "extends": [{"$ref": "common.json"}, {"$ref": "../actions/comment.json"}, {"$ref": "../actions/like.json"}] 29 | } 30 | 31 | -------------------------------------------------------------------------------- /tests/tests/.meta-tests/Timer test.js: -------------------------------------------------------------------------------- 1 | tests.add("Succeed instantly", function() { 2 | var thisTest = this; 3 | return true; 4 | }); 5 | tests.add("Fail after 1500ms", function() { 6 | var thisTest = this; 7 | setTimeout(function() { 8 | thisTest.fail(); 9 | }, 1500); 10 | }); 11 | tests.add("Pass after 1000ms", function() { 12 | var thisTest = this; 13 | setTimeout(function() { 14 | thisTest.pass(); 15 | }, 1000); 16 | }); 17 | tests.add("Fail with message \"Comparisons\" after 1000ms", function() { 18 | var thisTest = this; 19 | setTimeout(function() { 20 | thisTest.assert(1 > 2, "Comparisons"); 21 | thisTest.pass(); 22 | }, 1000); 23 | }); 24 | 25 | -------------------------------------------------------------------------------- /documentation/json-schema/keywords/General keywords/07 - format.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "format", 3 | "description": "semantic format of the instance value", 4 | "content": [ 5 | "This property is a string, that provides more information about the specify type of data the instance represents.", 6 | "The specification provides some example values:", 7 | [ 8 | "date-time", 9 | "email", 10 | "hostname", 11 | "ipv4", 12 | "ipv6", 13 | "uri" 14 | ], 15 | "Any string value is allowed, though, and version 3 provided more example values.", 16 | "Validation is not guaranteed to use this keyword - it is therefore unwise to use this keyword inside \"not\", \"oneOf\" or \"anyOf\" clauses, as behaviour could be inconsitent across environments.", 17 | "Jsonary does not use this keyword for validation." 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /documentation/json-schema/keywords/Array validation/02 - additionalItems.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "additionalItems", 3 | "description": "describes extra items, if \"tuple typing\" is being used.", 4 | "content": [ 5 | "If the value of \"items\" is an array (\"tuple typing\"), and if the instance is longer than the list of schemas in \"items\", then the additional items are described by the schema in this property.", 6 | "If this value is false, then the instance cannot be longer than the list of schemas in \"items\". If this value is \"true\", that is equivalent to the empty schema (anything goes)." 7 | ], 8 | "exampleSchema": { 9 | "type": "array", 10 | "items": [ 11 | {"type": "boolean"}, 12 | {"type": "integer"}, 13 | {"type": "string"} 14 | ], 15 | "additionalItems": {"type": "boolean", "default": false} 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/render-tests/01 - Basic tests/02 - custom renderers.js: -------------------------------------------------------------------------------- 1 | tests.add("Custom renderer (for specific value)", function() { 2 | var thisTest = this; 3 | // Simple read-only boolean 4 | var value = "foo bar baz"; 5 | var data = Jsonary.create(value, null, true); 6 | 7 | Jsonary.render.register({ 8 | renderHtml: function (data, context) { 9 | return Jsonary.escapeHtml(data.value().toUpperCase()); 10 | }, 11 | filter: { 12 | type: 'string', 13 | filter: function (data, schemas) { 14 | return data.value() === value; 15 | } 16 | } 17 | }); 18 | 19 | var expectedContains = "FOO BAR BAZ"; 20 | 21 | Jsonary.asyncRenderHtml(data, null, function (error, html) { 22 | thisTest.assert(error == null, 'No error'); 23 | thisTest.assert(html.indexOf(expectedContains) !== -1, "Contains expected string"); 24 | thisTest.pass(); 25 | }); 26 | }); -------------------------------------------------------------------------------- /demos/css/facebook.render.jsonary.css: -------------------------------------------------------------------------------- 1 | .facebook-links { 2 | background-color: #FFF; 3 | } 4 | 5 | .facebook-link { 6 | display: inline; 7 | font-family: sans-serif; 8 | padding-left: 1em; 9 | padding-right: 1em; 10 | color: #08F; 11 | text-decoration: none; 12 | } 13 | 14 | .facebook-link:hover { 15 | color: #1BF; 16 | text-decoration: underline; 17 | } 18 | 19 | .facebook-item { 20 | border: 1px solid black; 21 | } 22 | 23 | .facebook-picture { 24 | border: 1px solid #000; 25 | border-radius: 10px; 26 | vertical-align: middle; 27 | } 28 | 29 | .facebook-user-small { 30 | border: 1px solid black; 31 | border-radius: 10px; 32 | background-color: white; 33 | } 34 | 35 | .facebook-post { 36 | border: 1px solid black; 37 | border-radius: 10px; 38 | background-color: white; 39 | margin: 10px; 40 | padding: 10px; 41 | } 42 | 43 | .facebook-post-date { 44 | font-size: 0.8em; 45 | } 46 | -------------------------------------------------------------------------------- /tests/tests/01 - Basic tests/01 - Existence.js: -------------------------------------------------------------------------------- 1 | tests.add("API exists", function() { 2 | return Jsonary !== undefined; 3 | }); 4 | 5 | tests.add("create() exists", function() { 6 | return Jsonary.create !== undefined; 7 | }); 8 | 9 | tests.add("createSchema() exists", function() { 10 | return Jsonary.createSchema !== undefined; 11 | }); 12 | 13 | tests.add("keyIsVariant", function() { 14 | this.assert(Jsonary.keyIsVariant("2", "2") == true, "2 is a variant of 2"); 15 | this.assert(Jsonary.keyIsVariant("2.1", "2") == true, "2.1 is a variant of 2"); 16 | this.assert(Jsonary.keyIsVariant("2", "2.1") == false, "2 is not a variant of 2.1"); 17 | this.assert(Jsonary.keyIsVariant("2", "3") == false, "2 is not a variant of 3"); 18 | return true; 19 | }); 20 | 21 | tests.add("config", function() { 22 | this.assert(Jsonary.config !== undefined); 23 | return true; 24 | }); 25 | 26 | -------------------------------------------------------------------------------- /demos/hints/facebook/objects/album.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Album", 3 | "description": "https://developers.facebook.com/docs/reference/api/album/", 4 | "properties": { 5 | "from": {"$ref": "links/user.json"}, 6 | "cover_photo": {"$ref": "links/photo.json"} 7 | }, 8 | "links": [ 9 | { 10 | "href": "/{id}/photos", 11 | "rel": "photos", 12 | "targetSchema": {"$ref": "../arrays/photos.json"} 13 | }, 14 | { 15 | "href": "/{id}/likes", 16 | "rel": "likes", 17 | "targetSchema": {"$ref": "../arrays/likes.json"} 18 | }, 19 | { 20 | "href": "/{id}/comments", 21 | "rel": "comments", 22 | "targetSchema": {"$ref": "../arrays/comments.json"} 23 | }, 24 | { 25 | "href": "/{id}/picture", 26 | "rel": "picture" 27 | } 28 | ], 29 | "extends": [{"$ref": "common.json"}, {"$ref": "../actions/comment.json"}, {"$ref": "../actions/like.json"}, {"$ref": "../actions/upload-photo.json"}] 30 | } 31 | -------------------------------------------------------------------------------- /site/schemas/site.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Website", 3 | "type": "object", 4 | "properties": { 5 | "title": {"type": "string"}, 6 | "tagLine": { 7 | "type": "string", 8 | "format": "markdown" 9 | }, 10 | "blocks": { 11 | "type": "array", 12 | "items": {"$ref": "#/definitions/info-block"} 13 | }, 14 | "pages": { 15 | "type": "array", 16 | "items": {"$ref": "#/definitions/page-link"} 17 | } 18 | }, 19 | "required": ["title", "blocks"], 20 | "definitions": { 21 | "info-block": { 22 | "allOf": [{"$ref": "page.json#/definitions/block"}] 23 | }, 24 | "page-link": { 25 | "type": "object", 26 | "properties": { 27 | "title": {"type": "string"}, 28 | "id": {"type": "string"} 29 | }, 30 | "required": ["title", "id"], 31 | "additionalProperties": false, 32 | "links": [ 33 | { 34 | "rel": "full", 35 | "href": "pages/{+id}" 36 | } 37 | ] 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /documentation/json-schema/keywords/Object validation/01 - properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "properties", 3 | "description": "specifies schema(s) for the object properties", 4 | "content": [ 5 | "The value of this property must be an object, containing schemas.", 6 | "If an instance has a property defined, and the \"property\" object contains a corresponding entry, then the value of the instance's property is defined by the corresponding sub-schema.", 7 | "Jsonary uses the properties defined in this object (as well as the \"required\" keyword) to provide a requiredProperties() function, which is used by the default renderers to provide hints about which properties to add." 8 | ], 9 | "exampleSchema": { 10 | "type": "object", 11 | "properties": { 12 | "firstName": { 13 | "title": "First name", 14 | "type": "string" 15 | }, 16 | "lastName": { 17 | "title": "Last name", 18 | "type": "string" 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012-2013 Geraint Luff 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /documentation/json-schema/keywords/Object validation/02 - patternProperties.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "patternProperties", 3 | "description": "describes schemas for properties, matching keys using regular expressions", 4 | "content": [ 5 | "The keys of the \"patternProperties\" objects are used as regular expressions (ECMA 262). Each property key in the instance is matched against these regular expressions, and for any that match, the corresponding instance property is described by the corresponding schema.", 6 | "If a key matches more than one of these regular expressions, or it matches a regular expression in addition to matching a key in \"properties\", then all the relevant sub-schemas apply." 7 | ], 8 | "exampleSchema": { 9 | "type": "object", 10 | "properties": { 11 | "firstName": { 12 | "title": "First name" 13 | } 14 | }, 15 | "patternProperties": { 16 | "[nN]ame": { 17 | "title": "Name", 18 | "type": "string" 19 | } 20 | } 21 | }, 22 | "exampleData": { 23 | "firstName": "Json", 24 | "lastName": "Schema" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /demos/json/index.php: -------------------------------------------------------------------------------- 1 | 6 | { 7 | "properties": { 8 | "Title": { 9 | "title": "Title" 10 | } 11 | }, 12 | "links": [ 13 | { 14 | "href": "update.php", 15 | "rel": "update", 16 | "method": "POST", 17 | "schema": { 18 | "$ref": "update.php?schema" 19 | } 20 | }, 21 | { 22 | "href": "view.php", 23 | "rel": "data" 24 | } 25 | ] 26 | } 27 | 32 | { 33 | "Title": "The main page", 34 | "Explanation": "A basic test of Jsonary's ability to adapt to data/hyper-schemas supplied by the server.\n\nWhen you click the \"update\" link, you are prompted to enter data (according to a schema supplied by the server), which is then POSTed back and stored." 35 | } 36 | 39 | -------------------------------------------------------------------------------- /blog/login.php: -------------------------------------------------------------------------------- 1 | 20 | { 21 | "title": "Login page", 22 | "links": [ 23 | {"rel": "login", "href": "{+login}", "method": "POST", "schema":{"type": "string"}}, 24 | {"rel": "logout", "href": "{+logout}"}, 25 | {"rel": "home", "href": "{+home}"} 26 | ] 27 | } 28 | 35 | { 36 | "login": "?login" 37 | } 38 | 39 | -------------------------------------------------------------------------------- /tests/todo.html: -------------------------------------------------------------------------------- 1 | 2 | JsonApi framework: (53) 3 | 13 | 14 | JsonRenderer: (63) 15 | 25 | 26 | Json Storage: (50) 27 | 37 | -------------------------------------------------------------------------------- /renderers/contributed/validation.js: -------------------------------------------------------------------------------- 1 | // Uses (if exists) a "validation" property on the root object that matches the output of tv4 (https://github.com/geraintluff/tv4) 2 | Jsonary.render.Components.add('VALIDATION'); 3 | Jsonary.render.register({ 4 | component: Jsonary.render.Components.VALIDATION, 5 | renderHtml: function (data, context) { 6 | var result = ""; 7 | var rootData = data.document.root; 8 | if (rootData.validation) { 9 | var errors = rootData.validation.errors; 10 | if (errors) { 11 | for (var i = 0; i < errors.length; i++) { 12 | if (errors[i].dataPath == data.pointerPath()) { 13 | result += "" + errors[i].message + ""; 14 | //mark i entry in errors as rendered, so that we can display not rendered errors somewhere else 15 | errors[i].rendered = true; 16 | 17 | } 18 | } 19 | } 20 | } 21 | return context.renderHtml(data) + result; 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /demos/js/page.index-edit.js: -------------------------------------------------------------------------------- 1 | var currentUrl = "" 2 | var interval = setInterval(function() { 3 | var hash = window.location.hash.substring(1); 4 | if (hash != currentUrl) { 5 | navigateTo(hash); 6 | } 7 | }, 100); 8 | 9 | $('#go').click(function () { 10 | var itemUrl = $('#url-bar').val(); 11 | navigateTo(itemUrl); 12 | }); 13 | 14 | function navigateTo(itemUrl, req) { 15 | currentUrl = itemUrl; 16 | window.location = "#" + itemUrl; 17 | $('#url-bar').val(itemUrl); 18 | 19 | if (req == undefined) { 20 | req = Jsonary.getData(itemUrl); 21 | } 22 | $('#main').empty().addClass("loading"); 23 | req.getData(function(data, request) { 24 | $('#main').removeClass("loading").empty().renderJson(data.editableCopy()); 25 | }); 26 | } 27 | 28 | Jsonary.addLinkHandler(function(link, data, request) { 29 | navigateTo(link.href, request); 30 | return true; 31 | }); 32 | 33 | Jsonary.getSchema("json/?schema", function(schema) { 34 | var updateLink = schema.getLink("update"); 35 | updateLink.addHandler(function () { 36 | Jsonary.invalidate("json/view.php"); 37 | Jsonary.invalidate("json/update.php?schema"); 38 | }); 39 | }); 40 | 41 | -------------------------------------------------------------------------------- /documentation/json-schema/keywords/General keywords/02 - type.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "type", 3 | "description": "defines the basic type(s) that instances can take", 4 | "content": [ 5 | "This value can be either a string or an array of strings.", 6 | "The possible string values are:", 7 | [ 8 | "object", 9 | "array", 10 | "string", 11 | "number", 12 | "integer (all integers are also numbers)", 13 | "boolean", 14 | "null", 15 | "any (indicates the instance can be any of the above types)" 16 | ], 17 | "Custom-defined string values are possible (according to the standard), but they are ignored.", 18 | "If the value of \"type\" is an array, version 3 of the draft allows it to also contain schemas. This has been removed for version 4, in favour of the \"anyOf\" property.", 19 | "The default (basic) renderer set for Jsonary provides a default type-switcher - this is represented by a \"T\" next to the data." 20 | ], 21 | "exampleSchema": { 22 | "type": ["integer", "boolean", "null"] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /demos/js/page.index.js: -------------------------------------------------------------------------------- 1 | var currentUrl = "" 2 | var interval = setInterval(function() { 3 | var hash = window.location.hash.substring(1); 4 | if (hash != currentUrl) { 5 | navigateTo(hash); 6 | } 7 | }, 100); 8 | 9 | $('#go').click(function () { 10 | var itemUrl = $('#url-bar').val(); 11 | navigateTo(itemUrl); 12 | }); 13 | 14 | function navigateTo(itemUrl, req) { 15 | currentUrl = itemUrl; 16 | window.location = "#" + itemUrl; 17 | $('#url-bar').val(itemUrl); 18 | 19 | if (req == undefined) { 20 | req = Jsonary.getData(itemUrl); 21 | } 22 | $('#main').empty().addClass("loading"); 23 | window.scrollTo(0, 0); 24 | req.getData(function(data, request) { 25 | $('#main').removeClass("loading").empty().renderJson(data); 26 | }); 27 | } 28 | 29 | Jsonary.addLinkHandler(function(link, data, request) { 30 | navigateTo(link.href, request); 31 | return true; 32 | }); 33 | 34 | Jsonary.getSchema("json/?schema", function(schema) { 35 | var updateLink = schema.getLink("update"); 36 | updateLink.addHandler(function () { 37 | Jsonary.invalidate("json/view.php"); 38 | Jsonary.invalidate("json/update.php?schema"); 39 | }); 40 | }); 41 | 42 | -------------------------------------------------------------------------------- /documentation/json-schema/keywords/General keywords/04 - anyOf.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "anyOf", 3 | "description": "version 4 only - provides a list of schemas, at least one of which applies to the instance", 4 | "content": [ 5 | "Any schema in the list that matches the current instance also describes that instance. If this property is defined in the schema, the instance is only considered valid with regards to the containing schema if it is valid with regards to at least one of the schemas in the list.", 6 | "Jsonary makes this list of schemas available using the orSchemas() method of schemas / schema lists.", 7 | "The example below uses \"anyOf\" to specify that at least one of \"A\" and \"B\" must be specified." 8 | ], 9 | "exampleSchema": { 10 | "type": "object", 11 | "properties": { 12 | "A": {"type": "boolean"}, 13 | "B": {"type": "boolean"} 14 | }, 15 | "additionalProperties": false, 16 | "anyOf": [ 17 | { 18 | "title": "A required", 19 | "required": ["A"] 20 | }, 21 | { 22 | "title": "B required", 23 | "required": ["B"] 24 | } 25 | ] 26 | }, 27 | "exampleData": {"A": true} 28 | } 29 | -------------------------------------------------------------------------------- /documentation/json-schema/keywords/Object validation/04 - required.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "required", 3 | "description": "specifies which properties must exist in an instance object", 4 | "content": [ 5 | "In the following example, it is not possible to delete \"firstName\" or \"lastName\", although \"middleName\" is still optional.", 6 | "In version 3 of the draft, \"required\" was a boolean flag that was set in the child schema (the schema inside \"properties\") to indicate that the property was non-optional.", 7 | "In version 4 of the draft, \"required\" is an array of strings in the parent schema, specifying which properties the item must have.", 8 | "Jsonary can deal with either form interchangeably." 9 | ], 10 | "exampleSchema": { 11 | "type": "object", 12 | "properties": { 13 | "firstName": { 14 | "type": "string" 15 | }, 16 | "middleName": { 17 | "type": "string" 18 | }, 19 | "lastName": { 20 | "type": "string" 21 | } 22 | }, 23 | "required": ["firstName", "lastName"], 24 | "additionalProperties": false 25 | }, 26 | "exampleData": { 27 | "firstName": "Dwayne", 28 | "lastName": "Johnson", 29 | "middleName": "Douglas" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /documentation/json-schema/keywords/Object validation/03 - additionalProperties.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "additionalProperties", 3 | "description": "describes properties not accounted for by the \"properties\" or \"patternProperties\" keywords", 4 | "content": [ 5 | "The value of this property must be either a schema or a boolean.", 6 | "If this value is boolean false, then properties other than those listed in \"properties\" or \"patternProperties\" are not allowed.", 7 | "If this value is a schema, then any properties that are not described by the \"properties\" or \"patternProperties\" keywords are described by that schema.", 8 | "If this value is not specified (or is boolean true, then additional properties can contain anything." 9 | ], 10 | "exampleSchema": { 11 | "type": "object", 12 | "properties": { 13 | "firstName": { 14 | "title": "First name", 15 | "type": "string" 16 | }, 17 | "lastName": { 18 | "title": "Last name", 19 | "type": "string" 20 | } 21 | }, 22 | "additionalProperties": { 23 | "type": "boolean" 24 | } 25 | }, 26 | "exampleData": { 27 | "firstName": "JSON", 28 | "lastName": "Schema" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/tests/05 - Tertiary tests/01 - Numbers.js: -------------------------------------------------------------------------------- 1 | tests.add("numberInterval()", function() { 2 | var schema1 = Jsonary.createSchema({ 3 | divisibleBy: 1.5 4 | }); 5 | var schema2 = Jsonary.createSchema({ 6 | type: "integer" 7 | }); 8 | var schema3 = Jsonary.createSchema({ 9 | divisibleBy: -2 10 | }); 11 | var schema4 = Jsonary.createSchema({ 12 | divisibleBy: 0 13 | }); 14 | var data = Jsonary.create(6); 15 | 16 | var numberInterval = data.schemas().numberInterval(); 17 | this.assert(numberInterval == null, "numberInterval == null"); 18 | 19 | data.addSchema(schema1); 20 | var numberInterval = data.schemas().numberInterval(); 21 | this.assert(numberInterval == 1.5, "numberInterval == 1.5"); 22 | 23 | data.addSchema(schema2); 24 | var numberInterval = data.schemas().numberInterval(); 25 | this.assert(numberInterval == 3, "numberInterval == 3"); 26 | 27 | data.addSchema(schema3); 28 | var numberInterval = data.schemas().numberInterval(); 29 | this.assert(numberInterval == 6, "numberInterval == 6"); 30 | 31 | data.addSchema(schema4); 32 | var numberInterval = data.schemas().numberInterval(); 33 | this.assert(isNaN(numberInterval), "numberInterval should be NaN"); 34 | return true; 35 | }); 36 | 37 | -------------------------------------------------------------------------------- /renderers/contributed/full-preview.js: -------------------------------------------------------------------------------- 1 | Jsonary.render.register({ 2 | component: [Jsonary.render.Components.LIST_LINKS], 3 | renderHtml: function (data, context) { 4 | var previewLink = data.getLink('preview') || data.getLink('full-preview'); 5 | var innerHtml = context.renderHtml(previewLink.follow(null, false)); 6 | return context.actionHtml(innerHtml, 'full'); 7 | }, 8 | action: function (context, actionName) { 9 | var data = context.data; 10 | if (actionName == 'full') { 11 | var fullLink = data.getLink('full'); 12 | fullLink.follow(); 13 | } 14 | }, 15 | filter: function (data, schemas) { 16 | return data.readOnly() && data.getLink('full') && (data.getLink('preview') || data.getLink('full-preview')); 17 | } 18 | }); 19 | 20 | Jsonary.render.register({ 21 | component: [Jsonary.render.Components.LIST_LINKS], 22 | renderHtml: function (data, context) { 23 | var previewLink = data.getLink('preview') || data.getLink('full-preview'); 24 | return context.renderHtml(data) + " - " + context.renderHtml(previewLink.follow(null, false)); 25 | }, 26 | filter: function (data, schemas) { 27 | return !data.readOnly() && data.getLink('full') && (data.getLink('preview') || data.getLink('full-preview')); 28 | } 29 | }); -------------------------------------------------------------------------------- /api/jsonary-document.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Jsonary document", 3 | "description": "All the Jsonary data wrappers belong to a document.\n\nHowever, you should rarely have to deal with this object. Most of your interactions should be with Jsonary data wrappers, or requests.", 4 | "type": "object", 5 | "properties": { 6 | "getRoot": { 7 | "description": "Registers a callback to call when the root of the document is available.", 8 | "arguments": [ 9 | { 10 | "title": "callback", 11 | "arguments": [ 12 | {"title": "data", "$ref": "jsonary-data.json"} 13 | ] 14 | } 15 | ] 16 | }, 17 | "raw": { 18 | "description": "The data for this document.\n\nIf a link is present with relation \"root\", then this is NOT the same as the root of the document. If you need the root of the document, use getRoot().", 19 | "allOf": [{"$ref": "jsonary-data.json"}] 20 | }, 21 | "root": { 22 | "description": "The root data of this document, if it is known.", 23 | "oneOf": [ 24 | {"$ref": "jsonary-data.json"}, 25 | {"title": "null", "type": "null"} 26 | ] 27 | }, 28 | "url": { 29 | "title": "URL", 30 | "description": "The URL that this document represents", 31 | "type": "string" 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /demos/q-a/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Q&A demo 5 | 6 | 7 | 8 | 15 |
16 |
17 | 18 | 19 | 20 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /renderers/contributed/markdown.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | function stripTags(md) { 3 | // Rather brutal regex 4 | return md.replace(/<[^<>]+>/g, ''); 5 | } 6 | 7 | Jsonary.render.register({ 8 | renderHtml: function (data, context) { 9 | var html = ""; 10 | if (typeof markdown === 'object' && markdown && typeof markdown.toHTML === 'function') { 11 | html = markdown.toHTML(stripTags(data.value())); 12 | } else if (typeof Markdown === 'object' && typeof Markdown.getSanitizingConverter === 'function') { 13 | var converter = Markdown.getSanitizingConverter(); 14 | html = converter.makeHtml(data.value()); 15 | } 16 | if (html.match(/

/g).length == 1 && html.substring(0, 3) == "

" && html.substring(html.length - 4) == '

') { 17 | html = html.substring(3, html.length - 4); 18 | } 19 | return html; 20 | }, 21 | filter: function (data, schemas) { 22 | if (data.readOnly() && data.schemas().formats().indexOf('markdown') !== -1) { 23 | if (typeof markdown === 'object' && markdown && typeof markdown.toHTML === 'function') { 24 | return true; 25 | } else if (typeof Markdown === 'object' && typeof Markdown.getSanitizingConverter === 'function') { 26 | return true; 27 | } 28 | } 29 | return false; 30 | } 31 | }); 32 | })(); -------------------------------------------------------------------------------- /jsonary/main.js: -------------------------------------------------------------------------------- 1 | //Tidying 2 | // TODO: check all " == undefined", in case they should be " === undefined" instead (null-safety) 3 | // TODO: profile memory consumption - you're throwing closures around the place, it might go wrong 4 | // TODO: try/catch clauses for all listeners/monitors 5 | // TODO: document everything 6 | // TODO: does the assigned baseUrl/fragment of data change when it's removed or assigned? 7 | // TODO: various things are indexed by keys, and might have multiple entries - if we allow an entry to have more than one key, we need to do fewer calculations, and there is less duplication. This will also help speed up schema matching, as we won't have any duplicates. 8 | 9 | //Features: 10 | // TODO: Speculative schema matching (independent of applied schemas) 11 | // TODO: something about types - list of uniqueIds for the data object defining the type? 12 | // TODO: as long as we keep a request in the cache, keep a map of all custom-defined fragments 13 | // TODO: have monitors return boolean, saying whether they are interested in future updates (undefined means true) 14 | // TODO: re-structure monitor keys 15 | // TODO: separate schema monitors from type monitors? 16 | 17 | publicApi.config = { 18 | antiCacheUrls: false, 19 | accessImmediately: false, 20 | debug: false 21 | } -------------------------------------------------------------------------------- /demos/hints/facebook/objects/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "User", 3 | "description": "https://developers.facebook.com/docs/reference/api/user/", 4 | "properties": { 5 | "significant_other": {"$ref": "links/user.json"} 6 | }, 7 | "links": [ 8 | { 9 | "rel": "albums", 10 | "href": "/{id}/albums", 11 | "targetSchema": {"$ref": "../arrays/albums.json"} 12 | }, 13 | { 14 | "href": "/{id}/feed", 15 | "rel": "feed", 16 | "targetSchema": {"$ref": "../arrays/posts.json"} 17 | }, 18 | { 19 | "href": "/{id}/friends", 20 | "rel": "friends", 21 | "targetSchema": {"$ref": "../arrays/user-links.json"} 22 | }, 23 | { 24 | "rel": "mutualfriends", 25 | "href": "/{id}/mutualfriends", 26 | "targetSchema": {"$ref": "../arrays/user-links.json"} 27 | }, 28 | { 29 | "href": "/{id}/picture", 30 | "rel": "picture" 31 | }, 32 | { 33 | "rel": "photos", 34 | "href": "/{id}/photos", 35 | "targetSchema": {"$ref": "../arrays/photos.json"} 36 | }, 37 | { 38 | "rel": "posts", 39 | "href": "/{id}/posts", 40 | "targetSchema": {"$ref": "../arrays/posts.json"} 41 | }, 42 | { 43 | "rel": "tagged", 44 | "href": "/{id}/tagged", 45 | "targetSchema": {"$ref": "../arrays/posts.json"} 46 | } 47 | ], 48 | "extends": {"$ref": "common.json"} 49 | } 50 | 51 | -------------------------------------------------------------------------------- /tests/js/render.test-set.js: -------------------------------------------------------------------------------- 1 | function renderTestSet(testSet, resultsContainer) { 2 | var resultsTable = $('
').appendTo(resultsContainer); 3 | for (var i = 0; i < testSet.tests.length; i++) { 4 | (function(test) { 5 | var testRow = $('' + test.name + '').appendTo(resultsTable); 6 | var resultCell = $('').appendTo(testRow); 7 | var detailCell = $('').appendTo(testRow); 8 | test.onRun(function() { 9 | resultCell.text("Running..."); 10 | }).onPass(function() { 11 | resultCell.text("Passed"); 12 | detailCell.text(test.durationMillis + "ms"); 13 | 14 | testRow.removeClass("test-running"); 15 | testRow.addClass("test-passed"); 16 | }).onFail(function(reason) { 17 | resultCell.text("Failed"); 18 | detailCell.text(reason); 19 | resultCell.click(function() { 20 | alert(JSON.stringify(reason, null, 4)); 21 | }); 22 | testRow.removeClass("test-running"); 23 | testRow.addClass("test-failed"); 24 | }); 25 | })(testSet.tests[i]); 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /tests/tests/06 - Live data and requests/02 - Updating values.js: -------------------------------------------------------------------------------- 1 | tests.add("invalidate() - string argument", function () { 2 | var thisTest = this; 3 | Jsonary.addToCache("test.json?test=invalidate_1", "Cached data"); 4 | var data; 5 | Jsonary.getData("test.json?test=invalidate_1", function(d) { 6 | data = d; 7 | }); 8 | 9 | this.assert(data.value() == "Cached data", "Value should be \"Test data\", not " + JSON.stringify(data.value())); 10 | 11 | Jsonary.invalidate("test.json?test=invalidate_1"); 12 | 13 | setTimeout(function () { 14 | thisTest.assert(data.basicType() == "object", "Basic type should be object, was " + JSON.stringify(data.basicType())); 15 | thisTest.pass(); 16 | }, 100); 17 | }); 18 | 19 | tests.add("invalidate() - regex argument", function () { 20 | var thisTest = this; 21 | Jsonary.addToCache("test.json?test=invalidate_2", "Cached data"); 22 | var data; 23 | Jsonary.getData("test.json?test=invalidate_2", function(d) { 24 | data = d; 25 | }); 26 | 27 | this.assert(data.value() == "Cached data", "Value should be \"Test data\", not " + JSON.stringify(data.value())); 28 | 29 | Jsonary.invalidate(/test\.json/); 30 | 31 | setTimeout(function () { 32 | thisTest.assert(data.basicType() == "object", "Basic type should be object, was " + JSON.stringify(data.basicType())); 33 | thisTest.pass(); 34 | }, 100); 35 | }); 36 | -------------------------------------------------------------------------------- /tests/tests/05 - Tertiary tests/03 - Strings.js: -------------------------------------------------------------------------------- 1 | tests.add("minLength() and maxLength()", function() { 2 | var schema1 = Jsonary.createSchema({ 3 | maxLength: 10, 4 | minLength: 1 5 | }); 6 | var schema2 = Jsonary.createSchema({ 7 | maxLength: 5, 8 | minLength: 0 9 | }); 10 | var schema3 = Jsonary.createSchema({ 11 | maxLength: 5, 12 | minLength: 5 13 | }); 14 | var data = Jsonary.create("value"); 15 | 16 | var maxLength = data.schemas().maxLength(); 17 | this.assert(maxLength == null, "maxLength == null"); 18 | var minLength = data.schemas().minLength(); 19 | this.assert(minLength == 0, "minLength == 0"); 20 | 21 | data.addSchema(schema1); 22 | var maxLength = data.schemas().maxLength(); 23 | this.assert(maxLength == 10, "maxLength == 10"); 24 | var minLength = data.schemas().minLength(); 25 | this.assert(minLength == 1, "minLength == 1"); 26 | 27 | data.addSchema(schema2); 28 | var maxLength = data.schemas().maxLength(); 29 | this.assert(maxLength == 5, "maxLength == 5"); 30 | var minLength = data.schemas().minLength(); 31 | this.assert(minLength == 1, "minLength stil == 1"); 32 | 33 | data.addSchema(schema3); 34 | var maxLength = data.schemas().maxLength(); 35 | this.assert(maxLength == 5, "maxLength still == 5"); 36 | var minLength = data.schemas().minLength(); 37 | this.assert(minLength == 5, "minLength == 5"); 38 | 39 | return true; 40 | }); 41 | 42 | -------------------------------------------------------------------------------- /documentation/json-schema/keywords/General keywords/05 - oneOf.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "oneOf", 3 | "description": "version 4 only - provides a list of schemas, exactly one of must apply to the instance", 4 | "content": [ 5 | "If exactly one schema from the list matches the instance, then that schema describes the instance. If more than one schema matches, then none of the schemas apply.", 6 | "Jsonary makes this list of schemas available using the xorSchemas() method of schemas / schema lists.", 7 | "The example below illustrates this. If the value is five or greater, then one sub-schema applies. If it's zero or less, then the other applies. If it's in between, then neither applies (and in fact, the instance is not valid according to the parent schema)." 8 | ], 9 | "exampleSchema": { 10 | "type": "object", 11 | "properties": { 12 | "conditionalProperty": {"type": "boolean"} 13 | }, 14 | "required": ["conditionalProperty"], 15 | "additionalProperties": false, 16 | "oneOf": [ 17 | { 18 | "title": "Condition ON", 19 | "properties": { 20 | "conditionalProperty": { 21 | "enum": [true] 22 | } 23 | } 24 | }, 25 | { 26 | "title": "Condition OFF", 27 | "properties": { 28 | "conditionalProperty": { 29 | "enum": [false] 30 | } 31 | } 32 | } 33 | ] 34 | }, 35 | "exampleData": {"conditionalProperty": true} 36 | } 37 | -------------------------------------------------------------------------------- /renderers/api.jsonary.css: -------------------------------------------------------------------------------- 1 | .function-definition { 2 | background-color: #F8F8F8; 3 | border: 1px solid #468; 4 | font-size: 12px; 5 | padding: 0; 6 | font-family: Trebuchet MS, sans; 7 | border-radius: 3px; 8 | } 9 | 10 | .function-definition .expand { 11 | float: right; 12 | font-weight: bold; 13 | margin-right: 1em; 14 | } 15 | 16 | .function-definition-signature { 17 | font-family: monospace; 18 | font-weight: bold; 19 | font-size: 1.1em; 20 | margin: 0; 21 | padding: 0.3em; 22 | background-color: #F0F0F8; 23 | border-bottom: 1px solid #BBB; 24 | border-radius: 3px; 25 | } 26 | 27 | .function-keyword { 28 | color: #840; 29 | } 30 | 31 | .function-name { 32 | font-style: italic; 33 | } 34 | 35 | .function-argument-name { 36 | font-family: monospace; 37 | font-weight: bold; 38 | color: #05A; 39 | } 40 | 41 | .function-definition-section { 42 | padding-left: 2em; 43 | } 44 | 45 | .function-definition-arguments { 46 | width: 100%; 47 | margin-top: 0.5em; 48 | } 49 | 50 | .function-definition-arguments .function-argument-name { 51 | text-align: right; 52 | vertical-align: top; 53 | width: 8em; 54 | } 55 | .function-definition-arguments .function-argument-name-text { 56 | padding-right: 0.5em; 57 | border-right: 1px solid black; 58 | white-space: pre; 59 | } 60 | 61 | .function-definition-section-title { 62 | padding-left: 0.3em; 63 | font-size: 1.1em; 64 | font-weight: bold; 65 | } 66 | -------------------------------------------------------------------------------- /demos/index-plain.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JSON browser 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 19 | 20 |
14 | 15 | 17 | 18 |
21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /demos/index-edit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JSON browser 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 19 | 20 |
14 | 15 | 17 | 18 |
21 |
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 = ''; 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 | 40 | 61 | 62 |
32 | JSON data:
33 | 39 |
41 | JSON Schema:
42 | 60 |
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 | --------------------------------------------------------------------------------