UNPKG

3dmol

Version:

JavaScript/TypeScript molecular visualization library

913 lines (720 loc) 26.6 kB
/** * This is a colection of contructor to make different input element * @function $3Dmol.UI#Form */ $3Dmol.UI.Form = (function () { /** * Create Color input * @function $3Dmol.UI#Form.Color * @param {Object} outerControl Reference object to store the value */ Form.Color = function (outerControl) { var redDot = $('<div></div>'); redDot.height(10); redDot.width(10); redDot.css('border-radius', '50%'); redDot.css('background', 'red'); redDot.css('margin-right', '3px'); var blueDot = redDot.clone(); blueDot.css('background', 'blue'); var greenDot = redDot.clone(); greenDot.css('background', 'green'); var control = this.control = { R: { value: 0, min: 0, max: 255, label: redDot }, G: { value: 0, min: 0, max: 255, label: greenDot }, B: { value: 0, min: 0, max: 255, label: blueDot }, }; var surroundingBox = this.ui = $('<div></div>') var boundingBox = $('<div></div>'); surroundingBox.append(boundingBox); var spectrumControl = { key: 'Spectrum', value: null } var spectrum = new Form.Checkbox(spectrumControl); boundingBox.append(spectrum.ui); spectrum.ui.css({ 'margin-left': '2px' }) var RValue = new Form.Slider(control.R); var GValue = new Form.Slider(control.G); var BValue = new Form.Slider(control.B); var sliders = $('<div></div>'); sliders.append(RValue.ui, GValue.ui, BValue.ui); var color = $('<div></div>'); boundingBox.append(sliders); boundingBox.append(color); // CSS RValue.slide.css('color', 'red'); // GValue.ui.css('display', 'block'); GValue.slide.css('color', 'green'); // BValue.ui.css('display', 'block'); BValue.slide.css('color', 'blue'); color.height(15); // color.width(50); color.css('margin-top', '6px'); color.css('margin-bottom', '6px'); color.css('border', '1px solid grey'); color.css('border-radius', '500px'); this.update = function () {}; var self = this; // Functionality function updatePreview() { var c = `rgb(${control.R.value}, ${control.G.value}, ${control.B.value})`; color.css('background', c); outerControl.value = c; self.update(control); } RValue.update = GValue.update = BValue.update = updatePreview; updatePreview(); spectrum.update = function (v) { sliders.toggle(); if (v.value) { color.css({ 'background': 'linear-gradient(to right, red, orange, yellow, green, blue, indigo, violet)' }); outerControl.value = 'spectrum'; } else { updatePreview(); } } this.getValue = function () { return outerControl; } this.validate = function () { return true; } this.setValue = function (colorValue) { if (colorValue == 'spectrum') { spectrum.setValue(true); spectrum.update(spectrumControl); sliders.hide(); outerControl.value = 'spectrum'; } } spectrum.ui.hide(); this.enableSpectrum = function () { spectrum.ui.show(); } } /** * Create ListInput input * @function $3Dmol.UI#Form.ListInput * @param {Object} control Reference object to store the value * @param {Array} listElements list of the elements through which options are generated */ Form.ListInput = function (control, listElements) { // var label = $('<div></div>'); // label.text(control.key); var surroundingBox = this.ui = $('<div></div>'); var boundingBox = $('<div></div>'); var itemList = listElements; // surroundingBox.append(label); surroundingBox.append(boundingBox); var select = $('<select></select>'); select.css($3Dmol.defaultCSS.ListInput.select); boundingBox.append(select); this.showAlertBox = true; var failMessage = $('<div></div>'); failMessage.text('Please select some value'); failMessage.css({ 'color': 'crimson', 'font-family': 'Arial', 'font-weight': 'bold', 'font-size': '10px' }); failMessage.hide(); boundingBox.append(failMessage); this.update = function () {} select.on('click', { parent: this }, (event) => { control.value = select.children('option:selected').val(); event.data.parent.update(control); }); this.getValue = () => { return control; } // this.preventAlertBox = function(){ // show // } this.validate = function () { if (control.value == 'select' || control.value == null) { (this.showAlertBox) ? failMessage.show(): null; select.css({ 'box-shadow': '0px 0px 2px red' }); return false; } else { failMessage.hide(); boundingBox.css({ 'box-shadow': 'none' }); return true; } } this.setValue = function (val) { if (listElements.indexOf(val) != -1) { select.empty(); var defaultOption = $('<option></option>'); defaultOption.text('select'); itemList.forEach((item) => { var option = $('<option></option>'); option.text(item); option.attr('value', item); select.append(option); if (val == item) { option.prop('selected', true); } }); control.value = select.children('option:selected').val(); } else { console.error('UI::Form::ListInput:incorrect value', val); } } this.updateList = function (newList) { select.empty(); var defaultOption = $('<option></option>'); defaultOption.text('select'); defaultOption.attr('value', 'select'); select.append(defaultOption); itemList = newList; itemList.forEach((item) => { var option = $('<option></option>'); option.text(item); option.attr('value', item); select.append(option); }); } this.updateList(itemList); } /** * Create text, numeric or range Input * @function $3Dmol.UI#Form.Input * @param {Object} control Reference object to store the value */ Form.Input = function (control) { var surroundingBox = this.ui = $('<div></div>'); var boundingBox = $('<div></div>'); // surroundingBox.append(label); surroundingBox.append(boundingBox); var validationType = this.validationType = 'text'; surroundingBox.css({ 'width': '100%', 'box-sizing': 'border-box' }) var input = this.domElement = $('<input type="text">'); boundingBox.append(input); var alertBox = $('<div></div>'); alertBox.css({ 'border': '1px solid darkred', 'border-radius': '3px', 'font-family': 'Arial', 'font-size': '10px', 'font-weight': 'bold', 'margin': '2px', 'margin-left': '4px', 'padding': '2px', 'color': 'darkred', 'background': 'lightcoral' }); var alertMessage = { 'invalid-input': 'Invalid input please check the value entered', } boundingBox.append(alertBox); alertBox.hide(); this.setWidth = function (width) { input.width(width - 6); } this.setWidth(75); input.css({ // 'margin-left': '4px' }); this.update = function () { } input.on('change', { parent: this, control: control }, (event) => { let inputString = input.val(); if (inputString[inputString.length - 1] == ',') { inputString = inputString.slice(0, -1); } if (validationType == 'range') { control.value = inputString.split(','); } else { control.value = inputString; } // calling update function event.data.parent.update(control); }); input.on('select', () => { // selectedText = input.val().substring(e.target.selectionStart, e.target.selectionEnd); }); this.getValue = () => { return control; } var error = this.error = function (msg) { alertBox.show(); alertBox.text(msg) } this.setValue = function (val) { if (validationType == 'range') { var text = val.join(','); input.val(text); } else { input.val(val); } control.value = val; } function checkInputFloat() { var inputString = input.val(); var dots = inputString.match(/\./g) || []; var checkString = inputString.replaceAll(/\./g, '').replaceAll(/[0-9]/g, ''); if (dots.length > 1) { return false } if (checkString != '') return false; if (isNaN(parseFloat(inputString))) { return false; } else { return true; } } function checkInputNumber() { var inputString = input.val(); var checkString = inputString.replaceAll(/[0-9]/g, ''); if (checkString != '') return false; if (isNaN(parseInt(inputString))) { return false; } else { return true; } } // Parse Input Range Functions // Checks only number, comma and hyphen present function checkRangeTokens(inputString) { var finalString = inputString.replaceAll(',', '').replaceAll('-', '').replaceAll(/[0-9]/g, '').replaceAll(' ', ''); if (finalString == '') return true; else return false; } function checkList(inputString) { inputString = inputString.replaceAll(' ', ''); if (inputString[inputString.length - 1] == ',') { inputString = inputString.slice(0, -1); } var rangeList = inputString.split(','); // If dublicate comma return false; if (/,,/g.exec(inputString)) return false; // If first element not a number return false; if (isNaN(parseInt(rangeList[0]))) return false; var validRangeList = rangeList.map((rangeInput) => { return checkRangeInput(rangeInput); }); return validRangeList.find((e) => { return e == false }) == undefined ? true : false; } function checkRangeInput(inputString) { var rangeInputs = inputString.split('-'); if (rangeInputs.length > 2) { return false; } else { if (rangeInputs.length == 0) { return true; } else if (rangeInputs.length == 1) { if (isNaN(parseInt(rangeInputs[0]))) return false; else return true; } else if (rangeInputs.length == 2) { if (isNaN(parseInt(rangeInputs[0])) || isNaN(parseInt(rangeInputs[1]))) return false; else return true; } else return false; } } var checkInput = this.checkInput = function () { var inputString = input.val(); if (validationType == 'number') { if (checkInputNumber()) { alertBox.hide(); return true; } else { error(alertMessage['invalid-input']); return false; } } else if (validationType == 'float') { if (checkInputFloat()) { alertBox.hide(); return true; } else { error(alertMessage['invalid-input']); return false; } } else if (validationType == 'range') { if (checkRangeTokens(inputString)) { if (checkList(inputString)) { alertBox.hide(); return true; } else { error(alertMessage['invalid-input']); return false; } } else { error(alertMessage['invalid-input']); return false; } } else { return true; } } this.validateOnlyNumber = function (floatType = false) { if (floatType) { validationType = 'float'; } else { validationType = 'number'; } input.on('keydown keyup paste cut', function () { checkInput(); }); } this.validateInputRange = function () { validationType = 'range'; input.on('keydown keyup paste cut', () => { checkInput(); }); } this.isEmpty = function () { if (control.value == "") { return true; } } this.validate = function () { if ((control.active == true && control.value != null && control.value != "" && checkInput()) || (control.active == false)) { input.css('box-shadow', 'none'); return true } else { input.css('box-shadow', '0px 0px 2px red'); return false; } } // CSS input.css($3Dmol.defaultCSS.Input.input); boundingBox.css($3Dmol.defaultCSS.Input.boundingBox); } /** * Create Checkbox input for boolean values * @function $3Dmol.UI#Form.Checkbox * @param {Object} control Reference object to store the value */ Form.Checkbox = function (control) { var label = $('<div></div>'); label.text(control.key); label.css($3Dmol.defaultCSS.TextDefault); var surroundingBox = this.ui = $('<div></div>'); var boundingBox = $('<div></div>'); surroundingBox.append(boundingBox); surroundingBox.append(label); var checkbox = $('<input type="checkbox" />'); boundingBox.append(checkbox); this.click = () => {}; this.update = function () { } this.getValue = () => { return control; } checkbox.on('click', { parent: this }, (event) => { control.value = checkbox.prop('checked'); event.data.parent.update(control); }); // CSS label.css('display', 'inline-block'); boundingBox.css('display', 'inline-block') this.validate = function () { return true; } this.setValue = function (val) { checkbox.prop('checked', val); this.update(control); control.value = val; } } /** * Create input for values between two numbers * @function $3Dmol.UI#Form.Slider * @param {Object} control Reference object to store the value */ Form.Slider = function (control) { var surroundingBox = this.ui = $('<div></div>'); var boundingBox = $('<div></div>'); surroundingBox.append(boundingBox); boundingBox.css('display', 'flex'); var slide = this.slide = $('<input type="range">'); slide.css('width', '100%'); var min = control.min || 0; var max = control.max || 100; var step = control.step || 1; var defaultValue = control.default || min; var labelContent = control.label || ''; var label = $('<div></div>'); label.append(labelContent); boundingBox.append(label); slide.attr('min', min); slide.attr('max', max); slide.attr('step', step); slide.attr('value', defaultValue); control.value = defaultValue; boundingBox.append(slide); var setValue = false; this.update = function () { }; this.getValue = () => { return control; } slide.on('mousedown', () => { setValue = true; }); slide.on('mousemove', { parent: this }, (event) => { if (setValue) { control.value = slide.val(); event.data.parent.update(control); } }); slide.on('mouseup', () => { setValue = false; }); // CSS boundingBox.css('align-items', 'center'); boundingBox.height('21px'); // boundingBox.css('border-radius', '2px'); // label.css('line-height', '21px'); slide.css('padding', '0px'); slide.css('margin', '0px'); this.validate = function () { return true; } this.setValue = function (val) { slide.val(val); control.value = slide.val(); } } /** * Create empty element used for property that whose input cannot be taken * @function $3Dmol.UI#Form.EmptyElement * @param {Object} control Reference object to store the value */ Form.EmptyElement = function (control) { this.ui = $('<div></div>'); this.onUpdate = () => { } this.getValue = () => { return control; } this.validate = function () { return true; } } // mainControl param will be used to take in specName // in the form of key // type will be 'form' // active will be used to activate deactivate form if more than one form /** * Creates Form input that takes input from different input element * * @function $3Dmol.UI#Form * @param {validSelectionSpec|validStyleSpec|validAtomSpec} specs the defination of spec is used as an input to generate the form * @param {Object} mainControl Reference of variable to store the value from the form */ function Form(specs, mainControl) { specs = specs || {}; var boundingBox = this.ui = $('<div></div>'); var heading = $('<div></div>'); heading.text(mainControl.key); // Styling heading heading.css({ 'border-bottom': '1px solid black', 'font-family': 'Arial', 'font-size': '14px', 'font-weight': 'bold', 'padding-top': '2px', 'padding-bottom': '4px' }); boundingBox.append(heading); boundingBox.addClass('form'); var inputs = this.inputs = []; // body.append(boundingBox); var keys = Object.keys(specs); keys.forEach((key) => { if (specs[key].gui) { var prop = new Property(key, specs[key].type); inputs.push(prop); boundingBox.append(prop.ui); } }); this.update = function () {} var update = () => { }; inputs.forEach((input) => { input.update = update; }) this.getValue = function () { mainControl.value = {}; inputs.forEach((input) => { var inputValue = input.getValue(); if (inputValue.active) { mainControl.value[inputValue.key] = inputValue.value; } }); return mainControl; } var updateValues = function (inputControl) { mainControl.value[inputControl.key] = mainControl.value; //control =? update(mainControl); } this.validate = function () { var validations = inputs.map((i) => { if (i.active.getValue().value) { return i.placeholder.validate(); } else { return true; } }); if (validations.find(e => e == false) == undefined) return true; else { return false; } } this.setValue = function (val) { var keys = Object.keys(val); for (var i = 0; i < keys.length; i++) { var input = inputs.find((e) => { if (e.control.key == keys[i]) return e; }); input.placeholder.setValue(val[keys[i]]); input.active.setValue(true); input.placeholder.ui.show(); input.control.active = true; } // mainControl.value = val; this.update(mainControl); this.getValue(); } this.getInputs = function () { return inputs; } function Property(key, type) { var control = this.control = { value: null, type: type, key: key, active: false }; var boundingBox = this.ui = $('<div></div>'); this.placeholder = { ui: $('<div></div>') }; // default value for ui element this.active = new Form.Checkbox({ value: false, key: key }); if (specs[key].type == 'string' || specs[key].type == 'element') { this.placeholder = new Form.Input(control); this.placeholder.ui.attr('type', 'text'); } else if (specs[key].type == 'number') { var slider = false; if (specs[key].min != undefined && specs[key].max != undefined && specs[key].default != undefined) { slider = true; } if (slider) { // if( specs[key].min && spec[key].max){ control.min = specs[key].min; control.max = specs[key].max; control.default = specs[key].default; control.step = specs[key].step || ((control.max - control.max) / 1000); this.placeholder = new Form.Slider(control); } else { this.placeholder = new Form.Input(control); this.placeholder.ui.attr('type', 'text'); this.placeholder.validateOnlyNumber(specs[key].floatType); } } else if (specs[key].type == 'array_range') { this.placeholder = new Form.Input(control); this.placeholder.ui.attr('type', 'text'); this.placeholder.validateInputRange(); } else if (specs[key].type == 'color') { this.placeholder = new Form.Color(control); if (specs[key].spectrum) { this.placeholder.enableSpectrum(); } } else if (specs[key].type == 'boolean') { this.placeholder = new Form.Checkbox(control); } else if (specs[key].type == 'properties') { this.placeholder = new Form.Input(control); this.placeholder.ui.attr('type', 'text'); } else if (specs[key].type == 'colorscheme') { this.placeholder = new Form.ListInput(control, Object.keys($3Dmol.builtinColorSchemes)); this.placeholder.ui.attr('type', 'text'); } else if (specs[key].type == undefined) { if (specs[key].validItems) { this.placeholder = new Form.ListInput(control, specs[key].validItems); } } else if (specs[key].type == 'form') { this.placeholder = new Form(specs[key].validItems, control); this.placeholder.ui.append($('<div></div>').css($3Dmol.defaultCSS.LinkBreak)); } else { this.placeholder = new Form.EmptyElement(control); // return new Form.EmptyElement(control); } this.getValue = function () { if (this.placeholder.getValue) return this.placeholder.getValue(); else return null; } // Adding active control for the property var placeholder = this.placeholder; if (type != 'boolean') { placeholder.ui.hide(); boundingBox.append(this.active.ui); this.active.update = function (c) { (c.value) ? placeholder.ui.show(): placeholder.ui.hide(); control.active = c.value; } } else { this.placeholder.update = function (c) { control.active = c.value; } } boundingBox.append(this.placeholder.ui); if (this.placeholder.onUpdate) this.placeholder.onUpdate(updateValues); } } return Form; })();