json-editor
Version:
JSON Schema based editor
709 lines (629 loc) • 23.5 kB
JavaScript
JSONEditor.defaults.editors.array = JSONEditor.AbstractEditor.extend({
getDefault: function() {
return this.schema["default"] || [];
},
register: function() {
this._super();
if(this.rows) {
for(var i=0; i<this.rows.length; i++) {
this.rows[i].register();
}
}
},
unregister: function() {
this._super();
if(this.rows) {
for(var i=0; i<this.rows.length; i++) {
this.rows[i].unregister();
}
}
},
getNumColumns: function() {
var info = this.getItemInfo(0);
// Tabs require extra horizontal space
if(this.tabs_holder) {
return Math.max(Math.min(12,info.width+2),4);
}
else {
return info.width;
}
},
enable: function() {
if(this.add_row_button) this.add_row_button.disabled = false;
if(this.remove_all_rows_button) this.remove_all_rows_button.disabled = false;
if(this.delete_last_row_button) this.delete_last_row_button.disabled = false;
if(this.rows) {
for(var i=0; i<this.rows.length; i++) {
this.rows[i].enable();
if(this.rows[i].moveup_button) this.rows[i].moveup_button.disabled = false;
if(this.rows[i].movedown_button) this.rows[i].movedown_button.disabled = false;
if(this.rows[i].delete_button) this.rows[i].delete_button.disabled = false;
}
}
this._super();
},
disable: function() {
if(this.add_row_button) this.add_row_button.disabled = true;
if(this.remove_all_rows_button) this.remove_all_rows_button.disabled = true;
if(this.delete_last_row_button) this.delete_last_row_button.disabled = true;
if(this.rows) {
for(var i=0; i<this.rows.length; i++) {
this.rows[i].disable();
if(this.rows[i].moveup_button) this.rows[i].moveup_button.disabled = true;
if(this.rows[i].movedown_button) this.rows[i].movedown_button.disabled = true;
if(this.rows[i].delete_button) this.rows[i].delete_button.disabled = true;
}
}
this._super();
},
preBuild: function() {
this._super();
this.rows = [];
this.row_cache = [];
this.hide_delete_buttons = this.options.disable_array_delete || this.jsoneditor.options.disable_array_delete;
this.hide_delete_all_rows_buttons = this.hide_delete_buttons || this.options.disable_array_delete_all_rows || this.jsoneditor.options.disable_array_delete_all_rows;
this.hide_delete_last_row_buttons = this.hide_delete_buttons || this.options.disable_array_delete_last_row || this.jsoneditor.options.disable_array_delete_last_row;
this.hide_move_buttons = this.options.disable_array_reorder || this.jsoneditor.options.disable_array_reorder;
this.hide_add_button = this.options.disable_array_add || this.jsoneditor.options.disable_array_add;
},
build: function() {
var self = this;
if(!this.options.compact) {
this.header = document.createElement('span');
this.header.textContent = this.getTitle();
this.title = this.theme.getHeader(this.header);
this.container.appendChild(this.title);
this.title_controls = this.theme.getHeaderButtonHolder();
this.title.appendChild(this.title_controls);
if(this.schema.description) {
this.description = this.theme.getDescription(this.schema.description);
this.container.appendChild(this.description);
}
this.error_holder = document.createElement('div');
this.container.appendChild(this.error_holder);
if(this.schema.format === 'tabs') {
this.controls = this.theme.getHeaderButtonHolder();
this.title.appendChild(this.controls);
this.tabs_holder = this.theme.getTabHolder();
this.container.appendChild(this.tabs_holder);
this.row_holder = this.theme.getTabContentHolder(this.tabs_holder);
this.active_tab = null;
}
else {
this.panel = this.theme.getIndentedPanel();
this.container.appendChild(this.panel);
this.row_holder = document.createElement('div');
this.panel.appendChild(this.row_holder);
this.controls = this.theme.getButtonHolder();
this.panel.appendChild(this.controls);
}
}
else {
this.panel = this.theme.getIndentedPanel();
this.container.appendChild(this.panel);
this.controls = this.theme.getButtonHolder();
this.panel.appendChild(this.controls);
this.row_holder = document.createElement('div');
this.panel.appendChild(this.row_holder);
}
// Add controls
this.addControls();
},
onChildEditorChange: function(editor) {
this.refreshValue();
this.refreshTabs(true);
this._super(editor);
},
getItemTitle: function() {
if(!this.item_title) {
if(this.schema.items && !Array.isArray(this.schema.items)) {
var tmp = this.jsoneditor.expandRefs(this.schema.items);
this.item_title = tmp.title || 'item';
}
else {
this.item_title = 'item';
}
}
return this.item_title;
},
getItemSchema: function(i) {
if(Array.isArray(this.schema.items)) {
if(i >= this.schema.items.length) {
if(this.schema.additionalItems===true) {
return {};
}
else if(this.schema.additionalItems) {
return $extend({},this.schema.additionalItems);
}
}
else {
return $extend({},this.schema.items[i]);
}
}
else if(this.schema.items) {
return $extend({},this.schema.items);
}
else {
return {};
}
},
getItemInfo: function(i) {
var schema = this.getItemSchema(i);
// Check if it's cached
this.item_info = this.item_info || {};
var stringified = JSON.stringify(schema);
if(typeof this.item_info[stringified] !== "undefined") return this.item_info[stringified];
// Get the schema for this item
schema = this.jsoneditor.expandRefs(schema);
this.item_info[stringified] = {
title: schema.title || "item",
'default': schema["default"],
width: 12,
child_editors: schema.properties || schema.items
};
return this.item_info[stringified];
},
getElementEditor: function(i) {
var item_info = this.getItemInfo(i);
var schema = this.getItemSchema(i);
schema = this.jsoneditor.expandRefs(schema);
schema.title = item_info.title+' '+(i+1);
var editor = this.jsoneditor.getEditorClass(schema);
var holder;
if(this.tabs_holder) {
holder = this.theme.getTabContent();
}
else if(item_info.child_editors) {
holder = this.theme.getChildEditorHolder();
}
else {
holder = this.theme.getIndentedPanel();
}
this.row_holder.appendChild(holder);
var ret = this.jsoneditor.createEditor(editor,{
jsoneditor: this.jsoneditor,
schema: schema,
container: holder,
path: this.path+'.'+i,
parent: this,
required: true
});
ret.preBuild();
ret.build();
ret.postBuild();
if(!ret.title_controls) {
ret.array_controls = this.theme.getButtonHolder();
holder.appendChild(ret.array_controls);
}
return ret;
},
destroy: function() {
this.empty(true);
if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title);
if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description);
if(this.row_holder && this.row_holder.parentNode) this.row_holder.parentNode.removeChild(this.row_holder);
if(this.controls && this.controls.parentNode) this.controls.parentNode.removeChild(this.controls);
if(this.panel && this.panel.parentNode) this.panel.parentNode.removeChild(this.panel);
this.rows = this.row_cache = this.title = this.description = this.row_holder = this.panel = this.controls = null;
this._super();
},
empty: function(hard) {
if(!this.rows) return;
var self = this;
$each(this.rows,function(i,row) {
if(hard) {
if(row.tab && row.tab.parentNode) row.tab.parentNode.removeChild(row.tab);
self.destroyRow(row,true);
self.row_cache[i] = null;
}
self.rows[i] = null;
});
self.rows = [];
if(hard) self.row_cache = [];
},
destroyRow: function(row,hard) {
var holder = row.container;
if(hard) {
row.destroy();
if(holder.parentNode) holder.parentNode.removeChild(holder);
if(row.tab && row.tab.parentNode) row.tab.parentNode.removeChild(row.tab);
}
else {
if(row.tab) row.tab.style.display = 'none';
holder.style.display = 'none';
row.unregister();
}
},
getMax: function() {
if((Array.isArray(this.schema.items)) && this.schema.additionalItems === false) {
return Math.min(this.schema.items.length,this.schema.maxItems || Infinity);
}
else {
return this.schema.maxItems || Infinity;
}
},
refreshTabs: function(refresh_headers) {
var self = this;
$each(this.rows, function(i,row) {
if(!row.tab) return;
if(refresh_headers) {
row.tab_text.textContent = row.getHeaderText();
}
else {
if(row.tab === self.active_tab) {
self.theme.markTabActive(row.tab);
row.container.style.display = '';
}
else {
self.theme.markTabInactive(row.tab);
row.container.style.display = 'none';
}
}
});
},
setValue: function(value, initial) {
// Update the array's value, adding/removing rows when necessary
value = value || [];
if(!(Array.isArray(value))) value = [value];
var serialized = JSON.stringify(value);
if(serialized === this.serialized) return;
// Make sure value has between minItems and maxItems items in it
if(this.schema.minItems) {
while(value.length < this.schema.minItems) {
value.push(this.getItemInfo(value.length)["default"]);
}
}
if(this.getMax() && value.length > this.getMax()) {
value = value.slice(0,this.getMax());
}
var self = this;
$each(value,function(i,val) {
if(self.rows[i]) {
// TODO: don't set the row's value if it hasn't changed
self.rows[i].setValue(val,initial);
}
else if(self.row_cache[i]) {
self.rows[i] = self.row_cache[i];
self.rows[i].setValue(val,initial);
self.rows[i].container.style.display = '';
if(self.rows[i].tab) self.rows[i].tab.style.display = '';
self.rows[i].register();
}
else {
self.addRow(val,initial);
}
});
for(var j=value.length; j<self.rows.length; j++) {
self.destroyRow(self.rows[j]);
self.rows[j] = null;
}
self.rows = self.rows.slice(0,value.length);
// Set the active tab
var new_active_tab = null;
$each(self.rows, function(i,row) {
if(row.tab === self.active_tab) {
new_active_tab = row.tab;
return false;
}
});
if(!new_active_tab && self.rows.length) new_active_tab = self.rows[0].tab;
self.active_tab = new_active_tab;
self.refreshValue(initial);
self.refreshTabs(true);
self.refreshTabs();
self.onChange();
// TODO: sortable
},
refreshValue: function(force) {
var self = this;
var oldi = this.value? this.value.length : 0;
this.value = [];
$each(this.rows,function(i,editor) {
// Get the value for this editor
self.value[i] = editor.getValue();
});
if(oldi !== this.value.length || force) {
// If we currently have minItems items in the array
var minItems = this.schema.minItems && this.schema.minItems >= this.rows.length;
$each(this.rows,function(i,editor) {
// Hide the move down button for the last row
if(editor.movedown_button) {
if(i === self.rows.length - 1) {
editor.movedown_button.style.display = 'none';
}
else {
editor.movedown_button.style.display = '';
}
}
// Hide the delete button if we have minItems items
if(editor.delete_button) {
if(minItems) {
editor.delete_button.style.display = 'none';
}
else {
editor.delete_button.style.display = '';
}
}
// Get the value for this editor
self.value[i] = editor.getValue();
});
var controls_needed = false;
if(!this.value.length) {
this.delete_last_row_button.style.display = 'none';
this.remove_all_rows_button.style.display = 'none';
}
else if(this.value.length === 1) {
this.remove_all_rows_button.style.display = 'none';
// If there are minItems items in the array, or configured to hide the delete_last_row button, hide the delete button beneath the rows
if(minItems || this.hide_delete_last_row_buttons) {
this.delete_last_row_button.style.display = 'none';
}
else {
this.delete_last_row_button.style.display = '';
controls_needed = true;
}
}
else {
if(minItems || this.hide_delete_last_row_buttons) {
this.delete_last_row_button.style.display = 'none';
}
else {
this.delete_last_row_button.style.display = '';
controls_needed = true;
}
if(minItems || this.hide_delete_all_rows_buttons) {
this.remove_all_rows_button.style.display = 'none';
}
else {
this.remove_all_rows_button.style.display = '';
controls_needed = true;
}
}
// If there are maxItems in the array, hide the add button beneath the rows
if((this.getMax() && this.getMax() <= this.rows.length) || this.hide_add_button){
this.add_row_button.style.display = 'none';
}
else {
this.add_row_button.style.display = '';
controls_needed = true;
}
if(!this.collapsed && controls_needed) {
this.controls.style.display = 'inline-block';
}
else {
this.controls.style.display = 'none';
}
}
},
addRow: function(value, initial) {
var self = this;
var i = this.rows.length;
self.rows[i] = this.getElementEditor(i);
self.row_cache[i] = self.rows[i];
if(self.tabs_holder) {
self.rows[i].tab_text = document.createElement('span');
self.rows[i].tab_text.textContent = self.rows[i].getHeaderText();
self.rows[i].tab = self.theme.getTab(self.rows[i].tab_text);
self.rows[i].tab.addEventListener('click', function(e) {
self.active_tab = self.rows[i].tab;
self.refreshTabs();
e.preventDefault();
e.stopPropagation();
});
self.theme.addTab(self.tabs_holder, self.rows[i].tab);
}
var controls_holder = self.rows[i].title_controls || self.rows[i].array_controls;
// Buttons to delete row, move row up, and move row down
if(!self.hide_delete_buttons) {
self.rows[i].delete_button = this.getButton(self.getItemTitle(),'delete',this.translate('button_delete_row_title',[self.getItemTitle()]));
self.rows[i].delete_button.className += ' delete';
self.rows[i].delete_button.setAttribute('data-i',i);
self.rows[i].delete_button.addEventListener('click',function(e) {
e.preventDefault();
e.stopPropagation();
var i = this.getAttribute('data-i')*1;
var value = self.getValue();
var newval = [];
var new_active_tab = null;
$each(value,function(j,row) {
if(j===i) {
// If the one we're deleting is the active tab
if(self.rows[j].tab === self.active_tab) {
// Make the next tab active if there is one
// Note: the next tab is going to be the current tab after deletion
if(self.rows[j+1]) new_active_tab = self.rows[j].tab;
// Otherwise, make the previous tab active if there is one
else if(j) new_active_tab = self.rows[j-1].tab;
}
return; // If this is the one we're deleting
}
newval.push(row);
});
self.setValue(newval);
if(new_active_tab) {
self.active_tab = new_active_tab;
self.refreshTabs();
}
self.onChange(true);
});
if(controls_holder) {
controls_holder.appendChild(self.rows[i].delete_button);
}
}
if(i && !self.hide_move_buttons) {
self.rows[i].moveup_button = this.getButton('','moveup',this.translate('button_move_up_title'));
self.rows[i].moveup_button.className += ' moveup';
self.rows[i].moveup_button.setAttribute('data-i',i);
self.rows[i].moveup_button.addEventListener('click',function(e) {
e.preventDefault();
e.stopPropagation();
var i = this.getAttribute('data-i')*1;
if(i<=0) return;
var rows = self.getValue();
var tmp = rows[i-1];
rows[i-1] = rows[i];
rows[i] = tmp;
self.setValue(rows);
self.active_tab = self.rows[i-1].tab;
self.refreshTabs();
self.onChange(true);
});
if(controls_holder) {
controls_holder.appendChild(self.rows[i].moveup_button);
}
}
if(!self.hide_move_buttons) {
self.rows[i].movedown_button = this.getButton('','movedown',this.translate('button_move_down_title'));
self.rows[i].movedown_button.className += ' movedown';
self.rows[i].movedown_button.setAttribute('data-i',i);
self.rows[i].movedown_button.addEventListener('click',function(e) {
e.preventDefault();
e.stopPropagation();
var i = this.getAttribute('data-i')*1;
var rows = self.getValue();
if(i>=rows.length-1) return;
var tmp = rows[i+1];
rows[i+1] = rows[i];
rows[i] = tmp;
self.setValue(rows);
self.active_tab = self.rows[i+1].tab;
self.refreshTabs();
self.onChange(true);
});
if(controls_holder) {
controls_holder.appendChild(self.rows[i].movedown_button);
}
}
if(value) self.rows[i].setValue(value, initial);
self.refreshTabs();
},
addControls: function() {
var self = this;
this.collapsed = false;
this.toggle_button = this.getButton('','collapse',this.translate('button_collapse'));
this.title_controls.appendChild(this.toggle_button);
var row_holder_display = self.row_holder.style.display;
var controls_display = self.controls.style.display;
this.toggle_button.addEventListener('click',function(e) {
e.preventDefault();
e.stopPropagation();
if(self.collapsed) {
self.collapsed = false;
if(self.panel) self.panel.style.display = '';
self.row_holder.style.display = row_holder_display;
if(self.tabs_holder) self.tabs_holder.style.display = '';
self.controls.style.display = controls_display;
self.setButtonText(this,'','collapse',self.translate('button_collapse'));
}
else {
self.collapsed = true;
self.row_holder.style.display = 'none';
if(self.tabs_holder) self.tabs_holder.style.display = 'none';
self.controls.style.display = 'none';
if(self.panel) self.panel.style.display = 'none';
self.setButtonText(this,'','expand',self.translate('button_expand'));
}
});
// If it should start collapsed
if(this.options.collapsed) {
$trigger(this.toggle_button,'click');
}
// Collapse button disabled
if(this.schema.options && typeof this.schema.options.disable_collapse !== "undefined") {
if(this.schema.options.disable_collapse) this.toggle_button.style.display = 'none';
}
else if(this.jsoneditor.options.disable_collapse) {
this.toggle_button.style.display = 'none';
}
// Add "new row" and "delete last" buttons below editor
this.add_row_button = this.getButton(this.getItemTitle(),'add',this.translate('button_add_row_title',[this.getItemTitle()]));
this.add_row_button.addEventListener('click',function(e) {
e.preventDefault();
e.stopPropagation();
var i = self.rows.length;
if(self.row_cache[i]) {
self.rows[i] = self.row_cache[i];
self.rows[i].setValue(self.rows[i].getDefault(), true);
self.rows[i].container.style.display = '';
if(self.rows[i].tab) self.rows[i].tab.style.display = '';
self.rows[i].register();
}
else {
self.addRow();
}
self.active_tab = self.rows[i].tab;
self.refreshTabs();
self.refreshValue();
self.onChange(true);
});
self.controls.appendChild(this.add_row_button);
this.delete_last_row_button = this.getButton(this.translate('button_delete_last',[this.getItemTitle()]),'delete',this.translate('button_delete_last_title',[this.getItemTitle()]));
this.delete_last_row_button.addEventListener('click',function(e) {
e.preventDefault();
e.stopPropagation();
var rows = self.getValue();
var new_active_tab = null;
if(self.rows.length > 1 && self.rows[self.rows.length-1].tab === self.active_tab) new_active_tab = self.rows[self.rows.length-2].tab;
rows.pop();
self.setValue(rows);
if(new_active_tab) {
self.active_tab = new_active_tab;
self.refreshTabs();
}
self.onChange(true);
});
self.controls.appendChild(this.delete_last_row_button);
this.remove_all_rows_button = this.getButton(this.translate('button_delete_all'),'delete',this.translate('button_delete_all_title'));
this.remove_all_rows_button.addEventListener('click',function(e) {
e.preventDefault();
e.stopPropagation();
self.setValue([]);
self.onChange(true);
});
self.controls.appendChild(this.remove_all_rows_button);
if(self.tabs) {
this.add_row_button.style.width = '100%';
this.add_row_button.style.textAlign = 'left';
this.add_row_button.style.marginBottom = '3px';
this.delete_last_row_button.style.width = '100%';
this.delete_last_row_button.style.textAlign = 'left';
this.delete_last_row_button.style.marginBottom = '3px';
this.remove_all_rows_button.style.width = '100%';
this.remove_all_rows_button.style.textAlign = 'left';
this.remove_all_rows_button.style.marginBottom = '3px';
}
},
showValidationErrors: function(errors) {
var self = this;
// Get all the errors that pertain to this editor
var my_errors = [];
var other_errors = [];
$each(errors, function(i,error) {
if(error.path === self.path) {
my_errors.push(error);
}
else {
other_errors.push(error);
}
});
// Show errors for this editor
if(this.error_holder) {
if(my_errors.length) {
var message = [];
this.error_holder.innerHTML = '';
this.error_holder.style.display = '';
$each(my_errors, function(i,error) {
self.error_holder.appendChild(self.theme.getErrorMessage(error.message));
});
}
// Hide error area
else {
this.error_holder.style.display = 'none';
}
}
// Show errors for child editors
$each(this.rows, function(i,row) {
row.showValidationErrors(other_errors);
});
}
});