diff --git a/Gruntfile.js b/Gruntfile.js
index 59f5dbedd..d225e2ce3 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -27,6 +27,7 @@ module.exports = function(grunt) {
'src/editors/string.js',
'src/editors/number.js',
'src/editors/integer.js',
+ 'src/editors/rating.js',
'src/editors/object.js',
'src/editors/array.js',
'src/editors/table.js',
diff --git a/examples/basic.html b/examples/basic.html
index 6505025c2..a094af8a7 100644
--- a/examples/basic.html
+++ b/examples/basic.html
@@ -41,11 +41,18 @@
Basic JSON Editor Example
2010,2011,2012,2013,2014
],
default: 2008
+ },
+ safety: {
+ type: "integer",
+ format: "rating",
+ maximum: "5",
+ exclusiveMaximum: false,
+ readonly: false
}
}
}
});
-
+
// Hook up the submit button to log to the console
document.getElementById('submit').addEventListener('click',function() {
// Get the value from the editor
diff --git a/src/defaults.js b/src/defaults.js
index e3118c408..43d3add0c 100644
--- a/src/defaults.js
+++ b/src/defaults.js
@@ -188,6 +188,10 @@ JSONEditor.defaults.resolvers.unshift(function(schema) {
// If the schema is a simple type
if(typeof schema.type === "string") return schema.type;
});
+// Use a specialized editor for ratings
+JSONEditor.defaults.resolvers.unshift(function(schema) {
+ if(schema.type === "integer" && schema.format === "rating") return "rating";
+});
// Use the select editor for all boolean values
JSONEditor.defaults.resolvers.unshift(function(schema) {
if(schema.type === 'boolean') {
diff --git a/src/editors/rating.js b/src/editors/rating.js
new file mode 100644
index 000000000..e2d5807dc
--- /dev/null
+++ b/src/editors/rating.js
@@ -0,0 +1,152 @@
+JSONEditor.defaults.editors.rating = JSONEditor.defaults.editors.integer.extend({
+ build: function() {
+ var self = this, i;
+ if(!this.options.compact) this.header = this.label = this.theme.getFormInputLabel(this.getTitle());
+ if(this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description);
+
+ // Dynamically add the required CSS the first time this editor is used
+ var styleId = 'json-editor-style-rating';
+ var styles = document.getElementById(styleId);
+ if (!styles) {
+ var style = document.createElement('style');
+ style.id = styleId;
+ style.type = 'text/css';
+ style.innerHTML =
+ ' .rating-container {' +
+ ' display: inline-block;' +
+ ' clear: both;' +
+ ' }' +
+ ' ' +
+ ' .rating {' +
+ ' float:left;' +
+ ' }' +
+ ' ' +
+ ' /* :not(:checked) is a filter, so that browsers that don’t support :checked don’t' +
+ ' follow these rules. Every browser that supports :checked also supports :not(), so' +
+ ' it doesn’t make the test unnecessarily selective */' +
+ ' .rating:not(:checked) > input {' +
+ ' position:absolute;' +
+ ' top:-9999px;' +
+ ' clip:rect(0,0,0,0);' +
+ ' }' +
+ ' ' +
+ ' .rating:not(:checked) > label {' +
+ ' float:right;' +
+ ' width:1em;' +
+ ' padding:0 .1em;' +
+ ' overflow:hidden;' +
+ ' white-space:nowrap;' +
+ ' cursor:pointer;' +
+ ' color:#ddd;' +
+ ' }' +
+ ' ' +
+ ' .rating:not(:checked) > label:before {' +
+ ' content: \'★ \';' +
+ ' }' +
+ ' ' +
+ ' .rating > input:checked ~ label {' +
+ ' color: #FFB200;' +
+ ' }' +
+ ' ' +
+ ' .rating:not([readOnly]):not(:checked) > label:hover,' +
+ ' .rating:not([readOnly]):not(:checked) > label:hover ~ label {' +
+ ' color: #FFDA00;' +
+ ' }' +
+ ' ' +
+ ' .rating:not([readOnly]) > input:checked + label:hover,' +
+ ' .rating:not([readOnly]) > input:checked + label:hover ~ label,' +
+ ' .rating:not([readOnly]) > input:checked ~ label:hover,' +
+ ' .rating:not([readOnly]) > input:checked ~ label:hover ~ label,' +
+ ' .rating:not([readOnly]) > label:hover ~ input:checked ~ label {' +
+ ' color: #FF8C0D;' +
+ ' }' +
+ ' ' +
+ ' .rating:not([readOnly]) > label:active {' +
+ ' position:relative;' +
+ ' top:2px;' +
+ ' left:2px;' +
+ ' }';
+ document.getElementsByTagName('head')[0].appendChild(style);
+ }
+
+ this.input = this.theme.getFormInputField('hidden');
+ this.container.appendChild(this.input);
+
+ // Required to keep height
+ var ratingContainer = document.createElement('div');
+ ratingContainer.className = 'rating-container';
+
+ // Contains options for rating
+ var group = document.createElement('div');
+ group.setAttribute('name', this.formname);
+ group.className = 'rating';
+ ratingContainer.appendChild(group);
+
+ if(this.options.compact) this.container.setAttribute('class',this.container.getAttribute('class')+' compact');
+
+ var max = this.schema.maximum ? this.schema.maximum : 5;
+ if (this.schema.exclusiveMaximum) max--;
+
+ this.inputs = [];
+ for(i=max; i>0; i--) {
+ var id = this.formname + i;
+ var radioInput = this.theme.getFormInputField('radio');
+ radioInput.setAttribute('id', id);
+ radioInput.setAttribute('value', i);
+ radioInput.setAttribute('name', this.formname);
+ group.appendChild(radioInput);
+ this.inputs.push(radioInput);
+
+ var label = document.createElement('label');
+ label.setAttribute('for', id);
+ label.appendChild(document.createTextNode(i + (i == 1 ? ' star' : ' stars')));
+ group.appendChild(label);
+ }
+
+ if(this.schema.readOnly || this.schema.readonly) {
+ this.always_disabled = true;
+ $each(this.inputs,function(i,input) {
+ group.setAttribute("readOnly", "readOnly");
+ input.disabled = true;
+ });
+ }
+
+ ratingContainer
+ .addEventListener('change',function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+
+ self.input.value = e.srcElement.value;
+
+ self.is_dirty = true;
+
+ self.refreshValue();
+ self.watch_listener();
+ self.jsoneditor.notifyWatchers(self.path);
+ if(self.parent) self.parent.onChildEditorChange(self);
+ else self.jsoneditor.onChange();
+ });
+
+ this.control = this.theme.getFormControl(this.label, ratingContainer, this.description);
+ this.container.appendChild(this.control);
+
+ this.refreshValue();
+ },
+ setValue: function(val) {
+ var sanitized = this.sanitize(val);
+ if(this.value === sanitized) {
+ return;
+ }
+ var self = this;
+ $each(this.inputs,function(i,input) {
+ if (input.value === sanitized) {
+ input.checked = true;
+ self.value = sanitized;
+ self.input.value = self.value;
+ self.watch_listener();
+ self.jsoneditor.notifyWatchers(self.path);
+ return false;
+ }
+ });
+ }
+});