json-editor
Version:
JSON Schema based editor
472 lines (410 loc) • 14.1 kB
JavaScript
/**
* All editors should extend from this class
*/
JSONEditor.AbstractEditor = Class.extend({
onChildEditorChange: function(editor) {
this.onChange(true);
},
notify: function() {
this.jsoneditor.notifyWatchers(this.path);
},
change: function() {
if(this.parent) this.parent.onChildEditorChange(this);
else this.jsoneditor.onChange();
},
onChange: function(bubble) {
this.notify();
if(this.watch_listener) this.watch_listener();
if(bubble) this.change();
},
register: function() {
this.jsoneditor.registerEditor(this);
this.onChange();
},
unregister: function() {
if(!this.jsoneditor) return;
this.jsoneditor.unregisterEditor(this);
},
getNumColumns: function() {
return 12;
},
init: function(options) {
this.jsoneditor = options.jsoneditor;
this.theme = this.jsoneditor.theme;
this.template_engine = this.jsoneditor.template;
this.iconlib = this.jsoneditor.iconlib;
this.translate = this.jsoneditor.translate || JSONEditor.defaults.translate;
this.original_schema = options.schema;
this.schema = this.jsoneditor.expandSchema(this.original_schema);
this.options = $extend({}, (this.options || {}), (options.schema.options || {}), options);
if(!options.path && !this.schema.id) this.schema.id = 'root';
this.path = options.path || 'root';
this.formname = options.formname || this.path.replace(/\.([^.]+)/g,'[$1]');
if(this.jsoneditor.options.form_name_root) this.formname = this.formname.replace(/^root\[/,this.jsoneditor.options.form_name_root+'[');
this.key = this.path.split('.').pop();
this.parent = options.parent;
this.link_watchers = [];
if(options.container) this.setContainer(options.container);
},
setContainer: function(container) {
this.container = container;
if(this.schema.id) this.container.setAttribute('data-schemaid',this.schema.id);
if(this.schema.type && typeof this.schema.type === "string") this.container.setAttribute('data-schematype',this.schema.type);
this.container.setAttribute('data-schemapath',this.path);
},
preBuild: function() {
},
build: function() {
},
postBuild: function() {
this.setupWatchListeners();
this.addLinks();
this.setValue(this.getDefault(), true);
this.updateHeaderText();
this.register();
this.onWatchedFieldChange();
},
setupWatchListeners: function() {
var self = this;
// Watched fields
this.watched = {};
if(this.schema.vars) this.schema.watch = this.schema.vars;
this.watched_values = {};
this.watch_listener = function() {
if(self.refreshWatchedFieldValues()) {
self.onWatchedFieldChange();
}
};
this.register();
if(this.schema.hasOwnProperty('watch')) {
var path,path_parts,first,root,adjusted_path;
for(var name in this.schema.watch) {
if(!this.schema.watch.hasOwnProperty(name)) continue;
path = this.schema.watch[name];
if(Array.isArray(path)) {
if(path.length<2) continue;
path_parts = [path[0]].concat(path[1].split('.'));
}
else {
path_parts = path.split('.');
if(!self.theme.closest(self.container,'[data-schemaid="'+path_parts[0]+'"]')) path_parts.unshift('#');
}
first = path_parts.shift();
if(first === '#') first = self.jsoneditor.schema.id || 'root';
// Find the root node for this template variable
root = self.theme.closest(self.container,'[data-schemaid="'+first+'"]');
if(!root) throw "Could not find ancestor node with id "+first;
// Keep track of the root node and path for use when rendering the template
adjusted_path = root.getAttribute('data-schemapath') + '.' + path_parts.join('.');
self.jsoneditor.watch(adjusted_path,self.watch_listener);
self.watched[name] = adjusted_path;
}
}
// Dynamic header
if(this.schema.headerTemplate) {
this.header_template = this.jsoneditor.compileTemplate(this.schema.headerTemplate, this.template_engine);
}
},
addLinks: function() {
// Add links
if(!this.no_link_holder) {
this.link_holder = this.theme.getLinksHolder();
this.container.appendChild(this.link_holder);
if(this.schema.links) {
for(var i=0; i<this.schema.links.length; i++) {
this.addLink(this.getLink(this.schema.links[i]));
}
}
}
},
getButton: function(text, icon, title) {
var btnClass = 'json-editor-btn-'+icon;
if(!this.iconlib) icon = null;
else icon = this.iconlib.getIcon(icon);
if(!icon && title) {
text = title;
title = null;
}
var btn = this.theme.getButton(text, icon, title);
btn.className += ' ' + btnClass + ' ';
return btn;
},
setButtonText: function(button, text, icon, title) {
if(!this.iconlib) icon = null;
else icon = this.iconlib.getIcon(icon);
if(!icon && title) {
text = title;
title = null;
}
return this.theme.setButtonText(button, text, icon, title);
},
addLink: function(link) {
if(this.link_holder) this.link_holder.appendChild(link);
},
getLink: function(data) {
var holder, link;
// Get mime type of the link
var mime = data.mediaType || 'application/javascript';
var type = mime.split('/')[0];
// Template to generate the link href
var href = this.jsoneditor.compileTemplate(data.href,this.template_engine);
// Template to generate the link's download attribute
var download = null;
if(data.download) download = data.download;
if(download && download !== true) {
download = this.jsoneditor.compileTemplate(download, this.template_engine);
}
// Image links
if(type === 'image') {
holder = this.theme.getBlockLinkHolder();
link = document.createElement('a');
link.setAttribute('target','_blank');
var image = document.createElement('img');
this.theme.createImageLink(holder,link,image);
// When a watched field changes, update the url
this.link_watchers.push(function(vars) {
var url = href(vars);
link.setAttribute('href',url);
link.setAttribute('title',data.rel || url);
image.setAttribute('src',url);
});
}
// Audio/Video links
else if(['audio','video'].indexOf(type) >=0) {
holder = this.theme.getBlockLinkHolder();
link = this.theme.getBlockLink();
link.setAttribute('target','_blank');
var media = document.createElement(type);
media.setAttribute('controls','controls');
this.theme.createMediaLink(holder,link,media);
// When a watched field changes, update the url
this.link_watchers.push(function(vars) {
var url = href(vars);
link.setAttribute('href',url);
link.textContent = data.rel || url;
media.setAttribute('src',url);
});
}
// Text links
else {
link = holder = this.theme.getBlockLink();
holder.setAttribute('target','_blank');
holder.textContent = data.rel;
// When a watched field changes, update the url
this.link_watchers.push(function(vars) {
var url = href(vars);
holder.setAttribute('href',url);
holder.textContent = data.rel || url;
});
}
if(download && link) {
if(download === true) {
link.setAttribute('download','');
}
else {
this.link_watchers.push(function(vars) {
link.setAttribute('download',download(vars));
});
}
}
if(data.class) link.className = link.className + ' ' + data.class;
return holder;
},
refreshWatchedFieldValues: function() {
if(!this.watched_values) return;
var watched = {};
var changed = false;
var self = this;
if(this.watched) {
var val,editor;
for(var name in this.watched) {
if(!this.watched.hasOwnProperty(name)) continue;
editor = self.jsoneditor.getEditor(this.watched[name]);
val = editor? editor.getValue() : null;
if(self.watched_values[name] !== val) changed = true;
watched[name] = val;
}
}
watched.self = this.getValue();
if(this.watched_values.self !== watched.self) changed = true;
this.watched_values = watched;
return changed;
},
getWatchedFieldValues: function() {
return this.watched_values;
},
updateHeaderText: function() {
if(this.header) {
// If the header has children, only update the text node's value
if(this.header.children.length) {
for(var i=0; i<this.header.childNodes.length; i++) {
if(this.header.childNodes[i].nodeType===3) {
this.header.childNodes[i].nodeValue = this.getHeaderText();
break;
}
}
}
// Otherwise, just update the entire node
else {
this.header.textContent = this.getHeaderText();
}
}
},
getHeaderText: function(title_only) {
if(this.header_text) return this.header_text;
else if(title_only) return this.schema.title;
else return this.getTitle();
},
onWatchedFieldChange: function() {
var vars;
if(this.header_template) {
vars = $extend(this.getWatchedFieldValues(),{
key: this.key,
i: this.key,
i0: (this.key*1),
i1: (this.key*1+1),
title: this.getTitle()
});
var header_text = this.header_template(vars);
if(header_text !== this.header_text) {
this.header_text = header_text;
this.updateHeaderText();
this.notify();
//this.fireChangeHeaderEvent();
}
}
if(this.link_watchers.length) {
vars = this.getWatchedFieldValues();
for(var i=0; i<this.link_watchers.length; i++) {
this.link_watchers[i](vars);
}
}
},
setValue: function(value) {
this.value = value;
},
getValue: function() {
return this.value;
},
refreshValue: function() {
},
getChildEditors: function() {
return false;
},
destroy: function() {
var self = this;
this.unregister(this);
$each(this.watched,function(name,adjusted_path) {
self.jsoneditor.unwatch(adjusted_path,self.watch_listener);
});
this.watched = null;
this.watched_values = null;
this.watch_listener = null;
this.header_text = null;
this.header_template = null;
this.value = null;
if(this.container && this.container.parentNode) this.container.parentNode.removeChild(this.container);
this.container = null;
this.jsoneditor = null;
this.schema = null;
this.path = null;
this.key = null;
this.parent = null;
},
getDefault: function() {
if(this.schema["default"]) return this.schema["default"];
if(this.schema["enum"]) return this.schema["enum"][0];
var type = this.schema.type || this.schema.oneOf;
if(type && Array.isArray(type)) type = type[0];
if(type && typeof type === "object") type = type.type;
if(type && Array.isArray(type)) type = type[0];
if(typeof type === "string") {
if(type === "number") return 0.0;
if(type === "boolean") return false;
if(type === "integer") return 0;
if(type === "string") return "";
if(type === "object") return {};
if(type === "array") return [];
}
return null;
},
getTitle: function() {
return this.schema.title || this.key;
},
enable: function() {
this.disabled = false;
},
disable: function() {
this.disabled = true;
},
isEnabled: function() {
return !this.disabled;
},
isRequired: function() {
if(typeof this.schema.required === "boolean") return this.schema.required;
else if(this.parent && this.parent.schema && Array.isArray(this.parent.schema.required)) return this.parent.schema.required.indexOf(this.key) > -1;
else if(this.jsoneditor.options.required_by_default) return true;
else return false;
},
getDisplayText: function(arr) {
var disp = [];
var used = {};
// Determine how many times each attribute name is used.
// This helps us pick the most distinct display text for the schemas.
$each(arr,function(i,el) {
if(el.title) {
used[el.title] = used[el.title] || 0;
used[el.title]++;
}
if(el.description) {
used[el.description] = used[el.description] || 0;
used[el.description]++;
}
if(el.format) {
used[el.format] = used[el.format] || 0;
used[el.format]++;
}
if(el.type) {
used[el.type] = used[el.type] || 0;
used[el.type]++;
}
});
// Determine display text for each element of the array
$each(arr,function(i,el) {
var name;
// If it's a simple string
if(typeof el === "string") name = el;
// Object
else if(el.title && used[el.title]<=1) name = el.title;
else if(el.format && used[el.format]<=1) name = el.format;
else if(el.type && used[el.type]<=1) name = el.type;
else if(el.description && used[el.description]<=1) name = el.descripton;
else if(el.title) name = el.title;
else if(el.format) name = el.format;
else if(el.type) name = el.type;
else if(el.description) name = el.description;
else if(JSON.stringify(el).length < 50) name = JSON.stringify(el);
else name = "type";
disp.push(name);
});
// Replace identical display text with "text 1", "text 2", etc.
var inc = {};
$each(disp,function(i,name) {
inc[name] = inc[name] || 0;
inc[name]++;
if(used[name] > 1) disp[i] = name + " " + inc[name];
});
return disp;
},
getOption: function(key) {
try {
throw "getOption is deprecated";
}
catch(e) {
window.console.error(e);
}
return this.options[key];
},
showValidationErrors: function(errors) {
}
});