Commit 7d438d97 authored by Robert Lyon's avatar Robert Lyon Committed by Gerrit Code Review

Merge "Bug 1861252: remove JSON editor duplicated library code"

parents edafc556 4051b82a
......@@ -17,10 +17,23 @@ Changes:
Removed .github, tests and docs folders; Gruntfile.js, package.json,
package-lock.json, .gitattributes, .gitignore, npmignore, .npmrc and travis.yml
From src/iconlibs, removed bootstrap2.js, foundation2-3.js, materialicons.js
src/styles
src/templates - all but default
src/themes bootstrap2.js, foundation.js, materialize.js
And removed following directories and files since they are included in jsoneditor.js
editors/
iconlibs/
styles/
templates/
themes/
class.js
core.js
defaults.js
editor.js
iconlib.js
intro.js
jquery.js
outro.js
theme.js
utilities.js
validator.js
To allow changing of the text on the Edit JSON button:
in src/defaults.js, added:
......
/*jshint loopfunc: true */
/* Simple JavaScript Inheritance
* By John Resig http://ejohn.org/
* MIT Licensed.
*/
// Inspired by base2 and Prototype
var Class;
(function(){
var initializing = false, fnTest = /xyz/.test(function(){window.postMessage("xyz");}) ? /\b_super\b/ : /.*/;
// The base Class implementation (does nothing)
Class = function(){};
// Create a new Class that inherits from this class
Class.extend = function extend(prop) {
var _super = this.prototype;
// Instantiate a base class (but only create the instance,
// don't run the init constructor)
initializing = true;
var prototype = new this();
initializing = false;
// Copy the properties over onto the new prototype
for (var name in prop) {
// Check if we're overwriting an existing function
prototype[name] = typeof prop[name] == "function" &&
typeof _super[name] == "function" && fnTest.test(prop[name]) ?
(function(name, fn){
return function() {
var tmp = this._super;
// Add a new ._super() method that is the same method
// but on the super-class
this._super = _super[name];
// The method only need to be bound temporarily, so we
// remove it when we're done executing
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
};
})(name, prop[name]) :
prop[name];
}
// The dummy class constructor
function Class() {
// All construction is actually done in the init method
if ( !initializing && this.init )
this.init.apply(this, arguments);
}
// Populate our constructed prototype object
Class.prototype = prototype;
// Enforce the constructor to be what we expect
Class.prototype.constructor = Class;
// And make this class extendable
Class.extend = extend;
return Class;
};
return Class;
})();
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
JSONEditor.defaults.editors.arraySelectize = JSONEditor.AbstractEditor.extend({
build: function() {
this.title = this.theme.getFormInputLabel(this.getTitle());
this.title_controls = this.theme.getHeaderButtonHolder();
this.title.appendChild(this.title_controls);
this.error_holder = document.createElement('div');
if(this.schema.description) {
this.description = this.theme.getDescription(this.schema.description);
}
this.input = document.createElement('select');
this.input.setAttribute('multiple', 'multiple');
var group = this.theme.getFormControl(this.title, this.input, this.description);
this.container.appendChild(group);
this.container.appendChild(this.error_holder);
window.jQuery(this.input).selectize({
delimiter: false,
createOnBlur: true,
create: true
});
},
postBuild: function() {
var self = this;
this.input.selectize.on('change', function(event) {
self.refreshValue();
self.onChange(true);
});
},
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.input && this.input.parentNode) this.input.parentNode.removeChild(this.input);
this._super();
},
empty: function(hard) {},
setValue: function(value, initial) {
var self = this;
// Update the array's value, adding/removing rows when necessary
value = value || [];
if(!(Array.isArray(value))) value = [value];
this.input.selectize.clearOptions();
this.input.selectize.clear(true);
value.forEach(function(item) {
self.input.selectize.addOption({text: item, value: item});
});
this.input.selectize.setValue(value);
this.refreshValue(initial);
},
refreshValue: function(force) {
this.value = this.input.selectize.getValue();
},
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';
}
}
}
});
JSONEditor.defaults.editors.base64 = JSONEditor.AbstractEditor.extend({
getNumColumns: function() {
return 4;
},
setFileReaderListener: function (fr_multiple) {
var self = this;
fr_multiple.addEventListener("load", function(event) {
if (self.count == self.current_item_index) {
// Overwrite existing file by default, leave other properties unchanged
self.value[self.count][self.key] = event.target.result;
} else {
var temp_object = {};
// Create empty object
for (var key in self.parent.schema.properties) {
temp_object[key] = "";
}
// Set object media file
temp_object[self.key] = event.target.result;
self.value.splice(self.count, 0, temp_object); // insert new file object
}
// Increment using the listener and not the 'for' loop as the listener will be processed asynchronously
self.count += 1;
// When all files have been processed, update the value of the editor
if (self.count == (self.total+self.current_item_index)) {
self.arrayEditor.setValue(self.value);
}
});
},
build: function() {
var self = this;
this.title = this.header = this.label = this.theme.getFormInputLabel(this.getTitle());
if(this.options.infoText) this.infoButton = this.theme.getInfoButton(this.options.infoText);
// Input that holds the base64 string
this.input = this.theme.getFormInputField('hidden');
this.container.appendChild(this.input);
// Don't show uploader if this is readonly
if(!this.schema.readOnly && !this.schema.readonly) {
if(!window.FileReader) throw "FileReader required for base64 editor";
// File uploader
this.uploader = this.theme.getFormInputField('file');
// Set attribute of file input field to 'multiple' if:
// 'multiple' key has been set to 'true' in the schema
// and the parent object is of type 'object'
// and the parent of the parent type has been set to 'array'
if (self.schema.options && self.schema.options.multiple && self.schema.options.multiple == true && self.parent && self.parent.schema.type == 'object' && self.parent.parent && self.parent.parent.schema.type == 'array') {
this.uploader.setAttribute('multiple', '');
}
this.uploader.addEventListener('change',function(e) {
e.preventDefault();
e.stopPropagation();
if(this.files && this.files.length) {
// Check the amount of files uploaded.
// If 1, use the regular upload, otherwise use the multiple upload method
if (this.files.length>1 && self.schema.options && self.schema.options.multiple && self.schema.options.multiple == true && self.parent && self.parent.schema.type == 'object' && self.parent.parent && self.parent.parent.schema.type == 'array') {
// Load editor of parent.parent to get the array
self.arrayEditor = self.jsoneditor.getEditor(self.parent.parent.path);
// Check the current value of this editor
self.value = self.arrayEditor.getValue();
// Set variables for amount of files, index of current array item and
// count value containing current status of processed files
self.total = this.files.length;
self.current_item_index = parseInt(self.parent.key);
self.count = self.current_item_index;
for (var i = 0; i < self.total; i++) {
var fr_multiple = new FileReader();
self.setFileReaderListener(fr_multiple);
fr_multiple.readAsDataURL(this.files[i]);
}
} else {
var fr = new FileReader();
fr.onload = function(evt) {
self.value = evt.target.result;
self.refreshPreview();
self.onChange(true);
fr = null;
};
fr.readAsDataURL(this.files[0]);
}
}
});
}
this.preview = this.theme.getFormInputDescription(this.schema.description);
this.container.appendChild(this.preview);
this.control = this.theme.getFormControl(this.label, this.uploader||this.input, this.preview, this.infoButton);
this.container.appendChild(this.control);
},
refreshPreview: function() {
if(this.last_preview === this.value) return;
this.last_preview = this.value;
this.preview.innerHTML = '';
if(!this.value) return;
var mime = this.value.match(/^data:([^;,]+)[;,]/);
if(mime) mime = mime[1];
if(!mime) {
this.preview.innerHTML = '<em>Invalid data URI</em>';
}
else {
this.preview.innerHTML = '<strong>Type:</strong> '+mime+', <strong>Size:</strong> '+Math.floor((this.value.length-this.value.split(',')[0].length-1)/1.33333)+' bytes';
if(mime.substr(0,5)==="image") {
this.preview.innerHTML += '<br>';
var img = document.createElement('img');
img.style.maxWidth = '100%';
img.style.maxHeight = '100px';
img.src = this.value;
this.preview.appendChild(img);
}
}
},
enable: function() {
if(!this.always_disabled) {
if(this.uploader) this.uploader.disabled = false;
this._super();
}
},
disable: function(always_disabled) {
if(always_disabled) this.always_disabled = true;
if(this.uploader) this.uploader.disabled = true;
this._super();
},
setValue: function(val) {
if(this.value !== val) {
this.value = val;
this.input.value = this.value;
this.refreshPreview();
this.onChange();
}
},
destroy: function() {
if(this.preview && this.preview.parentNode) this.preview.parentNode.removeChild(this.preview);
if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title);
if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input);
if(this.uploader && this.uploader.parentNode) this.uploader.parentNode.removeChild(this.uploader);
this._super();
}
});
JSONEditor.defaults.editors.checkbox = JSONEditor.AbstractEditor.extend({
setValue: function(value,initial) {
this.value = !!value;
this.input.checked = this.value;
this.onChange();
},
register: function() {
this._super();
if(!this.input) return;
this.input.setAttribute('name',this.formname);
},
unregister: function() {
this._super();
if(!this.input) return;
this.input.removeAttribute('name');
},
getNumColumns: function() {
return Math.min(12,Math.max(this.getTitle().length/7,2));
},
build: function() {
var self = this;
if(!this.options.compact) {
this.label = this.header = this.theme.getCheckboxLabel(this.getTitle());
}
if(this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description);
if(this.options.infoText) this.infoButton = this.theme.getInfoButton(this.options.infoText);
if(this.options.compact) this.container.classList.add('compact');
this.input = this.theme.getCheckbox();
this.control = this.theme.getFormControl(this.label, this.input, this.description, this.infoButton);
if(this.schema.readOnly || this.schema.readonly) {
this.always_disabled = true;
this.input.disabled = true;
}
this.input.addEventListener('change',function(e) {
e.preventDefault();
e.stopPropagation();
self.value = this.checked;
self.onChange(true);
});
this.container.appendChild(this.control);
},
enable: function() {
if(!this.always_disabled) {
this.input.disabled = false;
this._super();
}
},
disable: function(always_disabled) {
if(always_disabled) this.always_disabled = true;
this.input.disabled = true;
this._super();
},
destroy: function() {
if(this.label && this.label.parentNode) this.label.parentNode.removeChild(this.label);
if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description);
if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input);
this._super();
},
showValidationErrors: function (errors) {
var self = this;
if (this.jsoneditor.options.show_errors === "always") {}
else if (!this.is_dirty && this.previous_error_setting === this.jsoneditor.options.show_errors) {
return;
}
this.previous_error_setting = this.jsoneditor.options.show_errors;
var messages = [];
$each(errors, function (i, error) {
if (error.path === self.path) {
messages.push(error.message);
}
});
this.input.controlgroup = this.control;
if (messages.length) {
this.theme.addInputError(this.input, messages.join('. ') + '.');
}
else {
this.theme.removeInputError(this.input);
}
}
});
/*
Edtended handling of date, time and datetime-local type fields.
Works with both string and integer data types. (default only support string type)
Adds support for setting "placeholder" through options.
Has optional support for using flatpickr datepicker.
All flatpickr options is supported with a few minor differences.
- "enableTime" and "noCalendar" are set automatically, based on the data type.
- Extra config option "errorDateFormat". If this is set, it will replace the format displayed in error messages.
- It is not possible to use "inline" and "wrap" options together.
- When using the "wrap" option, "toggle" and "clear" buttons are automatically added to markup. 2 extra boolean options ("showToggleButton" and "showClearButton") are available to control which buttons to display. Note: not all frameworks supports this. (Works in: Bootstrap and Foundation)
- When using the "inline" option, an extra boolean option ("inlineHideInput") is available to hide the original input field.
- If "mode" is set to either "multiple" or "range", only string data type is supported. Also the result from these is returned as a string not an array.
ToDo:
- Add support for "required" attribute. (Maybe this should be done on a general scale, as support for other input attributes are also missing, such as "placeholder")
- Test if validation works with "required" fields. (Not sure if I have to put this into custom validator, or if it's handled elsewhere. UPDATE required attribute is currently not supported at ALL!)
- Improve Handling of flatpicker "multiple" and "range" modes. (Currently the values are just added as string values, but the optimal scenario would be to save those as array if possible)
*/
JSONEditor.defaults.editors.datetime = JSONEditor.defaults.editors.string.extend({
build: function () {
this._super();
if(!this.input) return;
// Add required and placeholder text if available
if (this.options.placeholder !== undefined) this.input.setAttribute('placeholder', this.options.placeholder);
if(window.flatpickr && typeof this.options.flatpickr == 'object') {
// Make sure that flatpickr settings matches the input type
this.options.flatpickr.enableTime = this.schema.format == 'date' ? false : true;
this.options.flatpickr.noCalendar = this.schema.format == 'time' ? true : false;
// Curently only string can contain range or multiple values
if (this.schema.type == 'integer') this.options.flatpickr.mode = 'single';
// Attribute for flatpicker
this.input.setAttribute('data-input','');
var input = this.input;
if (this.options.flatpickr.wrap === true) {
// Create buttons for input group
var buttons = [];
if (this.options.flatpickr.showToggleButton !== false) {
var toggleButton = this.getButton('',this.schema.format == 'time' ? 'time' :'calendar', this.translate('flatpickr_toggle_button'));
// Attribute for flatpicker
toggleButton.setAttribute('data-toggle','');
buttons.push(toggleButton);
}
if (this.options.flatpickr.showClearButton !== false) {
var clearButton = this.getButton('','clear', this.translate('flatpickr_clear_button'));
// Attribute for flatpicker
clearButton.setAttribute('data-clear','');
buttons.push(clearButton);
}
// Save position of input field
var parentNode = this.input.parentNode, nextSibling = this.input.nextSibling;
var buttonContainer = this.theme.getInputGroup(this.input, buttons);
if (buttonContainer !== undefined) {
// Make sure "inline" option is turned off
this.options.flatpickr.inline = false;
// Insert container at same position as input field
parentNode.insertBefore(buttonContainer, nextSibling);
input = buttonContainer;
}
else {
this.options.flatpickr.wrap = false;
}
}
this.flatpickr = window.flatpickr(input, this.options.flatpickr);
if (this.options.flatpickr.inline === true && this.options.flatpickr.inlineHideInput === true) {
this.input.setAttribute('type','hidden');
}
}
},
getValue: function() {
if (!this.dependenciesFulfilled) {
return undefined;
}
if (this.schema.type == 'string') {
return this.value;
}
if (this.value === '' || this.value === undefined) {
return undefined;
}
var value = this.schema.format == 'time' ? '1970-01-01 ' + this.value : this.value;
return parseInt(new Date(value).getTime() / 1000);
},
setValue: function(value, initial, from_template) {
if (this.schema.type == 'string') {
this._super(value, initial, from_template);
}
else if (value > 0) {
var dateValue, dateObj = new Date(value * 1000),
year = dateObj.getFullYear(),
month = this.zeroPad(dateObj.getMonth() + 1),
day = this.zeroPad(dateObj.getDate()),
hour = this.zeroPad(dateObj.getHours()),
min = this.zeroPad(dateObj.getMinutes()),
sec = this.zeroPad(dateObj.getSeconds()),
date = [year, month, day].join('-'),
time = [hour, min, sec].join(':');
if (this.schema.format == 'date') dateValue = date;
else if (this.schema.format == 'time') dateValue = time;
else dateValue = date + ' ' + time;
this.input.value = dateValue;
}
},
destroy: function() {
if (this.flatpickr) this.flatpickr.destroy();
this.flatpickr = null;
this._super();
},
// helper function
zeroPad: function(value) {
return ('0' + value).slice(-2);
}
});
// Enum Editor (used for objects and arrays with enumerated values)
JSONEditor.defaults.editors["enum"] = JSONEditor.AbstractEditor.extend({
getNumColumns: function() {
return 4;
},
build: function() {
var container = this.container;
this.title = this.header = this.label = this.theme.getFormInputLabel(this.getTitle());
this.container.appendChild(this.title);
this.options.enum_titles = this.options.enum_titles || [];
this["enum"] = this.schema["enum"];
this.selected = 0;
this.select_options = [];
this.html_values = [];
var self = this;
for(var i=0; i<this["enum"].length; i++) {
this.select_options[i] = this.options.enum_titles[i] || "Value "+(i+1);
this.html_values[i] = this.getHTML(this["enum"][i]);
}
// Switcher
this.switcher = this.theme.getSwitcher(this.select_options);
this.container.appendChild(this.switcher);
// Display area
this.display_area = this.theme.getIndentedPanel();
this.container.appendChild(this.display_area);
if(this.options.hide_display) this.display_area.style.display = "none";
this.switcher.addEventListener('change',function() {
self.selected = self.select_options.indexOf(this.value);
self.value = self["enum"][self.selected];
self.refreshValue();
self.onChange(true);
});
this.value = this["enum"][0];
this.refreshValue();
if(this["enum"].length === 1) this.switcher.style.display = 'none';
},
refreshValue: function() {
var self = this;
self.selected = -1;
var stringified = JSON.stringify(this.value);
$each(this["enum"], function(i, el) {
if(stringified === JSON.stringify(el)) {
self.selected = i;
return false;
}
});
if(self.selected<0) {
self.setValue(self["enum"][0]);
return;
}
this.switcher.value = this.select_options[this.selected];
this.display_area.innerHTML = this.html_values[this.selected];
},
enable: function() {
if(!this.always_disabled) {
this.switcher.disabled = false;
this._super();
}
},
disable: function(always_disabled) {
if(always_disabled) this.always_disabled = true;
this.switcher.disabled = true;
this._super();
},
getHTML: function(el) {
var self = this;
if(el === null) {
return '<em>null</em>';
}