diff --git a/docs/styling.md b/docs/styling.md new file mode 100644 index 0000000..718ab9f --- /dev/null +++ b/docs/styling.md @@ -0,0 +1,24 @@ +# Styling Reference + +Documentation for writing custom JSON Editor styles. + +## Node +Node is the fundamental unit that makes up the hierarchical JSON display in the Form, Tree, and View modes. It can be +customized with several classes that reflect its type and state. + +- `jsoneditor-field`: the property name +- `jsoneditor-value`: the value of the property + - The value element will have one of the following classes depending on its type: + - `jsoneditor-null` + - `jsoneditor-undefined` + - `jsoneditor-number` + - `jsoneditor-string` + - `jsoneditor-boolean` + - `jsoneditor-regexp` + - `jsoneditor-array` + - `jsoneditor-object` + - `jsoneditor-url` + - `jsoneditor-is-default`: applied to the value element when the value matches the default from the schema + - `jsoneditor-is-not-default`: applied to the value element when the value does not match the default from the schema +- `jsoneditor-schema-error`: the warning icon that appears when the Node has a schema validation error + - `jsoneditor-popover`: the popover that appears when hovering over the schema validation error warning icon diff --git a/examples/07_json_schema_validation.html b/examples/07_json_schema_validation.html index b9f1f56..6969a2a 100644 --- a/examples/07_json_schema_validation.html +++ b/examples/07_json_schema_validation.html @@ -35,16 +35,28 @@ "type": "object", "properties": { "firstName": { + "title": "First Name", + "description": "The given name.", + "examples": [ + "John" + ], "type": "string" }, "lastName": { + "title": "Last Name", + "description": "The family name.", + "examples": [ + "Smith" + ], "type": "string" }, "gender": { + "title": "Gender", "enum": ["male", "female"] }, "availableToHire": { - "type": "boolean" + "type": "boolean", + "default": false }, "age": { "description": "Age in years", @@ -65,10 +77,19 @@ "required": ["address"], "properties": { "company": { - "type": "string" + "type": "string", + "examples": [ + "ACME", + "Dexter Industries" + ] }, "role": { - "type": "string" + "description": "Job title.", + "type": "string", + "examples": [ + "Human Resources Coordinator", + "Software Developer" + ] }, "address": { "type": "string" diff --git a/src/css/jsoneditor.css b/src/css/jsoneditor.css index f1af3af..fd94585 100644 --- a/src/css/jsoneditor.css +++ b/src/css/jsoneditor.css @@ -5,7 +5,8 @@ div.jsoneditor { div.jsoneditor-field, div.jsoneditor-value, -div.jsoneditor-readonly { +div.jsoneditor-readonly, +div.jsoneditor-default { border: 1px solid transparent; min-height: 16px; min-width: 32px; @@ -97,13 +98,12 @@ div.jsoneditor-value.jsoneditor-highlight-active:hover { } div.jsoneditor-value.jsoneditor-string { - color: #008000; + color: #006000; } div.jsoneditor-value.jsoneditor-object, div.jsoneditor-value.jsoneditor-array { min-width: 16px; - color: #808080; } div.jsoneditor-value.jsoneditor-number { @@ -122,7 +122,10 @@ div.jsoneditor-value.jsoneditor-invalid { color: #000000; } - +div.jsoneditor-default { + color: #808080; + padding-left: 10px; +} div.jsoneditor-tree button.jsoneditor-button { width: 24px; diff --git a/src/js/Node.js b/src/js/Node.js index 21dee0c..b03ac11 100644 --- a/src/js/Node.js +++ b/src/js/Node.js @@ -1822,6 +1822,8 @@ Node.prototype._updateDomValue = function () { // strip formatting from the contents of the editable div util.stripFormatting(domValue); + + this._updateDomDefault(); } }; @@ -1908,6 +1910,32 @@ Node.prototype._getDomField = function(silent) { } }; +/** + * Update the value of the schema default element in the DOM. + * @private + * @returns {undefined} + */ +Node.prototype._updateDomDefault = function () { + // Short-circuit if schema is missing, has no default, or if Node has children + if (!this.schema || !this.schema.default || this._hasChilds()) { + return; + } + + if (this.value === this.schema.default) { + if (this.dom.select) { + this.dom.value.removeAttribute('title'); + } else { + this.dom.value.title = translate('default'); + this.dom.value.classList.add('jsoneditor-is-default'); + this.dom.value.classList.remove('jsoneditor-is-not-default'); + } + } else { + this.dom.value.removeAttribute('title'); + this.dom.value.classList.remove('jsoneditor-is-default'); + this.dom.value.classList.add('jsoneditor-is-not-default'); + } +}; + /** * Validate this node and all it's childs * @return {Array.<{node: Node, error: {message: string}}>} Returns a list with duplicates diff --git a/src/js/i18n.js b/src/js/i18n.js index 0ab0a08..9c4450a 100644 --- a/src/js/i18n.js +++ b/src/js/i18n.js @@ -86,6 +86,7 @@ var _defs = { modeViewText: 'View', modeViewTitle: 'Switch to tree view', examples: 'Examples', + default: 'Default', }, 'zh-CN': { array: '数组', @@ -169,6 +170,7 @@ var _defs = { modeViewText: '视图', modeViewTitle: '切换至树视图', examples: '例子', + default: '缺省', }, 'pt-BR': { array: 'Lista', @@ -264,6 +266,7 @@ var _defs = { 'Campo do tipo nao é determinado através do seu valor, ' + 'mas sempre retornara um texto.', examples: 'Exemplos', + default: 'Revelia', }, tr: { array: 'Dizin', @@ -347,6 +350,7 @@ var _defs = { modeViewText: 'Görünüm', modeViewTitle: 'Ağaç görünümüne geç', examples: 'Örnekler', + default: 'Varsayılan', } }; diff --git a/src/js/util.js b/src/js/util.js index a0f2847..493dd15 100644 --- a/src/js/util.js +++ b/src/js/util.js @@ -1087,13 +1087,24 @@ exports.makeFieldTooltip = function (schema, locale) { tooltip += schema.description; } + if (schema.default) { + if (tooltip.length > 0) { + tooltip += '\n\n'; + } + tooltip += translate('default', undefined, locale) + '\n'; + tooltip += JSON.stringify(schema.default, null, 2); + } + if (Array.isArray(schema.examples) && schema.examples.length > 0) { if (tooltip.length > 0) { tooltip += '\n\n'; } tooltip += translate('examples', undefined, locale) + '\n'; - schema.examples.forEach(function (example) { - tooltip += JSON.stringify(example, null, 2) + '\n'; + schema.examples.forEach(function (example, index) { + tooltip += JSON.stringify(example, null, 2); + if (index !== schema.examples.length - 1) { + tooltip += '\n'; + } }); } diff --git a/test/util.test.js b/test/util.test.js index dab06a8..6a62a7d 100644 --- a/test/util.test.js +++ b/test/util.test.js @@ -160,8 +160,12 @@ describe('util', function () { assert.strictEqual(util.makeFieldTooltip({description: 'foo'}), 'foo'); }); + it('should make tooltips with only default', function () { + assert.strictEqual(util.makeFieldTooltip({default: 'foo'}), 'Default\n"foo"'); + }); + it('should make tooltips with only examples', function () { - assert.strictEqual(util.makeFieldTooltip({examples: ['foo', 'bar']}), 'Examples\n"foo"\n"bar"\n'); + assert.strictEqual(util.makeFieldTooltip({examples: ['foo', 'bar']}), 'Examples\n"foo"\n"bar"'); }); it('should make tooltips with title and description', function () { @@ -180,7 +184,14 @@ describe('util', function () { it('should make tooltips with title, description, and examples', function () { assert.strictEqual( util.makeFieldTooltip({title: 'foo', description: 'bar', examples: ['baz']}), - 'foo\nbar\n\nExamples\n"baz"\n', + 'foo\nbar\n\nExamples\n"baz"', + ); + }); + + it('should make tooltips with title, description, default, and examples', function () { + assert.strictEqual( + util.makeFieldTooltip({title: 'foo', description: 'bar', default: 'bat', examples: ['baz']}), + 'foo\nbar\n\nDefault\n"bat"\n\nExamples\n"baz"', ); }); @@ -188,11 +199,15 @@ describe('util', function () { assert.strictEqual(util.makeFieldTooltip({title: '', description: 'bar'}), 'bar'); assert.strictEqual(util.makeFieldTooltip({title: 'foo', description: ''}), 'foo'); assert.strictEqual(util.makeFieldTooltip({description: 'bar', examples: []}), 'bar'); - assert.strictEqual(util.makeFieldTooltip({description: 'bar', examples: ['']}), 'bar\n\nExamples\n""\n'); + assert.strictEqual(util.makeFieldTooltip({description: 'bar', examples: ['']}), 'bar\n\nExamples\n""'); + }); + + it('should internationalize "Defaults" correctly', function () { + assert.strictEqual(util.makeFieldTooltip({default: 'foo'}, 'pt-BR'), 'Revelia\n"foo"'); }); it('should internationalize "Examples" correctly', function () { - assert.strictEqual(util.makeFieldTooltip({examples: ['foo']}, 'pt-BR'), 'Exemplos\n"foo"\n'); + assert.strictEqual(util.makeFieldTooltip({examples: ['foo']}, 'pt-BR'), 'Exemplos\n"foo"'); }); }); // TODO: thoroughly test all util methods