/**
* @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)
{
$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();
$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 $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 $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)
{
$super();
this.enumInfos = []; // private field
},
/* @private */
/*
initProperties: function()
{
this.defineProp('allowUnsetValue', {'dataType': DataType.BOOL});
},
*/
/** @ignore */
setPropertyInfo: function($super, 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 = $super();
result = result | PEA.SUBPROPS;
return result;
},
/** @ignore */
hasSubPropertyEditors: function($super)
{
return $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 $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 = $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)
{
$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 = $super();
result = result | PEA.SUBPROPS;
return result;
},
/** @ignore */
hasSubPropertyEditors: function($super)
{
return $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 $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 = $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 = $super();
result = result | PEA.SUBPROPS;
return result;
},
/** @ignore */
hasSubPropertyEditors: function($super)
{
var v = this.getValue();
return $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 $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
}
);
})();