UNPKG

kekule

Version:

Open source JavaScript toolkit for chemoinformatics

1,493 lines (1,437 loc) 43.7 kB
/** * @fileoverview * Implementation of some basic property editor for object inspector. * @author Partridge Jiang */ /* * requires /lan/classes.js * requires /utils/kekule.utils.js * requires /widgets/kekule.widget.base.js * requires /widgets/commonCtrls/kekule.widget.formControls.js * requires /widgets/advCtrls/kekule.widget.colorPickers.js */ (function(){ "use strict"; var AU = Kekule.ArrayUtils; /** * Namespace for all property editors. * @namespace */ Kekule.Widget.PropertyEditor = {}; /** * Shortcur for {@link Kekule.Widget.PropertyEditor.} * @namespace */ Kekule.PropertyEditor = Kekule.Widget.PropertyEditor; /** * Property editor is association object to edit property row in * object inspector (similar to Delphi's property editor). * This is the base class of all property editors, containing a series of methods * that can be overrided to implement different styles of editors. * @class * @augments ObjectEx * * @property {Array} objects Objects currently been edited in object inspector and this property editor. * @property {Object} propInfo Information object of current property. * @property {Variant} propName Name of current property. Readonly. * Usually it is a string, but it may also be a int index when object is an array. * @property {Variant} propType Type of current property. Readonly. * //@property {Variant} originalValue Old value of property. * @property {Kekule.PropertyEditor.BaseEditor} parentEditor Parent property editor. * This property usually should be set by expandable parent editor. When child property is modified, * child will notify parent that the property has been changed. * @property {Int} valueTextMode Value from {@link Kekule.Widget.ValueListEditor.ValueDisplayMode}. * Simple or JSON, different value may cause different output text in object inspector. * @property {Bool} readOnly Whether this editor is read only. If this value is set to null or undefined, * actual read only value will be calculated automatically based on property info. * @property {Bool} allowEmpty Whether property value can be set to null when value text is set to ''. * Note that the effect of this property need to be implemented in concrete editor classes, * especially in saveEditValue method. */ Kekule.PropertyEditor.BaseEditor = Class.create(ObjectEx, /** @lends Kekule.PropertyEditor.BaseEditor# */ { /** @private */ CLASS_NAME: 'Kekule.PropertyEditor.BaseEditor', /** @constructs **/ initialize: function(/*$super*/) { this.tryApplySuper('initialize') /* $super() */; this._editWidget = null; }, /** @private */ initProperties: function() { this.defineProp('objects', {'dataType': DataType.ARRAY, 'serializable': false, 'setter': function(value) { var AU = Kekule.ArrayUtils; this.setPropStoreFieldValue('objects', AU.clone(AU.toArray(value))); // clone value to avoid the array be changed outside property editor } }); this.defineProp('propertyInfo', {'dataType': DataType.OBJECT, 'serializable': false}); // can not use propInfo, or will conflict with ObjectEx.getPropInfo method this.defineProp('propertyName', {'dataType': DataType.VARIANT, 'serializable': false, 'setter': null, 'getter': function() { var info = this.getPropertyInfo(); return info? info.name: null; } }); this.defineProp('propertyType', {'dataType': DataType.VARIANT, 'serializable': false, 'setter': null, 'getter': function() { var info = this.getPropertyInfo(); return info? info.dataType: null; } }); this.defineProp('parentEditor', {'dataType': 'Kekule.PropertyEditor.BaseEditor', 'serializable': false}); //this.defineProp('originalValue', {'dataType': DataType.VARIANT, 'serializable': false}); this.defineProp('valueTextMode', {'dataType': DataType.INT}); this.defineProp('readOnly', {'dataType': DataType.BOOL}); this.defineProp('allowEmpty', {'dataType': DataType.BOOL}); }, /** @ignore */ finalize: function(/*$super*/) { this.finalizeWidget(); this.tryApplySuper('finalize') /* $super() */; }, /** @private */ finalizeWidget: function() { if (this._editWidget) { this._editWidget.finalize(); this._editWidget = null; } }, /** * Returns currently created edit widget. * @returns {Kekule.Widget.BaseWidget} */ getEditWidget: function() { return this._editWidget; }, /** * Returns property or field value of object. * @param {Object} obj A plain object or instance of ObjectEx. * @param {String} propName If propName not set, obj itself will be returned. * @returns {Variant} * @private */ getObjPropValue: function(obj, propName) { if (Kekule.ObjUtils.isUnset(propName)) return obj; if (obj instanceof ObjectEx) { return obj.getPropValue(propName); } else if (DataType.isArrayValue(obj)) { return obj[propName]; } else if (obj instanceof Object) { return obj[propName]; } else return undefined; }, /** * Set property value to an object. * @param {Object} obj A plain object or instance of ObjectEx. * @param {String} propName * @param {Variant} propValue * @private */ setObjPropValue: function(obj, propName, propValue) { if (obj instanceof ObjectEx) { obj.setPropValue(propName, propValue); } else if (obj instanceof Object) { obj[propName] = propValue; } return this; }, /** * Returns property value of multiple objects. * If all objects have the same property value, this value will be returned. * Otherwise this method will return undefined. * @param {Array} objects * @param {String} propName If propName is not set, objects themselves will be returned. * @returns {Variant} * @private */ getObjsPropValue: function(objects, propName) { if (!objects) // || Kekule.ObjUtils.isUnset(propName)) return undefined; if (Kekule.ObjUtils.isUnset(propName)) return objects; var obj = objects[0]; var result = this.getObjPropValue(obj, propName); for (var i = 1, l = objects.length; i < l; ++i) { var obj = objects[i]; var value = this.getObjPropValue(obj, propName); if (value !== result) return undefined; } return result; }, /** * Set property value to multiple objects. * @param {Array} objects * @param {String} propName * @param {Variant} propValue * @private */ setObjsPropValue: function(objects, propName, propValue) { if (!objects || !propName) return this; for (var i = 0, l = objects.length; i < l; ++i) { var obj = objects[i]; this.setObjPropValue(obj, propName, propValue); } return this; }, /** * Whether the editor is a read only one. * @returns {Bool} */ isReadOnly: function() { return !!(this.getAttributes() & PEA.READONLY); }, /** * Whether the editor has sub property editors. * @returns {Bool} */ hasSubPropertyEditors: function() { var attrib = this.getAttributes(); return !!(attrib & PEA.SUBPROPS); }, // The following method can be override by descendants to implement different style of editors. /** * Returns attribute of editor. Different attribute causes different behavior and outlook in object inspector. * Descendants may override this method. * @returns {Int} */ getAttributes: function() { // default settings var result = PEA.MULTIOBJS | PEA.SIMPLEVALUE; var propInfo = this.getPropertyInfo(); if (Kekule.ObjUtils.isUnset(this.getReadOnly())) { if (propInfo && !propInfo.setter) result = result | PEA.READONLY; } else if (this.getReadOnly()) result = result | PEA.READONLY; return result; }, /** * Returns child property editors. This method will be used when SUBPROPS flag in property editor's attribute. * In object inspector, rows will be expanded according to child property editors. * Descendants may override this method. * @param {Array} propScopes * @returns {Array} */ getSubPropertyEditors: function(propScopes) { return null; }, /** * Returns property title to display in object inspector. Usually propInfo.title or propInfo.name. * Descendants seldom need to override this method. * @returns {String} */ getTitle: function() { var info = this.getPropertyInfo(); if (info) return (info.title || info.name); else return ''; }, /** * Returns property hint to display in object inspector. Usually this value is same with getTitle(). * Descendants seldom need to override this method. * @returns {String} */ getHint: function() { return this.getTitle(); }, /** * Returns property description. Usually propInfo.description. * Descendants seldom need to override this method. * @returns {String} */ getDescription: function() { var info = this.getPropertyInfo(); return info.description; }, /** * Returns property value of all objects. * Descendants may override this method. * @returns {Variant} */ getValue: function() { return this.getObjsPropValue(this.getObjects(), this.getPropertyName()); }, /** * Returns display text of property value. * Descendants may override this method. * @returns {String} */ getValueText: function() { var VDM = Kekule.Widget.ValueListEditor.ValueDisplayMode; var v = this.getValue(); var mode = this.getValueTextMode(); try { var result = (mode === VDM.JSON)? JSON.stringify(v): Kekule.ObjUtils.isUnset(v)? '': '' + v; } catch(e) // sometimes error in conversion to JSON { return '' + v; } return result; }, /** * Save property value back to objects. * @param {Variant} value */ setValue: function(value) { var result = this.setObjsPropValue(this.getObjects(), this.getPropertyName(), value); if (this.getParentEditor() && this.getParentEditor().notifyChildEditorValueChange) { this.getParentEditor().notifyChildEditorValueChange(this.getPropertyName(), value); } return result; }, /** * Create a custom edit widget and put old property value in it. * @param {Kekule.Widget.BaseWidget} parentWidget * @returns {Kekule.Widget.BaseWidget} */ createEditWidget: function(parentWidget) { var value = this.getValue(); //this.setOriginalValue(value); var result = this.doCreateEditWidget(parentWidget, value); if (result && result.setIsDirty) result.setIsDirty(false); this._editWidget = result; return result; }, /** * Do actual work of createEditWidget. * Descendants should override this method. * @param {Kekule.Widget.BaseWidget} parentWidget * //@param {Variant} propValue * @returns {Kekule.Widget.BaseWidget} */ doCreateEditWidget: function(parentWidget) { // do nothing here }, /* * Returns modified value in edit widget. This value is about to save to property. * @returns {Variant} */ /* getEditValue: function() { // do nothing here }, */ /** * Save edit value in edit widget back to property. * If the original value is not changed, false should be returned. Else true should be returned. * @returns {Variant} */ saveEditValue: function() { if (this._editWidget) { if (!this._editWidget.getIsDirty || this._editWidget.getIsDirty()) { var result = this.doSaveEditValue(); if (this._editWidget.setIsDirty) this._editWidget.setIsDirty(false); return result; } else { //console.log('not dirty', this.getClassName()); return false; } } else return false; }, /** * Do actual job of saveEditValue. Descendant should override this method. * @private */ doSaveEditValue: function() { }, /** * This method is called by child property editor (such as enum item editor) to notify the child property has been changed. * @param fieldName * @param fieldValue */ notifyChildEditorValueChange: function(fieldName, fieldValue) { // do nothing here } /* * Raise popup dialog (or other widgets) to edit the property. * Descendants may override this method. */ /* edit: function() { // do nothing here } */ }); /** * Enumeration of attributes of property editor. * Different value can be combinated by OR operation. * @class */ Kekule.PropertyEditor.EditorAttributes = { /** Property can be edit when multiple objects is selected in object inspector. **/ MULTIOBJS: 1, /** Property can be expand to multiple rows in object inspector (such as when property type is ObjectEx). */ SUBPROPS: 2, /** Property value is simple and can be edited by a text box. */ SIMPLEVALUE: 4, /** Show a drop down list and value can only be set from it. */ VALUELIST: 8, /** Show a custom widget to edit property value. */ CUSTOMEDIT: 16, /** Property value is read only and can not be edited. */ READONLY: 32, /** Edit widget can be created inside object inspector row. */ INLINE_EDIT: 128, /** Edit widget will be created as a drop down one after clicking a drop down button inside object inspector row. */ DROP_EDIT: 256, /** Edit widget will be created as a popup dialog after clicking a ellipse button inside object inspector row. */ POPUP_EDIT: 512 }; var PEA = Kekule.PropertyEditor.EditorAttributes; /** * Property editor manager. This class is used to select most suitable property editor for a certain property * in object inspector. * @class */ Kekule.PropertyEditor.EditorMananger = Class.create( /** @lends Kekule.PropertyEditor.EditorMananger# */ { /** @constructs */ initialize: function() { this._registeredItems = []; this._defaultItems = []; }, /** * Register a property editor. Usually at least one of propType, objClass, propName and matchFunc param should be set. * If nothing is set, a default editor will be registered. * @param {Class} editorClass Property editor class. * @param {String} propType Type of property, value from {@link DataType}. Set null to match all types. * @param {Class} objClass Target class. Property editor will only apply to properties in this class. Set null to match all classes. * @param {String} propName Property name. Property editor will only apply to property with this name. Set null to match all property names. * @param {Func} matchFunc A custom function to check if property editor matches on a property. The function is in the following form: * match(objClass, propInfo) * and should return a boolean result. */ register: function(editorClass, propType, objClass, propName, matchFunc) { var item = { 'editorClass': editorClass, 'propType': propType, 'objClass': objClass, 'propName': propName, 'matchFunc': matchFunc }; this._registeredItems.push(item); if (!propType && !objClass && !propName && !matchFunc) // default editor this._defaultItems.push(item); return this; }, /** * Unregister a property editor. * @param {Class} editorClass */ unregister: function(editorClass) { var items = this._registeredItems; for (var i = items.length - 1; i >= 0; --i) { var item = items[i]; if (item.editorClass === editorClass) items.splice(i, 1); } var items = this._defaultItems; for (var i = items.length - 1; i >= 0; --i) { var item = items[i]; if (item.editorClass === editorClass) items.splice(i, 1); } return this; }, /** @private */ _getDefaultItem: function() { var item = this._defaultItems; return item[item.length - 1]; }, /** @private */ _getSuperiorItem: function(item1, item2) { var fields = ['matchFunc', 'propName', 'objClass', 'propType']; for (var i = 0, l = fields.length; i < l; ++i) { if (!!item1[fields] === !!item2[fields]) continue; else return item1[fields]? item1: item2; } // all same return item1; }, /** @private */ _isMatchedPropType: function(srcType, targetType) { var result = (srcType === targetType); if (!result) { if (DataType.isObjectExType(srcType) && DataType.isObjectExType(targetType)) { var classObj1 = ClassEx.findClass(srcType); var classObj2 = ClassEx.findClass(targetType); result = ClassEx.isOrIsDescendantOf(classObj1, classObj2); } } return result; }, // The following test methods is used to select proper property editor. // These methods may returns three values: // 1: pass the test // 0: uncertain // -1: test failed, the property editor is not suitable _testOnPropType: function(editorItem, propInfo) { if (!editorItem.propType || !propInfo.dataType) return 0; if (this._isMatchedPropType(propInfo.dataType, editorItem.propType)) return 1; else return -1; }, _testOnObjClass: function(editorItem, objClass) { if (!editorItem.objClass || !objClass) return 0; else if (ClassEx.isOrIsDescendantOf(objClass, editorItem.objClass)) return 1; else return -1; }, _testOnPropName: function(editorItem, propInfo) { if (!editorItem.propName || !propInfo.name) return 0; else if (editorItem.propName === propInfo.name) return 1; else return -1; }, _testOnMatchFunc: function(editorItem, objClass, propInfo) { if (!editorItem.matchFunc) return 0; else if (editorItem.matchFunc(objClass, propInfo)) return 1; else return -1; }, /** * Find a suitable property editor class based on objClass and propInfo. * If nothing found, null will be returned. * @param {Class} objClass * @param {Object} propInfo * @returns {Class} */ findEditorClass: function(objClass, propInfo) { var result = null; var items = this._registeredItems; var matched = false; var matches = []; for (var i = items.length - 1; i >= 0; --i) { var item = items[i]; /* if (!propInfo.dataType) console.log(propInfo); */ /* matched = (!item.propType || this._isMatchedPropType(propInfo.dataType, item.propType)) && (!item.objClass || (ClassEx.isOrIsDescendantOf(objClass, item.objClass))) && (!item.propName || (item.propName === propInfo.name)) && (!item.matchFunc || item.matchFunc(objClass, propInfo)); */ var r1 = this._testOnPropType(item, propInfo); if (r1 < 0) // failed continue; var r2 = this._testOnObjClass(item, objClass); if (r2 < 0) continue; var r3 = this._testOnPropName(item, propInfo); if (r3 < 0) continue; var r4 = this._testOnMatchFunc(item, objClass, propInfo); if (r4 < 0) continue; matched = (r1 + r2 + r3 + r4) > 0; if (matched) { if (result) // check if the priority of previous found result and current one result = this._getSuperiorItem(result, item.editorClass); else result = item.editorClass; if (result && item.matchFunc) // max superioty, can pass all reset comparations break; } } if (!result) { var item = this._getDefaultItem(); result = item? item.editorClass: null; } return result; }, /** * Find a suitable property editor simply by property type and property name. * This method is useful to find property editor for some simple type value (such as object, array element). * @param {String} propType * @param {String} propName * @return {Class} */ findEditorClassForType: function(propType, propName) { var propInfo = { 'dataType': propType, 'name': propName }; return this.findEditorClass(null, propInfo); } }); Kekule.ClassUtils.makeSingleton(Kekule.PropertyEditor.EditorMananger); /** @ignore */ Kekule.PropertyEditor.findEditorClass = Kekule.PropertyEditor.EditorMananger.getInstance().findEditorClass.bind(Kekule.PropertyEditor.EditorMananger.getInstance()); /** @ignore */ Kekule.PropertyEditor.findEditorClassForType = Kekule.PropertyEditor.EditorMananger.getInstance().findEditorClassForType.bind(Kekule.PropertyEditor.EditorMananger.getInstance()); /** @ignore */ Kekule.PropertyEditor.register = Kekule.PropertyEditor.EditorMananger.getInstance().register.bind(Kekule.PropertyEditor.EditorMananger.getInstance()); /** @ignore */ Kekule.PropertyEditor.unregister = Kekule.PropertyEditor.EditorMananger.getInstance().unregister.bind(Kekule.PropertyEditor.EditorMananger.getInstance()); /** * A simple property editor that use a text box to edit property value. * @class * @augments Kekule.PropertyEditor.BaseEditor */ Kekule.PropertyEditor.SimpleEditor = Class.create(Kekule.PropertyEditor.BaseEditor, /** @lends Kekule.PropertyEditor.SimpleEditor# */ { /** @private */ CLASS_NAME: 'Kekule.PropertyEditor.SimpleEditor', /** * Convert string to proper value by targetType. * @param {String} str * @param {String} targetType Value from {@DataType}. * @private */ convertStrToValue: function(str, targetType) { if (DataType.isSimpleType(targetType)) { if (!str && this.getAllowEmpty()) return null; else return Kekule.StrUtils.convertToType(str, targetType); } else // can not convert return str; }, /** * Convert string into proper type of value and feedback to properties of objects. * Descendants may override this method. * @param {String} valueText */ setValueText: function(valueText) { if (valueText === this._originValueText) // value has not been changed, do nothing return false; var value; var ptype = this.getPropertyType(); if (ptype && !DataType.isSimpleType(ptype)) // can not set complex value, do nothing return false; if (ptype) // type found, convert text to proper value { value = this.convertStrToValue(valueText, ptype); } else // type not found, set value directly value = valueText; //this.setObjsPropValue(this.getObjects(), this.getPropertyName(), value); this.setValue(value); return true; }, // overided methods /** @ignore */ doCreateEditWidget: function(parentWidget) { var text = this.getValueText(); // save old text to compare with later edited value this._originValueText = text; var result = new Kekule.Widget.TextBox(parentWidget, text); return result; }, /** @ignore */ doSaveEditValue: function() { return this.setValueText(this.getEditWidget().getValue()); } }); Kekule.PropertyEditor.register(Kekule.PropertyEditor.SimpleEditor); // register as default editor /** * A property editor that use a text box to edit string property value. * An additional button is also create to popup a large text area to edit multiple strings. * @class * @augments Kekule.PropertyEditor.SimpleEditor */ Kekule.PropertyEditor.TextEditor = Class.create(Kekule.PropertyEditor.SimpleEditor, /** @lends Kekule.PropertyEditor.TextEditor# */ { /** @private */ CLASS_NAME: 'Kekule.PropertyEditor.TextEditor', /** @private */ LINE_BREAK_REPLACER: '\\n', /** @private */ textToSingleLine: function(text) { // since line break will be automatically removed in textbox, we need to replace them first return text.split('\n').join(this.LINE_BREAK_REPLACER); }, /** @private */ textToMultiLine: function(text) { //return text.replace(new RegExp(this.LINE_BREAK_REPLACER, 'g'), '\n'); return text.split(this.LINE_BREAK_REPLACER).join('\n'); }, // overided methods /** @ignore */ doCreateEditWidget: function(parentWidget) { this._parentWidget = parentWidget; var text = this.getValueText(); // save old text to compare with later edited value this._originValueText = text; var result = new Kekule.Widget.ButtonTextBox(parentWidget, this.textToSingleLine(text)); this._textbox = result; result.setButtonKind(Kekule.Widget.Button.Kinds.POPUP); result.addEventListener('buttonExecute', this.reactButtonExecute, this); return result; }, /** @private */ reactButtonExecute: function(e) { this.openPopupEditor(); }, /** @private */ openPopupEditor: function() { var dialog = this._popupDialog; if (!dialog) { dialog = this.createPopupDialog(); this._popupDialog = dialog; } var editor = this._popupEditor; editor.setValue(this.getValueText()); var self = this; dialog.openPopup(function(result) { if (result === Kekule.Widget.DialogButtons.OK) // feedback value { self._textbox.setValue(self.textToSingleLine(editor.getValue())); self._textbox.setIsDirty(true); //self._textbox.focus(); self.saveEditValue(); } }, this._textbox); }, /** @private */ createPopupDialog: function() { var doc = this._parentWidget.getDocument(); var result = new Kekule.Widget.Dialog( doc, this.getPropertyName(), [Kekule.Widget.DialogButtons.OK, Kekule.Widget.DialogButtons.CANCEL]); result.setLocation(Kekule.Widget.Location.CENTER_OR_FULLFILL); var editorClass = /*Kekule.Widget.TextEditor ||*/ Kekule.Widget.TextArea; var textArea = new editorClass(doc); //new Kekule.Widget.TextArea(doc); var style = textArea.getElement().style; // TODO: currently fixed here style.width = '40em'; style.height = '20em'; textArea.appendToWidget(result); this._popupEditor = textArea; return result; }, /** @ignore */ doSaveEditValue: function() { var text = this.getEditWidget().getValue(); text = this.textToMultiLine(text); if (text !== this._originValueText) { this.setValueText(text); return true; } else { return false; } } }); Kekule.PropertyEditor.register(Kekule.PropertyEditor.TextEditor, DataType.STRING); /** * A property editor that use a check box to edit boolean property value. * @class * @augments Kekule.PropertyEditor.BaseEditor */ Kekule.PropertyEditor.BoolEditor = Class.create(Kekule.PropertyEditor.BaseEditor, /** @lends Kekule.PropertyEditor.BoolEditor# */ { /** @private */ CLASS_NAME: 'Kekule.PropertyEditor.BoolEditor', /** @private */ boolToStr: function(value) { return Kekule.StrUtils.boolToStr(value); }, // overided methods /** @ignore */ doCreateEditWidget: function(parentWidget) { // save old text to compare with later edited value this._originValue = this.getValue(); var result = new Kekule.Widget.CheckBox(parentWidget, this._originValue); result.setText(this.boolToStr(this._originValue)); var self = this; result.addEventListener('valueChange', function(e) { var value = result.getChecked(); result.setText(self.boolToStr(value)); } ); return result; }, /** @ignore */ getValueText: function(/*$super*/) { var v = this.getValue(); if (Kekule.ObjUtils.isUnset(v)) // not true or false, value is undefined or null return Kekule.$L('WidgetTexts.S_VALUE_UNSET'); //Kekule.WidgetTexts.S_VALUE_UNSET; else return this.tryApplySuper('getValueText') /* $super() */; }, /** @ignore */ doSaveEditValue: function() { var value = this.getEditWidget().getChecked(); if (value !== this._originValue) { this.setValue(value); return true; } else return false; } }); Kekule.PropertyEditor.register(Kekule.PropertyEditor.BoolEditor, DataType.BOOL); /** * A property editor that use a select box to edit property value. * This is an abstract property editor, user should not use it directly. * @class * @augments Kekule.PropertyEditor.BaseEditor */ Kekule.PropertyEditor.SelectEditor = Class.create(Kekule.PropertyEditor.BaseEditor, /** @lends Kekule.PropertyEditor.SelectEditor# */ { /** @private */ CLASS_NAME: 'Kekule.PropertyEditor.SelectEditor', /** @ignore */ getValueText: function(/*$super*/) { var v = this.getValue(); var items = this.getSelectItems() || []; for (var i = 0, l = items.length; i < l; ++i) { var item = items[i]; if (item.value === v) return item.text; } return this.tryApplySuper('getValueText') /* $super() */; }, /** * Returns items need to be shown in select box. * Descendants need to override this method. * @returns {Array} * @private */ getSelectItems: function() { return []; }, // overided methods /** @ignore */ doCreateEditWidget: function(parentWidget) { var result = new Kekule.Widget.SelectBox(parentWidget, this.getSelectItems()); this._originalValue = this.getValue(); //console.log('set value', this._originalValue); result.setValue(this._originalValue); return result; }, /** @ignore */ doSaveEditValue: function() { var value = this.getEditWidget().getValue(); if (value !== this._originalValue) { this.setValue(value); return true; } else return false; } }); /** * A property editor that use a select box to edit enumeration value. * @class * @augments Kekule.PropertyEditor.SelectEditor * * //@property {Bool} allowUnsetValue If true, in list there will be a special item to set value "undefined" to property. */ Kekule.PropertyEditor.EnumEditor = Class.create(Kekule.PropertyEditor.SelectEditor, /** @lends Kekule.PropertyEditor.EnumEditor# */ { /** @private */ CLASS_NAME: 'Kekule.PropertyEditor.EnumEditor', /** @constructs */ initialize: function(/*$super*/) { this.tryApplySuper('initialize') /* $super() */; this.enumInfos = []; // private field }, /* @private */ /* initProperties: function() { this.defineProp('allowUnsetValue', {'dataType': DataType.BOOL}); }, */ /** @ignore */ setPropertyInfo: function(/*$super, */value) { this.tryApplySuper('setPropertyInfo', [value]) /* $super(value) */; if (value) // save enum info { var enumSrc = value.enumSource; if (enumSrc && DataType.isObjectValue(enumSrc)) { var fields = Kekule.ObjUtils.getOwnedFieldNames(enumSrc); this.enumInfos = []; for (var i = 0, l = fields.length; i < l; ++i) { this.enumInfos.push({'text': fields[i], 'value': enumSrc[fields[i]]}); } } } }, /** * Returns items need to be shown in select box. * Descendants need to override this method. * @returns {Array} * @private */ getSelectItems: function() { var v = this.getValue(); if (Kekule.ObjUtils.isUnset(v)) // has unset value { var result = Kekule.ArrayUtils.clone(this.enumInfos); result.unshift({'text': /*Kekule.WidgetTexts.S_VALUE_UNSET*/Kekule.$L('WidgetTexts.S_VALUE_UNSET'), 'value': v}); return result; } return this.enumInfos; } }); Kekule.PropertyEditor.register(Kekule.PropertyEditor.EnumEditor, null, null, null, function(objClass, propInfo) { return !!propInfo.enumSource; } ); /** * A property editor that to edit ObjectEx instance. * @class * @augments Kekule.PropertyEditor.BaseEditor */ Kekule.PropertyEditor.ObjectExEditor = Class.create(Kekule.PropertyEditor.BaseEditor, /** @lends Kekule.PropertyEditor.ObjectExEditor# */ { /** @private */ CLASS_NAME: 'Kekule.PropertyEditor.ObjectExEditor', // override methods /** @ignore */ getAttributes: function(/*$super*/) { var result = this.tryApplySuper('getAttributes') /* $super() */; result = result | PEA.SUBPROPS; return result; }, /** @ignore */ hasSubPropertyEditors: function(/*$super*/) { return this.tryApplySuper('hasSubPropertyEditors') /* $super() */ && this.getValue(); }, /** @ignore */ getSubPropertyEditors: function(propScopes) { /* // debug var editor = new Kekule.PropertyEditor.ObjectExEditor(); editor.setValueTextMode(this.getValueTextMode()); editor.setObjects(this.getObjects()); editor.setPropertyInfo(this.getPropertyInfo()); return [editor, editor]; */ var result = []; var objs = this.getValue(); if (objs) { if (objs instanceof ObjectEx) // a single instance { var obj = objs; // iterate all properties of obj var objClass = obj.getClass(); } else // array of instances { var objClass = ClassEx.getCommonSuperClass(objs); } //var propList = obj.getAllPropList(); //var propList = obj.getPropListOfScopes(propScopes); var propList = ClassEx.getPropListOfScopes(objClass, propScopes); for (var i = 0, l = propList.getLength(); i < l; ++i) { var propInfo = propList.getPropInfoAt(i); var editorClass = Kekule.PropertyEditor.findEditorClass(objClass, propInfo); if (editorClass) { var editor = new editorClass(); editor.setObjects(Kekule.ArrayUtils.toArray(objs)); editor.setPropertyInfo(propInfo); // copy some settings editor.setValueTextMode(this.getValueTextMode()); result.push(editor); } } } return result; }, /** @ignore */ getValueText: function(/*$super*/) { var v = this.getValue(); if (Kekule.ObjUtils.notUnset(v)) { if (v.getClassName) return '[' + v.getClassName() + ']'; else return this.tryApplySuper('getValueText') /* $super() */; } else return Kekule.$L('WidgetTexts.S_OBJECT_UNSET'); //Kekule.WidgetTexts.S_OBJECT_UNSET; } }); Kekule.PropertyEditor.register(Kekule.PropertyEditor.ObjectExEditor, 'ObjectEx'); /** * A simple property editor that use a text box to edit a single field of a object (not objectEx). * @class * @augments Kekule.PropertyEditor.SimpleEditor * * //@property {String} fieldName * //@property {String} fieldType */ Kekule.PropertyEditor.ObjectFieldEditor = Class.create(Kekule.PropertyEditor.SimpleEditor, /** @lends Kekule.PropertyEditor.ObjectFieldEditor# */ { /** @private */ CLASS_NAME: 'Kekule.PropertyEditor.ObjectFieldEditor', /** @private */ initialize: function() { //this.defineProp('fieldName', {'dataType': DataType.STRING}); //this.defineProp('fieldType', {'dataType': DataType.STRING}); //this.defineProp('parentEditor', {'dataType': DataType.OBJECT}); }, // override methods /* getPropertyType: function() { return this.getFieldType(); }, getPropertyName: function() { return this.getFieldName(); }, getPropertyInfo: function() { return null; }, getTitle: function() { return this.getFieldName(); }, getValue: function() { var obj = this.getParentEditor().getValue(); return obj? obj[this.getFieldName()]: undefined; }, */ /** @ignore */ doCreateEditWidget: function(/*$super, */parentWidget) { if (!this.getPropertyType()) // guess type { var value = this.getValue(); this.getPropertyInfo().dataType = DataType.getType(value); } var result = this.tryApplySuper('doCreateEditWidget', [parentWidget]) /* $super(parentWidget) */; return result; } }); /** * A property editor that to edit a object (not ObjectEx) property. * @class * @augments Kekule.PropertyEditor.BaseEditor */ Kekule.PropertyEditor.ObjectEditor = Class.create(Kekule.PropertyEditor.BaseEditor, /** @lends Kekule.PropertyEditor.ObjectEditor# */ { /** @private */ CLASS_NAME: 'Kekule.PropertyEditor.ObjectEditor', /** @constructs */ initialize: function(/*$super*/) { this.tryApplySuper('initialize') /* $super() */; this._initialObjValue = undefined; // private }, /** * Returns property editor class to edit a specified field of obj. * Descendants may override this method. * @param {Array} objs * //@param {String} fieldName * @param {Hash} fieldInfo * @returns {Class} * @private */ getFieldEditorClass: function(objs, /*fieldName, dataType*/ fieldInfo) { //return Kekule.PropertyEditor.ObjectFieldEditor; //return Kekule.PropertyEditor.findEditorClassForType(dataType, fieldName) || Kekule.PropertyEditor.ObjectFieldEditor; return Kekule.PropertyEditor.findEditorClass(null, fieldInfo) || Kekule.PropertyEditor.ObjectFieldEditor; }, /** * Returns proper data type of a field of obj. Return null means "uncertain". * Descendants may override this method. * @param {Array} obj * @param {String} fieldName * @private */ getFieldValueType: function(obj, fieldName) { //return null; var value = obj[fieldName]; if (Kekule.ObjUtils.notUnset(value)) return DataType.getType(value); else return null; }, /** * Returns properly property editor for editing object field value. * Returned value should be an instance of {@link Kekule.PropertyEditor.ObjectEditor}. * Descendants may override this method. * @param {Array} objs * @param {Hash} fieldInfo * @returns {Kekule.PropertyEditor.ObjectEditor} * @private */ createFieldEditor: function(objs, fieldInfo) { //var dataType = this.getFieldValueType(objs, fieldName); var c = this.getFieldEditorClass(objs, fieldInfo); //console.log('create field editor', ClassEx.getClassName(c)); var result = new c(); result.setParentEditor(this); result.setReadOnly(false); // important, otherwise since there is no setter in propInfo, field editor will be auto set to read only. result.setAllowEmpty(this.getAllowEmpty()); //result.setPropertyInfo({'name': fieldName, 'dataType': dataType}); result.setPropertyInfo(fieldInfo); result.setObjects(objs); result.setValueTextMode(this.getValueTextMode()); /* if (dataType) result.setFieldType(dataType); */ return result; }, /** @private */ getObjFields: function(obj) { if (obj) return Kekule.ObjUtils.getOwnedFieldNames(obj); else return []; }, /** @private */ getObjFieldInfos: function(obj) { var fields = this.getObjFields(obj); var result = []; for (var i = 0, l = fields.length; i < l; ++i) { var info = {'name': fields[i], 'dataType': this.getFieldValueType(obj, fields[i])}; result.push(info); } return result; }, /** @private */ notifyChildEditorValueChange: function(fieldName, fieldValue) { var old = this.getValue() || this._initialObjValue; /* if (!old) old = {}; */ old[fieldName] = fieldValue; this.setValue(old); }, // override methods /** @ignore */ getAttributes: function(/*$super*/) { var result = this.tryApplySuper('getAttributes') /* $super() */; result = result | PEA.SUBPROPS; return result; }, /** @ignore */ hasSubPropertyEditors: function(/*$super*/) { return this.tryApplySuper('hasSubPropertyEditors') /* $super() */ && this.getObjFieldInfos(this.getValue()).length; }, /** @ignore */ getValueText: function(/*$super*/) { var VDM = Kekule.Widget.ValueListEditor.ValueDisplayMode; var mode = this.getValueTextMode(); if (mode === VDM.JSON) return this.tryApplySuper('getValueText') /* $super() */; var value = this.getValue(); if (value) { return '[' + /*Kekule.WidgetTexts.S_OBJECT*/Kekule.$L('WidgetTexts.S_OBJECT') + ']'; } else return ''; }, setValue: function(/*$super, */value) { var result = this.tryApplySuper('setValue', [value]) /* $super(value) */; this._initialObjValue = value; return result; }, /** @ignore */ getSubPropertyEditors: function(propScopes) { var result = []; var objs = AU.toArray(this.getValue()); if (objs.length > 1) // TODO: more than one object, currently can not handle return []; var obj = objs[0]; if (!obj) this._initialObjValue = {}; else this._initialObjValue = obj; //if (obj) // some editor need to expand even if obj is null { //var fields = this.getObjFields(obj); var fieldInfos = this.getObjFieldInfos(obj); var targets = [this._initialObjValue]; for (var i = 0, l = fieldInfos.length; i < l; ++i) { var editor = this.createFieldEditor(targets, fieldInfos[i]); result.push(editor); } } return result; } }); Kekule.PropertyEditor.register(Kekule.PropertyEditor.ObjectEditor, DataType.OBJECT); /** * A property editor that to edit an array property. * @class * @augments Kekule.PropertyEditor.BaseEditor */ Kekule.PropertyEditor.ArrayEditor = Class.create(Kekule.PropertyEditor.BaseEditor, /** @lends Kekule.PropertyEditor.ArrayEditor# */ { /** @private */ CLASS_NAME: 'Kekule.PropertyEditor.ArrayEditor', // override methods /** @ignore */ getAttributes: function(/*$super*/) { var result = this.tryApplySuper('getAttributes') /* $super() */; result = result | PEA.SUBPROPS; return result; }, /** @ignore */ hasSubPropertyEditors: function(/*$super*/) { var v = this.getValue(); return this.tryApplySuper('hasSubPropertyEditors') /* $super() */ && v && v.length; }, /** @ignore */ getSubPropertyEditors: function(propScopes) { var result = []; var v = this.getValue(); if (DataType.isArrayValue(v) && v.length) { var defItemType = this.getPropertyInfo().elementType; for (var i = 0, l = v.length; i < l; ++i) { var item = v[i]; var itemType = defItemType || DataType.getType(item); var editorClass = Kekule.PropertyEditor.findEditorClassForType(itemType); //console.log(itemType, ClassEx.getClassName(editorClass)); if (editorClass) { var editor = new editorClass(); editor.setParentEditor(this); editor.setObjects([v]); var fakePropInfo = {'name': i, 'title': i, 'dataType': itemType}; editor.setPropertyInfo(fakePropInfo); // copy some settings editor.setValueTextMode(this.getValueTextMode()); result.push(editor); } } } return result; }, /** @ignore */ getValueText: function(/*$super*/) { var v = this.getValue(); if (DataType.isArrayValue(v)) return '[' + v.length + ' ' + /*Kekule.WidgetTexts.S_ITEMS*/Kekule.$L('WidgetTexts.S_ITEMS') + ']'; else return this.tryApplySuper('getValueText') /* $super() */; } }); Kekule.PropertyEditor.register(Kekule.PropertyEditor.ArrayEditor, DataType.ARRAY); /** * A property editor that use a color drop down text box to edit color property value. * @class * @augments Kekule.PropertyEditor.SimpleEditor */ Kekule.PropertyEditor.ColorEditor = Class.create(Kekule.PropertyEditor.SimpleEditor, /** @lends Kekule.PropertyEditor.ColorEditor# */ { /** @private */ CLASS_NAME: 'Kekule.PropertyEditor.ColorEditor', // overided methods /** @ignore */ doCreateEditWidget: function(parentWidget) { this._parentWidget = parentWidget; var value = this.getValue(); // save old text to compare with later edited value this._originValue = value; var result = new Kekule.Widget.ColorDropTextBox(parentWidget, value); result.setSpecialColors([Kekule.Widget.ColorPicker.SpecialColors.UNSET]); result.addEventListener('valueSet', function(e) { this.saveEditValue(); }, this ); this._textbox = result; return result; }, /** @ignore */ doSaveEditValue: function() { var value = this.getEditWidget().getValue(); if (value === Kekule.Widget.ColorPicker.SpecialColors.UNSET) { value = undefined; } else if (value === '') value = undefined; if (value !== this._originValue) { this.setValue(value); return true; } else { return false; } } }); Kekule.PropertyEditor.register(Kekule.PropertyEditor.ColorEditor, DataType.STRING, null, null, function(objClass, propInfo) { var propName = propInfo.name; return propName && (propName.toLowerCase().indexOf('color') >= 0); // propname may be empty in array items } ); })();