├── .gitignore ├── img ├── h5on.png ├── view.png ├── compare.png └── h5on.svg ├── Gruntfile.js ├── package.json ├── css ├── h5on.css └── h5on-solarized-light.css ├── data └── sample-data.json ├── license.txt ├── demo ├── pretty.js └── demo.html ├── h5on.min.js ├── src └── h5on.jquery.js ├── h5on.jquery.min.js ├── h5on.js ├── h5on.jquery.js └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /img/h5on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrkn/h5on/HEAD/img/h5on.png -------------------------------------------------------------------------------- /img/view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrkn/h5on/HEAD/img/view.png -------------------------------------------------------------------------------- /img/compare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrkn/h5on/HEAD/img/compare.png -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function( grunt ){ 2 | grunt.initConfig({ 3 | pkg: grunt.file.readJSON( 'package.json' ), 4 | browserify: { 5 | 'h5on.jquery.js': [ 'src/h5on.jquery.js' ] 6 | }, 7 | uglify: { 8 | dist: { 9 | files: { 10 | 'h5on.min.js': [ 'h5on.js' ], 11 | 'h5on.jquery.min.js': [ 'h5on.jquery.js' ] 12 | } 13 | } 14 | }, 15 | }); 16 | grunt.loadNpmTasks( 'grunt-browserify' ); 17 | grunt.loadNpmTasks( 'grunt-contrib-uglify' ); 18 | grunt.registerTask( 'default', [ 'browserify', 'uglify' ] ); 19 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "h5on", 3 | "version": "0.1.0", 4 | "description": "HTML5 Object Notation", 5 | "main": "h5on.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/nrkn/h5on.git" 9 | }, 10 | "keywords": [ 11 | "HTML5", 12 | "JSON", 13 | "object", 14 | "traversal", 15 | "notation" 16 | ], 17 | "author": "Nik Coughlin", 18 | "licenses": [ 19 | { 20 | "type": "MIT", 21 | "url": "https://github.com/nrkn/h5on/blob/master/license.txt" 22 | } 23 | ], 24 | "bugs": { 25 | "url": "https://github.com/nrkn/h5on/issues" 26 | }, 27 | "homepage": "http://h5on.org/" 28 | } 29 | -------------------------------------------------------------------------------- /css/h5on.css: -------------------------------------------------------------------------------- 1 | js-object, js-array, js-string, js-number, js-boolean, js-null { 2 | display: inline-block; 3 | } 4 | 5 | js-object > *, js-array > * { 6 | display: table-row; 7 | } 8 | 9 | js-object > *:before, js-array > *:before { 10 | display: table-cell; 11 | font-weight: bold; 12 | padding-right: 0.5em; 13 | } 14 | 15 | js-object:empty:after { 16 | content: '{}' 17 | } 18 | 19 | js-object > *:before { 20 | content: attr( data-key ); 21 | } 22 | 23 | js-array { 24 | counter-reset: array -1; 25 | } 26 | 27 | js-array:empty:after { 28 | content: '[]' 29 | } 30 | 31 | js-array > *:before { 32 | content: '[' counter( array ) ']'; 33 | } 34 | 35 | js-array > * { 36 | counter-increment: array; 37 | } 38 | 39 | js-null:after { 40 | content: 'null'; 41 | } 42 | -------------------------------------------------------------------------------- /data/sample-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "Akosua", 3 | "Occupation": "Zombie Hunter", 4 | "Is Infected": false, 5 | "Equipment": [ 6 | { 7 | "Name": "Backpack", 8 | "Type": "Container", 9 | "Capacity": 40000, 10 | "Weight": 2000, 11 | "Contents": [ 12 | { 13 | "Name": "Water Bottle", 14 | "Type": "Container", 15 | "Capacity": 1000, 16 | "Weight": 0.2, 17 | "Contents": [ 18 | { 19 | "Name": "Water", 20 | "Weight": 365.9 21 | } 22 | ] 23 | }, 24 | { 25 | "Name": "Necronomicon", 26 | "Type": "Book", 27 | "Weight": 0.87 28 | } 29 | ] 30 | }, 31 | { 32 | "Name": "Katana", 33 | "Type": "Weapon", 34 | "Class": "Edged", 35 | "Damage": { 36 | "Base": "4d6", 37 | "Modifier": -2 38 | }, 39 | "Weight": 1200 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Nik Coughlin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /css/h5on-solarized-light.css: -------------------------------------------------------------------------------- 1 | /* 2 | $base03: #002b36; 3 | $base02: #073642; 4 | $base01: #586e75; 5 | $base00: #657b83; 6 | $base0: #839496; 7 | $base1: #93a1a1; 8 | $base2: #eee8d5; 9 | $base3: #fdf6e3; 10 | $yellow: #b58900; 11 | $orange: #cb4b16; 12 | $red: #dc322f; 13 | $magenta: #d33682; 14 | $violet: #6c71c4; 15 | $blue: #268bd2; 16 | $cyan: #2aa198; 17 | $green: #859900; 18 | */ 19 | 20 | js-object, js-array, js-number, js-string, js-boolean, js-null { 21 | background: #fdf6e3; 22 | font-family: Consolas, "Liberation Mono", Courier, monospace; 23 | } 24 | 25 | js-object > *:before, js-array > *:before { 26 | padding: 0.25em; 27 | padding-right: 0.5em; 28 | 29 | background: #eee8d5; 30 | font-weight: normal; 31 | } 32 | 33 | js-object > *:before { 34 | color: #b58900; 35 | } 36 | 37 | js-array > *:before { 38 | color: #657b83; 39 | } 40 | 41 | js-object { 42 | outline: 1px solid #93a1a1; 43 | } 44 | 45 | js-array { 46 | outline: 1px dotted #93a1a1; 47 | } 48 | 49 | js-string, js-number { 50 | color: #2aa198; 51 | } 52 | 53 | js-boolean { 54 | color: #657b83; 55 | } 56 | 57 | js-null { 58 | color: #657b83; 59 | } -------------------------------------------------------------------------------- /demo/pretty.js: -------------------------------------------------------------------------------- 1 | function pretty( $el ){ 2 | var inline = { 3 | 'js-string': true, 4 | 'js-number': true, 5 | 'js-boolean': true, 6 | 'js-null': true 7 | }; 8 | 9 | function print( $el, depth ){ 10 | var result = ''; 11 | $el.each( function(){ 12 | var $current = $( this ); 13 | var el = $current[ 0 ]; 14 | 15 | if( depth > 0 ){ 16 | result += Array( depth * 2 + 1 ).join( ' ' ); 17 | } 18 | 19 | if( el.nodeType === 3 ){ 20 | result += el.textContent; 21 | if( depth > 0 ){ 22 | result += '\n'; 23 | } 24 | return; 25 | } 26 | 27 | var tag = el.tagName.toLowerCase(); 28 | 29 | result += '<' + tag; 30 | if( el.attributes.length ){ 31 | $.each( el.attributes, function( i, attribute ){ 32 | result += ( ' ' + attribute.name + '="' + attribute.value + '"' ); 33 | }); 34 | } 35 | result += '>'; 36 | if( !inline[ tag ] ){ 37 | result += '\n'; 38 | } 39 | $.each( $current.contents(), function( i, content ){ 40 | result += print( $( content ), inline[ tag ] ? -1 : depth + 1 ); 41 | }); 42 | if( !inline[ tag ] ){ 43 | result += Array( depth * 2 + 1 ).join( ' ' ); 44 | } 45 | result += '\n' 46 | }); 47 | return result; 48 | } 49 | 50 | return print( $el, 0 ); 51 | } -------------------------------------------------------------------------------- /h5on.min.js: -------------------------------------------------------------------------------- 1 | !function(){"use strict";function a(a){function b(b,c,d){var e=a.createElement("js-"+b);return void 0!==d&&e.setAttribute("data-key",d),void 0!==c&&(e.textContent=c),e}function c(d,e){d=JSON.parse(JSON.stringify(d));var f=typeof d;if("number"===f)return b("number",d,e);if("string"===f)return b("string",d,e);if("boolean"===f)return b("boolean",d,e);if(!d)return b("null",void 0,e);if(Array.isArray(d)){var g=b("array",void 0,e);return d.forEach(function(a){g.appendChild(c(a))}),g}if(d.tagName){var g=a.createElement(d.tagName);if(g.attributes)for(e in g.attributes)g.setAttribute(e,g.attributes[e]);return Array.isArray(g.children)&&g.children.forEach(function(a){g.appendChild(c(a))}),g}var g=b("object",void 0,e);return Object.keys(d).forEach(function(a){g.appendChild(c(d[a],a))}),g}function d(a){if(3===a.nodeType)return a.textContent;var b=a.tagName.toLowerCase();if("js-number"===b)return 1*a.textContent;if("js-string"===b)return a.textContent;if("js-boolean"===b)return"true"===a.textContent.trim().toLowerCase();if("js-null"===b)return null;if("js-array"===b){for(var c=[],e=0;e= b; 33 | }, 34 | gt: function( a, b ){ 35 | return a > b; 36 | } 37 | }; 38 | 39 | function compare( el, value, operator ){ 40 | var $el = $( el ); 41 | 42 | if( $el.is( 'js-number, js-string, js-boolean, js-null' ) ){ 43 | var elValue = h5on.toObj( el ); 44 | 45 | value = JSON.parse( value ); 46 | 47 | if( $.type( comparers[ operator ] ) === 'function' ){ 48 | return comparers[ operator ]( elValue, value ); 49 | } 50 | } 51 | 52 | return false; 53 | }; 54 | 55 | $.extend( $.expr[ ':' ], { 56 | valEq: function( el, i, args ){ 57 | return compare( el, args[ 3 ], 'eq' ); 58 | }, 59 | valLte: function( el, i, args ){ 60 | return compare( el, args[ 3 ], 'lte' ); 61 | }, 62 | valLt: function( el, i, args ){ 63 | return compare( el, args[ 3 ], 'lt' ); 64 | }, 65 | valGte: function( el, i, args ){ 66 | return compare( el, args[ 3 ], 'gte' ); 67 | }, 68 | valGt: function( el, i, args ){ 69 | return compare( el, args[ 3 ], 'gt' ); 70 | } 71 | }); 72 | })( jQuery, window.document ); -------------------------------------------------------------------------------- /img/h5on.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 32 | -------------------------------------------------------------------------------- /h5on.jquery.min.js: -------------------------------------------------------------------------------- 1 | !function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g=a},lt:function(a,b){return b>a},gte:function(a,b){return a>=b},gt:function(a,b){return a>b}};b.extend(b.expr[":"],{valEq:function(a,b,c){return d(a,c[3],"eq")},valLte:function(a,b,c){return d(a,c[3],"lte")},valLt:function(a,b,c){return d(a,c[3],"lt")},valGte:function(a,b,c){return d(a,c[3],"gte")},valGt:function(a,b,c){return d(a,c[3],"gt")}})}(jQuery,window.document)},{"../h5on":1}]},{},[2]); -------------------------------------------------------------------------------- /demo/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Template 7 | 8 | 9 | 10 | 15 | 16 | 17 |
18 |

H5ON

19 |
20 |

JSON

21 |

22 |       
23 |
24 |

H5ON

25 |

26 |       
27 |
28 |

View

29 |
30 |
31 |
32 | 33 | 34 | 35 | 36 | 37 | 96 | 97 | -------------------------------------------------------------------------------- /h5on.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | function H5on( document ){ 5 | function createEl( tag, val, key ){ 6 | var el = document.createElement( 'js-' + tag ); 7 | if( key !== undefined ){ 8 | el.setAttribute( 'data-key', key ); 9 | } 10 | if( val !== undefined ){ 11 | el.textContent = val; 12 | } 13 | return el; 14 | } 15 | 16 | function toEl( obj, key ){ 17 | obj = JSON.parse( JSON.stringify( obj ) ); 18 | 19 | var type = typeof obj; 20 | 21 | if( type === 'number' ) return createEl( 'number', obj, key ); 22 | if( type === 'string' ) return createEl( 'string', obj, key ); 23 | if( type === 'boolean' ) return createEl( 'boolean', obj, key ); 24 | if( !obj ) return createEl( 'null', undefined, key ); 25 | 26 | if( Array.isArray( obj ) ){ 27 | var el = createEl( 'array', undefined, key ); 28 | obj.forEach( function( child ){ 29 | el.appendChild( toEl( child ) ); 30 | }); 31 | return el; 32 | } 33 | 34 | if( obj.tagName ){ 35 | var el = document.createElement( obj.tagName ); 36 | 37 | if( el.attributes ){ 38 | for( key in el.attributes ){ 39 | el.setAttribute( key, el.attributes[ key ] ); 40 | } 41 | } 42 | 43 | if( Array.isArray( el.children ) ){ 44 | el.children.forEach( function( child ){ 45 | el.appendChild( toEl( child ) ); 46 | }); 47 | } 48 | 49 | return el; 50 | } 51 | 52 | var el = createEl( 'object', undefined, key ); 53 | 54 | Object.keys( obj ).forEach( function( key ){ 55 | el.appendChild( toEl( obj[ key ], key ) ); 56 | }); 57 | 58 | return el; 59 | } 60 | 61 | function toObj( el ){ 62 | if( el.nodeType === 3 ) return el.textContent; 63 | 64 | var tag = el.tagName.toLowerCase(); 65 | 66 | if( tag === 'js-number' ) return el.textContent * 1; 67 | if( tag === 'js-string' ) return el.textContent; 68 | if( tag === 'js-boolean' ) return el.textContent.trim().toLowerCase() === 'true'; 69 | if( tag === 'js-null' ) return null; 70 | 71 | if( tag === 'js-array' ){ 72 | var arr = []; 73 | for( var i = 0; i < el.childNodes.length; i++ ){ 74 | arr.push( toObj( el.childNodes[ i ] ) ); 75 | } 76 | return arr; 77 | } 78 | 79 | var obj = {}; 80 | 81 | if( tag === 'js-object' ){ 82 | var missingKeyIndex = 0; 83 | for( var i = 0; i < el.childNodes.length; i++ ){ 84 | var child = el.childNodes[ i ]; 85 | if( !child.hasAttribute( 'data-key' ) ){ 86 | child.setAttribute( 'data-key', 'h5-missing-key-' + missingKeyIndex++ ); 87 | } 88 | obj[ child.getAttribute( 'data-key' ) ] = toObj( child ); 89 | } 90 | return obj; 91 | } 92 | 93 | obj.tagName = el.tagName; 94 | 95 | if( el.attributes.length ){ 96 | obj.attributes = {}; 97 | for( var i = 0; i < el.attributes.length; i++ ){ 98 | var attr = el.attributes[ i ]; 99 | obj.attributes[ attr.nodeName ] = attr.nodeValue; 100 | } 101 | } 102 | 103 | if( el.childNodes.length ){ 104 | obj.children = []; 105 | for( var i = 0; i < el.childNodes.length; i++ ){ 106 | obj.children.push( toObj( el.childNodes[ i ] ) ); 107 | } 108 | } 109 | 110 | return obj; 111 | } 112 | 113 | return { 114 | toEl: toEl, 115 | toObj: toObj 116 | }; 117 | } 118 | 119 | if( typeof module !== 'undefined' && typeof module.exports !== 'undefined' ){ 120 | module.exports = H5on; 121 | } else { 122 | window.h5on = H5on( window.document ); 123 | } 124 | })(); -------------------------------------------------------------------------------- /h5on.jquery.js: -------------------------------------------------------------------------------- 1 | (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= b; 159 | }, 160 | gt: function( a, b ){ 161 | return a > b; 162 | } 163 | }; 164 | 165 | function compare( el, value, operator ){ 166 | var $el = $( el ); 167 | 168 | if( $el.is( 'js-number, js-string, js-boolean, js-null' ) ){ 169 | var elValue = h5on.toObj( el ); 170 | 171 | value = JSON.parse( value ); 172 | 173 | if( $.type( comparers[ operator ] ) === 'function' ){ 174 | return comparers[ operator ]( elValue, value ); 175 | } 176 | } 177 | 178 | return false; 179 | }; 180 | 181 | $.extend( $.expr[ ':' ], { 182 | valEq: function( el, i, args ){ 183 | return compare( el, args[ 3 ], 'eq' ); 184 | }, 185 | valLte: function( el, i, args ){ 186 | return compare( el, args[ 3 ], 'lte' ); 187 | }, 188 | valLt: function( el, i, args ){ 189 | return compare( el, args[ 3 ], 'lt' ); 190 | }, 191 | valGte: function( el, i, args ){ 192 | return compare( el, args[ 3 ], 'gte' ); 193 | }, 194 | valGt: function( el, i, args ){ 195 | return compare( el, args[ 3 ], 'gt' ); 196 | } 197 | }); 198 | })( jQuery, window.document ); 199 | },{"../h5on":1}]},{},[2]); 200 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ![H5ON](img/h5on.png?raw=true) 2 | 3 | ![Side by side comparison](img/compare.png?raw=true) 4 | 5 | # H5ON (HTML5 Object Notation) 6 | 7 | H5ON is an object notation, like [JSON](http://json.org), for representing objects as [HTML5](http://www.w3.org/TR/html5/) elements. 8 | 9 | This document describes the syntax. The project contains both a Node.js and a jQuery plugin implementation. 10 | 11 | It came out of a thought experiment with two goals in mind: 12 | 13 | 1. How would I traverse a JavaScript object graph using jQuery? 14 | 2. How can I have an object notation that the browser displays meaningfully? 15 | 16 | As this is the fruit of a thought experiment, please be aware that there are many alternative ways to traverse object graphs, and that for the most part they are much saner than this one. You have been warned! 17 | 18 | Despite that caveat, you can do some interesting and powerful things using this approach. jQuery is a sophisticated traversal library and contains a lot of useful ways to search and filter data. 19 | 20 | # Sanity Check 21 | 22 | Alternatively, you can traverse JSON data and run CSS selectors against it using [these plugins](https://github.com/mojule/json-dom-plugins) for [JSON tree](https://github.com/mojule/json-tree) 23 | 24 | # Table of Contents 25 | 26 | 1. [Usage](#usage) 27 | 1. [Node.js](#nodejs) 28 | 2. [jQuery](#jquery) 29 | 3. [Convert an object to H5ON](#convert-an-object-to-h5on) 30 | 4. [Place H5ON in the DOM for viewing](#place-h5on-in-the-dom-for-viewing) 31 | 5. [Traverse H5ON using selectors](#traverse-h5on-using-selectors) 32 | 6. [Convert H5ON to an object](#convert-h5on-to-an-object) 33 | 7. [Manipulation](#manipulation) 34 | 8. [Mixing HTML and H5ON](#mixing-html-and-h5on) 35 | 2. [Demos and Examples](#demos-and-examples) 36 | 1. [Demos](#demos) 37 | 2. [Selector Examples](#selector-examples) 38 | 3. [Why would you want to traverse an object graph with jQuery?](#why-would-you-want-to-traverse-an-object-graph-with-jquery) 39 | 1. [Example - find all objects in the graph which have a property named “Weight”:](#example---find-all-objects-in-the-graph-which-have-a-property-named-weight) 40 | 4. [What do you mean by human readable?](#what-do-you-mean-by-human-readable) 41 | 1. [Example of browser rendering, using H5ON generated by the input from the previous example and h5on.css:](#example-of-browser-rendering-using-h5on-generated-by-the-input-from-the-previous-example-and-h5oncss) 42 | 5. [Syntax](#syntax) 43 | 1. [Primitive Literals](#primitive-literals) 44 | 2. [Number](#number) 45 | 3. [String](#string) 46 | 4. [Boolean](#boolean) 47 | 5. [Array](#array) 48 | 6. [Object](#object) 49 | 7. [Null](#null) 50 | 6. [Status](#status) 51 | 7. [License](#license) 52 | 53 | ## Usage 54 | 55 | ### Node.js 56 | 57 | `npm install h5on` 58 | 59 | h5on requires a reference to a document - use `window.document` in the browser, otherwise you can use `jsdom` or similar: 60 | 61 | ```javascript 62 | var h5on = require( 'h5on' )( document ); 63 | var domElements = h5on.toEl( data ); 64 | // do something with elements 65 | var backToJsObject = h5on.toObj( domElements ); 66 | ``` 67 | 68 | The rest of this document assumes that you are using the jQuery plugin - the only real differences are traversal (but you can use `document.querySelector` or a library), the [custom selectors](#custom-selector-expressions) included in the jQuery plugin, and that you convert to and from JS objects using the static methods outlined above rather than the static `$.h5on` and `$instance.h5on()` methods. 69 | 70 | ### jQuery 71 | 72 | Download and reference [h5on.jquery.min.js](h5on.jquery.min.js) and [h5on.css](h5on.css). 73 | 74 | **TODO: Bower** 75 | 76 | ### Convert an object to H5ON 77 | 78 | ```javascript 79 | var person = { 80 | name: 'Akosua', 81 | age: 39 82 | }; 83 | var $person = $.h5on( person ); 84 | ``` 85 | 86 | ### Place H5ON in the DOM for viewing 87 | 88 | ```javascript 89 | $( 'body' ).append( $person ); 90 | ``` 91 | 92 | It's recommended that you use CSS (such as the included h5on.css) to style H5ON elements, otherwise the browser won't know how to render them and it'll come out as a mess of plain text. 93 | 94 | ### Traverse H5ON using selectors 95 | 96 | ```javascript 97 | var $age = $person.find( '[data-key="age"]' ); 98 | ``` 99 | 100 | ### Convert H5ON to an object 101 | 102 | ```javascript 103 | var age = $age.h5on(); 104 | ``` 105 | 106 | ### Manipulation 107 | 108 | You can manipulate the H5ON DOM in the same way you would the standard DOM, using jQuery. 109 | 110 | You can create new H5ON elements however you like (provided the syntax is correct), but the easiest way to create them is to use the static helper: 111 | 112 | ```javascript 113 | var $age = $.h5on( 39 ); 114 | ``` 115 | 116 | The plugin expects you to respect the [H5ON syntax](#syntax) and performs no error checking. 117 | 118 | When creating a property for an object, pass the key as the second argument to the static helper: 119 | 120 | ```javascript 121 | $someH5Object.append( $.h5on( 39, 'age' ) ); 122 | ``` 123 | 124 | ### Mixing HTML and H5ON 125 | 126 | The H5ON DOM can contain arbitrary HTML at any point. HTML is converted to and from JavaScript objects using the following syntax: 127 | 128 | ```javascript 129 | var heading = { 130 | "tagName": "h1", 131 | "attr": { 132 | "style": "color: red;" 133 | }, 134 | "children": [ 135 | "Hello World" 136 | ] 137 | }; 138 | ``` 139 | 140 | The ``children`` array can contain any mixture of text nodes (as strings), element nodes structured as per above, and H5ON elements - other node types such as comments are not currently supported (pull request welcome!). 141 | 142 | ## Demos and Examples 143 | 144 | ### Demos 145 | 146 | 1. [Interactive selector demo](http://h5on.org/demo.html) 147 | 2. [Visual H5ON editor demo](http://h5on.org/editor.html) 148 | 149 | ### Selector Examples 150 | 151 | #### Find all of a specific type 152 | ```javascript 153 | //strings 154 | var $strings = $( 'js-string' ); 155 | 156 | //objects 157 | var $objects = $( 'js-object' ); 158 | 159 | //etc. 160 | ``` 161 | 162 | #### Find all objects that have a certain key 163 | ```javascript 164 | var $withWeights = $( 'js-object:has( > [data-key="Weight"] )' ); 165 | ``` 166 | 167 | #### Find all objects with a certain key value pair 168 | ```javascript 169 | var $containers = $( 'js-object:has( > [data-key="Type"]:valEq( "Container" ) )' ); 170 | ``` 171 | 172 | #### Find all objects that contain a certain type 173 | ```javascript 174 | var $withArrays = $( 'js-object:has( > js-array )' ); 175 | ``` 176 | 177 | #### Custom selector expressions 178 | 179 | The plugin has the following custom selector expressions: 180 | 181 | ```css 182 | :valEq( value ) 183 | :valLte( value ) 184 | :valLt( value ) 185 | :valGte( value ) 186 | :valGt( value ) 187 | ``` 188 | 189 | For example: 190 | 191 | ```javascript 192 | var $lightObjects = $( 'js-object:has( > [data-key="Weight"]:valLt( 500 ) )' ) 193 | ``` 194 | 195 | ## Why would you want to traverse an object graph with jQuery? 196 | 197 | JSON data is an object graph - the [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model) is an object graph. 198 | Many developers are already familiar with using [selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_started/Selectors) to traverse and manipulate the DOM. 199 | By representing an object graph as [HTML5 custom elements](http://www.w3.org/TR/custom-elements/), you can apply the same techniques you use on the DOM with any data. 200 | The plugin takes a plain (JSON-serializable) JavaScript object as data, and returns H5ON, which is composed of ordinary DOM elements wrapped in a jQuery object which can be traversed and manipulated as usual, and can be placed in a document for viewing. 201 | Calling the H5ON function again on these jQuery objects converts them back to a plain JavaScript object. 202 | 203 | In addition, H5ON also maps non-H5ON elements to and from JavaScript objects so that you can mix H5ON and ordinary DOM elements. 204 | 205 | ### Example - find all objects in the graph which have a property named "Weight": 206 | 207 | #### Input 208 | ```javascript 209 | { 210 | "Name": "Akosua", 211 | "Occupation": "Zombie Hunter", 212 | "Is Infected": false, 213 | "Equipment": [ 214 | { 215 | "Name": "Backpack", 216 | "Type": "Container", 217 | "Capacity": 40000, 218 | "Weight": 2000, 219 | "Contents": [ 220 | { 221 | "Name": "Water Bottle", 222 | "Type": "Container", 223 | "Capacity": 1000, 224 | "Weight": 0.2, 225 | "Contents": [ 226 | { 227 | "Name": "Water", 228 | "Weight": 365.9 229 | } 230 | ] 231 | }, 232 | { 233 | "Name": "Necronomicon", 234 | "Type": "Book", 235 | "Weight": 0.87 236 | } 237 | ] 238 | }, 239 | { 240 | "Name": "Katana", 241 | "Type": "Weapon", 242 | "Class": "Edged", 243 | "Damage": { 244 | "Base": "4d6", 245 | "Modifier": -2 246 | }, 247 | "Weight": 1200 248 | } 249 | ] 250 | } 251 | ``` 252 | 253 | #### JavaScript 254 | ```javascript 255 | //convert a JavaScript object to H5ON 256 | var $h5Data = $.h5on( input ); 257 | //find all objects in the object graph with a key matching "Weight" 258 | var $h5WithWeights = $h5Data.find( 'js-object:has( > [data-key]="Weight" )' ); 259 | //convert the H5ON back to a JavaScript object 260 | var output = $h5WithWeights.h5on(); 261 | ``` 262 | 263 | Or, in one line: 264 | 265 | ```javascript 266 | var output = $.h5on( input ).find( 'js-object:has( > [data-key]="Weight" )' ).h5on(); 267 | ``` 268 | 269 | #### Output 270 | ```javascript 271 | [ 272 | { 273 | "Name": "Backpack", 274 | "Type": "Container", 275 | "Capacity": 40000, 276 | "Weight": 2000, 277 | "Contents": [ 278 | { 279 | "Name": "Water Bottle", 280 | "Type": "Container", 281 | "Capacity": 1000, 282 | "Weight": 0.2, 283 | "Contents": [ 284 | { 285 | "Name": "Water", 286 | "Weight": 365.9 287 | } 288 | ] 289 | }, 290 | { 291 | "Name": "Necronomicon", 292 | "Type": "Book", 293 | "Weight": 0.87 294 | } 295 | ] 296 | }, 297 | { 298 | "Name": "Water Bottle", 299 | "Type": "Container", 300 | "Capacity": 1000, 301 | "Weight": 0.2, 302 | "Contents": [ 303 | { 304 | "Name": "Water", 305 | "Weight": 365.9 306 | } 307 | ] 308 | }, 309 | { 310 | "Name": "Water", 311 | "Weight": 365.9 312 | }, 313 | { 314 | "Name": "Necronomicon", 315 | "Type": "Book", 316 | "Weight": 0.87 317 | }, 318 | { 319 | "Name": "Katana", 320 | "Type": "Weapon", 321 | "Class": "Edged", 322 | "Damage": { 323 | "Base": "4d6", 324 | "Modifier": -2 325 | }, 326 | "Weight": 1200 327 | } 328 | ] 329 | ``` 330 | 331 | ## What do you mean by human readable? 332 | 333 | When we say "human readable", we primarily mean as rendered by a browser's layout engine. 334 | 335 | ### Example of browser rendering, using H5ON generated by the input from the previous example and [h5on.css](css/h5on.css), themed with [Solarized Light]( http://ethanschoonover.com/solarized ): 336 | 337 | #### Browser rendering 338 | ![H5ON](img/view.png?raw=true) 339 | 340 | #### Generated H5ON 341 | ```html 342 | 343 | Akosua 344 | Zombie Hunter 345 | false 346 | 347 | 348 | Backpack 349 | Container 350 | 40000 351 | 2000 352 | 353 | 354 | Water Bottle 355 | Container 356 | 1000 357 | 0.2 358 | 359 | 360 | Water 361 | 365.9 362 | 363 | 364 | 365 | 366 | Necronomicon 367 | Book 368 | 0.87 369 | 370 | 371 | 372 | 373 | Katana 374 | Weapon 375 | Edged 376 | 377 | 4d6 378 | -2 379 | 380 | 1200 381 | 382 | 383 | 384 | ``` 385 | 386 | ## Syntax 387 | 388 | Like JSON, possible values are: 389 | 390 | * number 391 | * string 392 | * boolean 393 | * array 394 | * object 395 | * null 396 | 397 | ### Primitive Literals 398 | 399 | The primitive values are number, string and boolean. The notation for these is: 400 | 401 | ```html 402 | {{value}} 403 | ``` 404 | 405 | When converting back to an object, the plugin expects this element to contain a single text node only, and the text content of that node is converted to the expected type. No error checking is performed! 406 | 407 | ### Number 408 | 409 | #### H5ON 410 | ```html 411 | 42 412 | ``` 413 | 414 | #### JSON 415 | ```javascript 416 | 42 417 | ``` 418 | 419 | ### String 420 | 421 | #### H5ON 422 | ```html 423 | Hello World 424 | ``` 425 | 426 | #### JSON 427 | ```javascript 428 | "Hello World" 429 | ``` 430 | 431 | ### Boolean 432 | 433 | #### H5ON 434 | ```html 435 | false 436 | ``` 437 | 438 | #### JSON 439 | ```javascript 440 | false 441 | ``` 442 | 443 | ### Array 444 | 445 | #### H5ON 446 | ```html 447 | 448 | 42 449 | Hello World 450 | false 451 | 452 | ``` 453 | 454 | #### JSON 455 | ```javascript 456 | [ 42, "Hello World", false ] 457 | ``` 458 | 459 | When converting back to an object each direct child of the js-array element is considered to be an array element. An array can contain any other value. 460 | 461 | ### Object 462 | 463 | #### H5ON 464 | ```html 465 | 466 | Akosua 467 | 39 468 | Hunting zombies 469 | 470 | ``` 471 | 472 | #### JSON 473 | ```javascript 474 | { 475 | "name": "Akosua", 476 | "age": 39, 477 | "hobby": "Hunting zombies" 478 | } 479 | ``` 480 | 481 | When converting an H5ON object back to a JavaScript object, every direct child of the js-object element is considered to be a property on the JS object. Each direct child should have an attribute `data-key` and this will be used as the key on the resulting object. If the attribute is missing an autogenerated key will be used. 482 | 483 | ### Null 484 | 485 | #### H5ON 486 | ```html 487 | 488 | ``` 489 | 490 | #### JSON 491 | ```javascript 492 | null 493 | ``` 494 | 495 | ## Status 496 | 497 | Working prototype - usable, shows what it can do, demonstrates the concept, imperfect. Pull requests welcomed, if it's a simple and obvious bug fix go ahead, but for anything more major please open an issue to discuss first. 498 | 499 | ## License 500 | 501 | The MIT License (MIT) 502 | 503 | Copyright (c) 2015 Nik Coughlin 504 | 505 | Permission is hereby granted, free of charge, to any person obtaining a copy 506 | of this software and associated documentation files (the "Software"), to deal 507 | in the Software without restriction, including without limitation the rights 508 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 509 | copies of the Software, and to permit persons to whom the Software is 510 | furnished to do so, subject to the following conditions: 511 | 512 | The above copyright notice and this permission notice shall be included in all 513 | copies or substantial portions of the Software. 514 | 515 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 516 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 517 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 518 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 519 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 520 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 521 | SOFTWARE. 522 | --------------------------------------------------------------------------------