UNPKG

json-editor

Version:
887 lines (784 loc) 29.9 kB
JSONEditor.defaults.editors.object = JSONEditor.AbstractEditor.extend({ getDefault: function() { return $extend({},this.schema["default"] || {}); }, getChildEditors: function() { return this.editors; }, register: function() { this._super(); if(this.editors) { for(var i in this.editors) { if(!this.editors.hasOwnProperty(i)) continue; this.editors[i].register(); } } }, unregister: function() { this._super(); if(this.editors) { for(var i in this.editors) { if(!this.editors.hasOwnProperty(i)) continue; this.editors[i].unregister(); } } }, getNumColumns: function() { return Math.max(Math.min(12,this.maxwidth),3); }, enable: function() { if(this.editjson_button) this.editjson_button.disabled = false; if(this.addproperty_button) this.addproperty_button.disabled = false; this._super(); if(this.editors) { for(var i in this.editors) { if(!this.editors.hasOwnProperty(i)) continue; this.editors[i].enable(); } } }, disable: function() { if(this.editjson_button) this.editjson_button.disabled = true; if(this.addproperty_button) this.addproperty_button.disabled = true; this.hideEditJSON(); this._super(); if(this.editors) { for(var i in this.editors) { if(!this.editors.hasOwnProperty(i)) continue; this.editors[i].disable(); } } }, layoutEditors: function() { var self = this, i, j; if(!this.row_container) return; // Sort editors by propertyOrder this.property_order = Object.keys(this.editors); this.property_order = this.property_order.sort(function(a,b) { var ordera = self.editors[a].schema.propertyOrder; var orderb = self.editors[b].schema.propertyOrder; if(typeof ordera !== "number") ordera = 1000; if(typeof orderb !== "number") orderb = 1000; return ordera - orderb; }); var container; if(this.format === 'grid') { var rows = []; $each(this.property_order, function(j,key) { var editor = self.editors[key]; if(editor.property_removed) return; var found = false; var width = editor.options.hidden? 0 : (editor.options.grid_columns || editor.getNumColumns()); var height = editor.options.hidden? 0 : editor.container.offsetHeight; // See if the editor will fit in any of the existing rows first for(var i=0; i<rows.length; i++) { // If the editor will fit in the row horizontally if(rows[i].width + width <= 12) { // If the editor is close to the other elements in height // i.e. Don't put a really tall editor in an otherwise short row or vice versa if(!height || (rows[i].minh*0.5 < height && rows[i].maxh*2 > height)) { found = i; } } } // If there isn't a spot in any of the existing rows, start a new row if(found === false) { rows.push({ width: 0, minh: 999999, maxh: 0, editors: [] }); found = rows.length-1; } rows[found].editors.push({ key: key, //editor: editor, width: width, height: height }); rows[found].width += width; rows[found].minh = Math.min(rows[found].minh,height); rows[found].maxh = Math.max(rows[found].maxh,height); }); // Make almost full rows width 12 // Do this by increasing all editors' sizes proprotionately // Any left over space goes to the biggest editor // Don't touch rows with a width of 6 or less for(i=0; i<rows.length; i++) { if(rows[i].width < 12) { var biggest = false; var new_width = 0; for(j=0; j<rows[i].editors.length; j++) { if(biggest === false) biggest = j; else if(rows[i].editors[j].width > rows[i].editors[biggest].width) biggest = j; rows[i].editors[j].width *= 12/rows[i].width; rows[i].editors[j].width = Math.floor(rows[i].editors[j].width); new_width += rows[i].editors[j].width; } if(new_width < 12) rows[i].editors[biggest].width += 12-new_width; rows[i].width = 12; } } // layout hasn't changed if(this.layout === JSON.stringify(rows)) return false; this.layout = JSON.stringify(rows); // Layout the form container = document.createElement('div'); for(i=0; i<rows.length; i++) { var row = this.theme.getGridRow(); container.appendChild(row); for(j=0; j<rows[i].editors.length; j++) { var key = rows[i].editors[j].key; var editor = this.editors[key]; if(editor.options.hidden) editor.container.style.display = 'none'; else this.theme.setGridColumnSize(editor.container,rows[i].editors[j].width); row.appendChild(editor.container); } } } // Normal layout else { container = document.createElement('div'); $each(this.property_order, function(i,key) { var editor = self.editors[key]; if(editor.property_removed) return; var row = self.theme.getGridRow(); container.appendChild(row); if(editor.options.hidden) editor.container.style.display = 'none'; else self.theme.setGridColumnSize(editor.container,12); row.appendChild(editor.container); }); } this.row_container.innerHTML = ''; this.row_container.appendChild(container); }, getPropertySchema: function(key) { // Schema declared directly in properties var schema = this.schema.properties[key] || {}; schema = $extend({},schema); var matched = this.schema.properties[key]? true : false; // Any matching patternProperties should be merged in if(this.schema.patternProperties) { for(var i in this.schema.patternProperties) { if(!this.schema.patternProperties.hasOwnProperty(i)) continue; var regex = new RegExp(i); if(regex.test(key)) { schema.allOf = schema.allOf || []; schema.allOf.push(this.schema.patternProperties[i]); matched = true; } } } // Hasn't matched other rules, use additionalProperties schema if(!matched && this.schema.additionalProperties && typeof this.schema.additionalProperties === "object") { schema = $extend({},this.schema.additionalProperties); } return schema; }, preBuild: function() { this._super(); this.editors = {}; this.cached_editors = {}; var self = this; this.format = this.options.layout || this.options.object_layout || this.schema.format || this.jsoneditor.options.object_layout || 'normal'; this.schema.properties = this.schema.properties || {}; this.minwidth = 0; this.maxwidth = 0; // If the object should be rendered as a table row if(this.options.table_row) { $each(this.schema.properties, function(key,schema) { var editor = self.jsoneditor.getEditorClass(schema); self.editors[key] = self.jsoneditor.createEditor(editor,{ jsoneditor: self.jsoneditor, schema: schema, path: self.path+'.'+key, parent: self, compact: true, required: true }); self.editors[key].preBuild(); var width = self.editors[key].options.hidden? 0 : (self.editors[key].options.grid_columns || self.editors[key].getNumColumns()); self.minwidth += width; self.maxwidth += width; }); this.no_link_holder = true; } // If the object should be rendered as a table else if(this.options.table) { // TODO: table display format throw "Not supported yet"; } // If the object should be rendered as a div else { if(!this.schema.defaultProperties) { if(this.jsoneditor.options.display_required_only || this.options.display_required_only) { this.schema.defaultProperties = []; $each(this.schema.properties, function(k,s) { if(self.isRequired({key: k, schema: s})) { self.schema.defaultProperties.push(k); } }); } else { self.schema.defaultProperties = Object.keys(self.schema.properties); } } // Increase the grid width to account for padding self.maxwidth += 1; $each(this.schema.defaultProperties, function(i,key) { self.addObjectProperty(key, true); if(self.editors[key]) { self.minwidth = Math.max(self.minwidth,(self.editors[key].options.grid_columns || self.editors[key].getNumColumns())); self.maxwidth += (self.editors[key].options.grid_columns || self.editors[key].getNumColumns()); } }); } // Sort editors by propertyOrder this.property_order = Object.keys(this.editors); this.property_order = this.property_order.sort(function(a,b) { var ordera = self.editors[a].schema.propertyOrder; var orderb = self.editors[b].schema.propertyOrder; if(typeof ordera !== "number") ordera = 1000; if(typeof orderb !== "number") orderb = 1000; return ordera - orderb; }); }, build: function() { var self = this; // If the object should be rendered as a table row if(this.options.table_row) { this.editor_holder = this.container; $each(this.editors, function(key,editor) { var holder = self.theme.getTableCell(); self.editor_holder.appendChild(holder); editor.setContainer(holder); editor.build(); editor.postBuild(); if(self.editors[key].options.hidden) { holder.style.display = 'none'; } if(self.editors[key].options.input_width) { holder.style.width = self.editors[key].options.input_width; } }); } // If the object should be rendered as a table else if(this.options.table) { // TODO: table display format throw "Not supported yet"; } // If the object should be rendered as a div else { this.header = document.createElement('span'); this.header.textContent = this.getTitle(); this.title = this.theme.getHeader(this.header); this.container.appendChild(this.title); this.container.style.position = 'relative'; // Edit JSON modal this.editjson_holder = this.theme.getModal(); this.editjson_textarea = this.theme.getTextareaInput(); this.editjson_textarea.style.height = '170px'; this.editjson_textarea.style.width = '300px'; this.editjson_textarea.style.display = 'block'; this.editjson_save = this.getButton('Save','save','Save'); this.editjson_save.addEventListener('click',function(e) { e.preventDefault(); e.stopPropagation(); self.saveJSON(); }); this.editjson_cancel = this.getButton('Cancel','cancel','Cancel'); this.editjson_cancel.addEventListener('click',function(e) { e.preventDefault(); e.stopPropagation(); self.hideEditJSON(); }); this.editjson_holder.appendChild(this.editjson_textarea); this.editjson_holder.appendChild(this.editjson_save); this.editjson_holder.appendChild(this.editjson_cancel); // Manage Properties modal this.addproperty_holder = this.theme.getModal(); this.addproperty_list = document.createElement('div'); this.addproperty_list.style.width = '295px'; this.addproperty_list.style.maxHeight = '160px'; this.addproperty_list.style.padding = '5px 0'; this.addproperty_list.style.overflowY = 'auto'; this.addproperty_list.style.overflowX = 'hidden'; this.addproperty_list.style.paddingLeft = '5px'; this.addproperty_list.setAttribute('class', 'property-selector'); this.addproperty_add = this.getButton('add','add','add'); this.addproperty_input = this.theme.getFormInputField('text'); this.addproperty_input.setAttribute('placeholder','Property name...'); this.addproperty_input.style.width = '220px'; this.addproperty_input.style.marginBottom = '0'; this.addproperty_input.style.display = 'inline-block'; this.addproperty_add.addEventListener('click',function(e) { e.preventDefault(); e.stopPropagation(); if(self.addproperty_input.value) { if(self.editors[self.addproperty_input.value]) { window.alert('there is already a property with that name'); return; } self.addObjectProperty(self.addproperty_input.value); if(self.editors[self.addproperty_input.value]) { self.editors[self.addproperty_input.value].disable(); } self.onChange(true); } }); this.addproperty_holder.appendChild(this.addproperty_list); this.addproperty_holder.appendChild(this.addproperty_input); this.addproperty_holder.appendChild(this.addproperty_add); var spacer = document.createElement('div'); spacer.style.clear = 'both'; this.addproperty_holder.appendChild(spacer); // Description if(this.schema.description) { this.description = this.theme.getDescription(this.schema.description); this.container.appendChild(this.description); } // Validation error placeholder area this.error_holder = document.createElement('div'); this.container.appendChild(this.error_holder); // Container for child editor area this.editor_holder = this.theme.getIndentedPanel(); this.container.appendChild(this.editor_holder); // Container for rows of child editors this.row_container = this.theme.getGridContainer(); this.editor_holder.appendChild(this.row_container); $each(this.editors, function(key,editor) { var holder = self.theme.getGridColumn(); self.row_container.appendChild(holder); editor.setContainer(holder); editor.build(); editor.postBuild(); }); // Control buttons this.title_controls = this.theme.getHeaderButtonHolder(); this.editjson_controls = this.theme.getHeaderButtonHolder(); this.addproperty_controls = this.theme.getHeaderButtonHolder(); this.title.appendChild(this.title_controls); this.title.appendChild(this.editjson_controls); this.title.appendChild(this.addproperty_controls); // Show/Hide button this.collapsed = false; this.toggle_button = this.getButton('','collapse',this.translate('button_collapse')); this.title_controls.appendChild(this.toggle_button); this.toggle_button.addEventListener('click',function(e) { e.preventDefault(); e.stopPropagation(); if(self.collapsed) { self.editor_holder.style.display = ''; self.collapsed = false; self.setButtonText(self.toggle_button,'','collapse',self.translate('button_collapse')); } else { self.editor_holder.style.display = 'none'; self.collapsed = true; self.setButtonText(self.toggle_button,'','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'; } // Edit JSON Button this.editjson_button = this.getButton('JSON','edit','Edit JSON'); this.editjson_button.addEventListener('click',function(e) { e.preventDefault(); e.stopPropagation(); self.toggleEditJSON(); }); this.editjson_controls.appendChild(this.editjson_button); this.editjson_controls.appendChild(this.editjson_holder); // Edit JSON Buttton disabled if(this.schema.options && typeof this.schema.options.disable_edit_json !== "undefined") { if(this.schema.options.disable_edit_json) this.editjson_button.style.display = 'none'; } else if(this.jsoneditor.options.disable_edit_json) { this.editjson_button.style.display = 'none'; } // Object Properties Button this.addproperty_button = this.getButton('Properties','edit','Object Properties'); this.addproperty_button.addEventListener('click',function(e) { e.preventDefault(); e.stopPropagation(); self.toggleAddProperty(); }); this.addproperty_controls.appendChild(this.addproperty_button); this.addproperty_controls.appendChild(this.addproperty_holder); this.refreshAddProperties(); } // Fix table cell ordering if(this.options.table_row) { this.editor_holder = this.container; $each(this.property_order,function(i,key) { self.editor_holder.appendChild(self.editors[key].container); }); } // Layout object editors in grid if needed else { // Initial layout this.layoutEditors(); // Do it again now that we know the approximate heights of elements this.layoutEditors(); } }, showEditJSON: function() { if(!this.editjson_holder) return; this.hideAddProperty(); // Position the form directly beneath the button // TODO: edge detection this.editjson_holder.style.left = this.editjson_button.offsetLeft+"px"; this.editjson_holder.style.top = this.editjson_button.offsetTop + this.editjson_button.offsetHeight+"px"; // Start the textarea with the current value this.editjson_textarea.value = JSON.stringify(this.getValue(),null,2); // Disable the rest of the form while editing JSON this.disable(); this.editjson_holder.style.display = ''; this.editjson_button.disabled = false; this.editing_json = true; }, hideEditJSON: function() { if(!this.editjson_holder) return; if(!this.editing_json) return; this.editjson_holder.style.display = 'none'; this.enable(); this.editing_json = false; }, saveJSON: function() { if(!this.editjson_holder) return; try { var json = JSON.parse(this.editjson_textarea.value); this.setValue(json); this.hideEditJSON(); } catch(e) { window.alert('invalid JSON'); throw e; } }, toggleEditJSON: function() { if(this.editing_json) this.hideEditJSON(); else this.showEditJSON(); }, insertPropertyControlUsingPropertyOrder: function (property, control, container) { var propertyOrder; if (this.schema.properties[property]) propertyOrder = this.schema.properties[property].propertyOrder; if (typeof propertyOrder !== "number") propertyOrder = 1000; control.propertyOrder = propertyOrder; for (var i = 0; i < container.childNodes.length; i++) { var child = container.childNodes[i]; if (control.propertyOrder < child.propertyOrder) { this.addproperty_list.insertBefore(control, child); control = null; break; } } if (control) { this.addproperty_list.appendChild(control); } }, addPropertyCheckbox: function(key) { var self = this; var checkbox, label, labelText, control; checkbox = self.theme.getCheckbox(); checkbox.style.width = 'auto'; if (this.schema.properties[key] && this.schema.properties[key].title) labelText = this.schema.properties[key].title; else labelText = key; label = self.theme.getCheckboxLabel(labelText); control = self.theme.getFormControl(label,checkbox); control.style.paddingBottom = control.style.marginBottom = control.style.paddingTop = control.style.marginTop = 0; control.style.height = 'auto'; //control.style.overflowY = 'hidden'; this.insertPropertyControlUsingPropertyOrder(key, control, this.addproperty_list); checkbox.checked = key in this.editors; checkbox.addEventListener('change',function() { if(checkbox.checked) { self.addObjectProperty(key); } else { self.removeObjectProperty(key); } self.onChange(true); }); self.addproperty_checkboxes[key] = checkbox; return checkbox; }, showAddProperty: function() { if(!this.addproperty_holder) return; this.hideEditJSON(); // Position the form directly beneath the button // TODO: edge detection this.addproperty_holder.style.left = this.addproperty_button.offsetLeft+"px"; this.addproperty_holder.style.top = this.addproperty_button.offsetTop + this.addproperty_button.offsetHeight+"px"; // Disable the rest of the form while editing JSON this.disable(); this.adding_property = true; this.addproperty_button.disabled = false; this.addproperty_holder.style.display = ''; this.refreshAddProperties(); }, hideAddProperty: function() { if(!this.addproperty_holder) return; if(!this.adding_property) return; this.addproperty_holder.style.display = 'none'; this.enable(); this.adding_property = false; }, toggleAddProperty: function() { if(this.adding_property) this.hideAddProperty(); else this.showAddProperty(); }, removeObjectProperty: function(property) { if(this.editors[property]) { this.editors[property].unregister(); delete this.editors[property]; this.refreshValue(); this.layoutEditors(); } }, addObjectProperty: function(name, prebuild_only) { var self = this; // Property is already added if(this.editors[name]) return; // Property was added before and is cached if(this.cached_editors[name]) { this.editors[name] = this.cached_editors[name]; if(prebuild_only) return; this.editors[name].register(); } // New property else { if(!this.canHaveAdditionalProperties() && (!this.schema.properties || !this.schema.properties[name])) { return; } var schema = self.getPropertySchema(name); // Add the property var editor = self.jsoneditor.getEditorClass(schema); self.editors[name] = self.jsoneditor.createEditor(editor,{ jsoneditor: self.jsoneditor, schema: schema, path: self.path+'.'+name, parent: self }); self.editors[name].preBuild(); if(!prebuild_only) { var holder = self.theme.getChildEditorHolder(); self.editor_holder.appendChild(holder); self.editors[name].setContainer(holder); self.editors[name].build(); self.editors[name].postBuild(); } self.cached_editors[name] = self.editors[name]; } // If we're only prebuilding the editors, don't refresh values if(!prebuild_only) { self.refreshValue(); self.layoutEditors(); } }, onChildEditorChange: function(editor) { this.refreshValue(); this._super(editor); }, canHaveAdditionalProperties: function() { if (typeof this.schema.additionalProperties === "boolean") { return this.schema.additionalProperties; } return !this.jsoneditor.options.no_additional_properties; }, destroy: function() { $each(this.cached_editors, function(i,el) { el.destroy(); }); if(this.editor_holder) this.editor_holder.innerHTML = ''; if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title); if(this.error_holder && this.error_holder.parentNode) this.error_holder.parentNode.removeChild(this.error_holder); this.editors = null; this.cached_editors = null; if(this.editor_holder && this.editor_holder.parentNode) this.editor_holder.parentNode.removeChild(this.editor_holder); this.editor_holder = null; this._super(); }, getValue: function() { var result = this._super(); if(this.jsoneditor.options.remove_empty_properties || this.options.remove_empty_properties) { for(var i in result) { if(result.hasOwnProperty(i)) { if(!result[i]) delete result[i]; } } } return result; }, refreshValue: function() { this.value = {}; var self = this; for(var i in this.editors) { if(!this.editors.hasOwnProperty(i)) continue; this.value[i] = this.editors[i].getValue(); } if(this.adding_property) this.refreshAddProperties(); }, refreshAddProperties: function() { if(this.options.disable_properties || (this.options.disable_properties !== false && this.jsoneditor.options.disable_properties)) { this.addproperty_controls.style.display = 'none'; return; } var can_add = false, can_remove = false, num_props = 0, i, show_modal = false; // Get number of editors for(i in this.editors) { if(!this.editors.hasOwnProperty(i)) continue; num_props++; } // Determine if we can add back removed properties can_add = this.canHaveAdditionalProperties() && !(typeof this.schema.maxProperties !== "undefined" && num_props >= this.schema.maxProperties); if(this.addproperty_checkboxes) { this.addproperty_list.innerHTML = ''; } this.addproperty_checkboxes = {}; // Check for which editors can't be removed or added back for(i in this.cached_editors) { if(!this.cached_editors.hasOwnProperty(i)) continue; this.addPropertyCheckbox(i); if(this.isRequired(this.cached_editors[i]) && i in this.editors) { this.addproperty_checkboxes[i].disabled = true; } if(typeof this.schema.minProperties !== "undefined" && num_props <= this.schema.minProperties) { this.addproperty_checkboxes[i].disabled = this.addproperty_checkboxes[i].checked; if(!this.addproperty_checkboxes[i].checked) show_modal = true; } else if(!(i in this.editors)) { if(!can_add && !this.schema.properties.hasOwnProperty(i)) { this.addproperty_checkboxes[i].disabled = true; } else { this.addproperty_checkboxes[i].disabled = false; show_modal = true; } } else { show_modal = true; can_remove = true; } } if(this.canHaveAdditionalProperties()) { show_modal = true; } // Additional addproperty checkboxes not tied to a current editor for(i in this.schema.properties) { if(!this.schema.properties.hasOwnProperty(i)) continue; if(this.cached_editors[i]) continue; show_modal = true; this.addPropertyCheckbox(i); } // If no editors can be added or removed, hide the modal button if(!show_modal) { this.hideAddProperty(); this.addproperty_controls.style.display = 'none'; } // If additional properties are disabled else if(!this.canHaveAdditionalProperties()) { this.addproperty_add.style.display = 'none'; this.addproperty_input.style.display = 'none'; } // If no new properties can be added else if(!can_add) { this.addproperty_add.disabled = true; } // If new properties can be added else { this.addproperty_add.disabled = false; } }, isRequired: function(editor) { if(typeof editor.schema.required === "boolean") return editor.schema.required; else if(Array.isArray(this.schema.required)) return this.schema.required.indexOf(editor.key) > -1; else if(this.jsoneditor.options.required_by_default) return true; else return false; }, setValue: function(value, initial) { var self = this; value = value || {}; if(typeof value !== "object" || Array.isArray(value)) value = {}; // First, set the values for all of the defined properties $each(this.cached_editors, function(i,editor) { // Value explicitly set if(typeof value[i] !== "undefined") { self.addObjectProperty(i); editor.setValue(value[i],initial); } // Otherwise, remove value unless this is the initial set or it's required else if(!initial && !self.isRequired(editor)) { self.removeObjectProperty(i); } // Otherwise, set the value to the default else { editor.setValue(editor.getDefault(),initial); } }); $each(value, function(i,val) { if(!self.cached_editors[i]) { self.addObjectProperty(i); if(self.editors[i]) self.editors[i].setValue(val,initial); } }); this.refreshValue(); this.layoutEditors(); this.onChange(); }, 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 error for the table row if this is inside a table if(this.options.table_row) { if(my_errors.length) { this.theme.addTableRowError(this.container); } else { this.theme.removeTableRowError(this.container); } } // Show errors for child editors $each(this.editors, function(i,editor) { editor.showValidationErrors(other_errors); }); } });