kekule
Version:
Open source JavaScript toolkit for chemoinformatics
1,452 lines (1,359 loc) • 232 kB
JavaScript
/**
* @fileoverview
* Widget is a control embeded in HTML element and react to UI events (so it can interact with users).
* @author Partridge Jiang
*/
/*
* requires /lan/classes.js
* requires /core/kekule.common.js
* requires /utils/kekule.utils.js
* requires /utils/kekule.domUtils.js
* requires /xbrowsers/kekule.x.js
* requires /widgets/kekule.widget.root.js
* requires /widgets/kekule.widget.events.js
* requires /widgets/kekule.widget.keys.js
*/
(function(){
var AU = Kekule.ArrayUtils;
var EU = Kekule.HtmlElementUtils;
function _isNativePointerEventEnabled()
{
return !Kekule.globalOptions.widget.events.forceSimulatePointerEvent && Kekule.BrowserFeature.pointerEvent;
}
/**
* Enumeration of predefined widget html element tag names.
* @ignore
*/
Kekule.Widget.HtmlTagNames = {
CHILD_SLOT_HOLDER: 'slot',
CHILD_HOLDER: 'span'
};
/**
* Enumeration of predefined widget html element names.
* @ignore
*/
Kekule.Widget.HtmlNames = {
CHILD_HOLDER: 'children'
};
/**
* Enumeration of predefined widget element class names.
* @ignore
*/
Kekule.Widget.HtmlClassNames = {
/** A class name should add to all widget elements. */
BASE: 'K-Widget',
/** Child widget dynamic created by parent widget. */
DYN_CREATED: 'K-Dynamic-Created',
/** Container element to hold child widgets */
CHILD_HOLDER: 'K-Child-Holder',
/* A top most layer. */
TOP_LAYER: 'K-Top-Layer',
/** An isolated layer */
ISOLATED_LAYER: 'K-Isolated-Layer',
NORMAL_BACKGROUND: 'K-Normal-Background',
/** Indicate text in widget can not be selected. */
NONSELECTABLE: 'K-NonSelectable',
SELECTABLE: 'K-Selectable',
// widget style mode
STYLE_INHERITED: 'K-Style-Inherited', // the widget has no special styles like color/bgcolor and font, all inherited from document
STYLE_UNDEPENDENT: 'K-Style-Undependent', // the widget do not inherit styles like color/bgcolor and font from document
// State classes
/** Class name for all widget elements in normal (enabled) state. */
STATE_NORMAL: 'K-State-Normal',
/** Class name for all widget elements in disabled state. */
STATE_DISABLED: 'K-State-Disabled',
STATE_HOVER: 'K-State-Hover',
STATE_ACTIVE: 'K-State-Active',
STATE_FOCUSED: 'K-State-Focused',
STATE_SELECTED: 'K-State-Selected',
STATE_CURRENT_SELECTED: 'K-State-Current-Selected',
STATE_CHECKED: 'K-State-Checked',
STATE_READONLY: 'K-State-ReadOnly',
// show type
SHOW_POPUP: 'K-Show-Popup',
SHOW_DIALOG: 'K-Show-Dialog',
SHOW_ACTIVE_MODAL: 'K-Show-ActiveModal',
// section
SECTION: 'K-Section',
// parts
PART_CONTENT: 'K-Content',
PART_TEXT_CONTENT: 'K-Text-Content',
PART_ASSOC_TEXT_CONTENT: 'K-Assoc-Text-Content',
PART_IMG_CONTENT: 'K-Img-Content',
PART_GLYPH_CONTENT: 'K-Glyph-Content',
PART_PRI_GLYPH_CONTENT: 'K-Pri-Glyph-Content',
PART_ASSOC_GLYPH_CONTENT: 'K-Assoc-Glyph-Content',
PART_DECORATION_CONTENT: 'K-Decoration-Content',
PART_ERROR_REPORT: 'K-Error-Report',
// container
FIRST_CHILD: 'K-First-Child',
LAST_CHILD: 'K-Last-Child',
/*
BTN_GROUP_H: 'K-ButtonGroup-H',
BTN_GROUP_V: 'K-ButtonGroup-V',
*/
// text control
TEXT_NO_WRAP: 'K-No-Wrap',
// layout
LAYOUT_H: 'K-Layout-H',
LAYOUT_V: 'K-Layout-V',
LAYOUT_G: 'K-Layout-G', // grid
// outlook/decoration classes
CORNER_ALL: 'K-Corner-All',
CORNER_LEFT: 'K-Corner-Left',
CORNER_RIGHT: 'K-Corner-Right',
CORNER_TOP: 'K-Corner-Top',
CORNER_BOTTOM: 'K-Corner-Bottom',
CORNER_TL: 'K-Corner-TL',
CORNER_TR: 'K-Corner-TR',
CORNER_BL: 'K-Corner-BL',
CORNER_BR: 'K-Corner-BR',
CORNER_LEADING: 'K-Corner-Leading',
CORNER_TAILING: 'K-Corner-Tailing',
FULLFILL: 'K-Fulfill',
NOWRAP: 'K-No-Wrap',
HIDE_TEXT: 'K-Text-Hide',
HIDE_GLYPH: 'K-Glyph-Hide',
SHOW_TEXT: 'K-Text-Show',
SHOW_GLYPH: 'K-Glyph-Show',
MODAL_BACKGROUND: 'K-Modal-Background',
DUMB_WIDGET: 'K-Dumb-Widget',
PLACEHOLDER: 'K-PlaceHolder'
};
var CNS = Kekule.Widget.HtmlClassNames;
/**
* Enumeration of widget style dependent mode.
*/
Kekule.Widget.StyleMode = {
UNDEPENDENT: 0,
INHERITED: 1
};
/**
* Enumeration of layout of widget group.
*/
Kekule.Widget.Layout = {
HORIZONTAL: 1,
VERTICAL: 2,
GRID: 4
};
/**
* Enumeration of relative position of widget.
*/
Kekule.Widget.Position = {
AUTO: 0,
TOP: 1,
LEFT: 2,
BOTTOM: 4,
RIGHT: 8,
TOP_LEFT: 3,
TOP_RIGHT: 9,
BOTTOM_LEFT: 6,
BOTTOM_RIGHT: 12
};
/**
* Enumeration of directions.
* In some case, use can use the combination of directions, e.g. LTR | TTB.
*/
Kekule.Widget.Direction = {
/** Automatic direction. */
AUTO: 0,
/** Left to right. */
LTR: 1,
/** Top to bottom. */
TTB: 2,
/** Right to left. */
RTL: 4,
/** Bottom to top. */
BTT: 8,
/**
* Check if direction has horizontal component (LTR/RTL).
* @param {Int} direction
* @returns {Bool}
*/
isInHorizontal: function(direction)
{
var D = Kekule.Widget.Direction;
return !!((direction & D.LTR) || (direction & D.RTL));
},
/**
* Check if direction has vertical component (TTB/BTT).
* @param {Int} direction
* @returns {Bool}
*/
isInVertical: function(direction)
{
var D = Kekule.Widget.Direction;
return !!((direction & D.TTB) || (direction & D.BTT));
}
};
/**
* Enumeration of state of widget.
* @enum
*/
Kekule.Widget.State = {
NORMAL: 0,
FOCUSED: 1,
HOVER: 2,
ACTIVE: 3,
DISABLED: -1
};
/** @ignore */
var WS = Kekule.Widget.State;
/**
* Enumeration of mode of showing widget.
* @enum
*/
Kekule.Widget.ShowHideType = {
DROPDOWN: 1,
POPUP: 2,
DIALOG: 3,
DEFAULT: 0
};
/**
* Stores related consts of drag and drop methods
* @object
*/
Kekule.Widget.DragDrop = {
ELEM_INDEX_DATA_TYPE: 'application/x-kekule-dragdrop-elem-index'
};
/** @private */
Kekule.Widget._PointerHoldParams = {
DURATION_THRESHOLD: 1000, // ms
MOVEMENT_THRESHOLD: 10 // px
};
/** @ignore */
var widgetBindingField = '__$kekule_widget__';
/**
* An abstract UI widget.
* Event param invoked by widget will always has a 'widget' field indicate the widget raise the event.
* This value may not be same as event.target, e.g., a widget containing child widgets, when child widget
* invokes an event and bubbles to parent widget, parent widget may overwrite event.widget.
* @class
* @augments ObjectEx
*
* @param {Variant} HTMLElement or HTMLDocument or {@link Kekule.Widget.BaseWidget}.
* If it is an HTML element, the widget will bind to this one.
* If it is an HTML document, the widget will be created in it.
* If it is Kekule.Widget.BaseWidget, a new HTML element will be created and append in parent widget.
* @param {Bool} isDumb Whether the widget is a dumb one (do not react to events).
* This type of dumb widget is used to create some very light-weighted static widgets, in other word,
* just used to bind widget styles to some HTML element.
* @param {Bool} bubbleUiEvents Defaultly, ui event (mouseenter, keyup and so on) will only be handled by
* widget itself and will not bubble to higher level widget. Set this property to true to pass such events
* to parent widget.
* @param {Bool} inheritBubbleUiEvents When bubbleUiEvents value is inherited from parent widget.
* For example, if this.getBubbleUiEvents() == false but this.getParent().getBubbleUiEvents() == true,
* the ui events will still bubbled to parent widget.
*
* @property {Kekule.Widget.BaseWidget} parent Parent widget.
* @property {HTMLDocument} document HTML document contains this widget.
* @property {HTMLElement} element HTML element bind with this widget.
* @property {Bool} isDumb Whether the widget is a dumb one (do not react to events). Readonly.
* This type of dumb widget is used to create some very light-weighted static widgets, in other word,
* just used to bind widget styles to some HTML element.
* @property {Bool} observeElementAttribChanges If this property is true, when the attribute of binded element changed in DOM,
* the widget will also reflect to it.
* @property {String} id ID of corresponding HTML element.
* @property {String} width Width style of element.
* @property {String} height Height style of element.
* @property {String} innerHTML Current element's innerHTML value.
* @property {Int} tabIndex Tab index of widget element.
* @property {Object} style CSS style object of current binding element.
* @property {String} cssText CSS text of current binding element.
* @property {String} htmlClassName HTML class of current binding element. This property will include all values in element's class attribute.
* @property {String} customHtmlClassName HTML class set by user. This property will exclude some predefined class names.
* //@property {Array} outlookStyleClassNames Classes used to control the outlook of widget. Usually user do not need to access this value.
* @property {String} touchAction Touch action style value of widget element.
* You should set this value (e.g., to 'none') to enable pointer event on touch as describle by pep.js.
* @property {Hash} minDimension A {width, height} hash defines the min size of widget.
* @property {Bool} enableDimensionTransform If true, when setting size of widget by setDimension method
* and the size is less than minDimension, CSS3 transform scale will be used.
* @property {Bool} useCornerDecoration
* @property {Int} layout Layout of child widgets. Value from {@link Kekule.Widget.Layout}.
* @property {Bool} allowTextWrap
* @property {Bool} showText Whether show text content in widget.
* @property {Bool} showGlyph Whether show glyph content in widget.
* @property {Bool} visible Whether current bind element's visibility style is not 'hidden'.
* @property {Bool} displayed Whether current bind element's display style is not 'none'.
* @property {Bool} finalizeAfterHiding If true, this widget will be automatically be finalize
* after {@link Kekule.Widget.BaseWidget.hide} is called.
* @property {Bool} enabled Whether widget can reflect to user input. Default is true.
* @property {Bool} inheritEnabled If set to true, widget will be turned to disabled when parent is disabled.
* @property {Bool} static Whether this widget can react to interaction events.
* @property {Bool} inheritStatic If set to true, widget will be static if parent is static.
* @property {Int} state State (normal, focused, hover, active) of widget, value from {@link Kekule.Widget.State}. Readonly.
* @property {Bool} inheritState If set to true, widget will has the same state value of parent.
* @property {String} hint Hint of widget, actually mapping to title attribute of HTML element.
* @property {String} cursor CSS cursor property for widget.
* @property {Array} shortcuts An array of {@link Kekule.Widget.Shortcut}, shortcut keys of this widget.
* @property {Array} shortcutKeys Array of shortcut key strings.
* @property {Array} inheritedStyles Flags indicating which CSS properties should be set to 'inhertied'.
* e.g. ['color', 'fontSize'] (note: in JavaScript form rather than CSS form like 'font-size').
*
* @property {Bool} draggable Whether this widget is draggable, mapping to HTML draggable attribute.
* @property {Bool} droppable Whether this widget is a target of drag-drop.
* @property {Array} droppableDataKinds The data kinds that accepted by this widget in drag-drop. Array of strings.
* Default is null, means accept all kinds.
* @property {Bool} fileDroppable Whether external local files can be dropped to this widget.
* Same as droppableDataKinds.indexOf('file') >= 0.
*
* @property {Kekule.Action} action Action associated with widget. Excute the widget will invoke that action.
* @property {Bool} enablePeriodicalExec If this property is true, the execute event will be invoked repeatly between startPeriodicalExec and stopPeriodicalExec methods.
* (for instance, mousedown on button).
* @property {Int} periodicalExecDelay How many milliseconds should periodical execution begin after startPeriodicalExec is called.
* Available only when enablePeriodicalExec property is true.
* @property {Int} periodicalExecInterval Milliseconds between two execution in periodical mode.
* Available only when enablePeriodicalExec property is true.
* @property {Hash} autoResizeConstraints A hash of {width, height}, each value from 0-1 indicating the ratio of widget width/height to client.
* If this property is set, widget will automatically adjust its size when the browser window is resized.
* @property {Bool} autoAdjustSizeOnPopup Whether shrink to browser visible client size when popping up or dropping down.
* @property {Bool} observeElemResize Whether use a resize observer to detect the resizing of element and evoke the resized method in supported browser.
* @property {Bool} isPopup Whether this is a "popup" widget, when click elsewhere on window, the widget will automatically hide itself.
* @property {Bool} isDialog Whether this is a "dialog" widget, when press ESC key, the widget will automatically hide itself.
* @property {Kekule.HashEx} iaControllerMap Interaction controller map (id= > controller) linked to this component. Read only.
* @property {String} defIaControllerId Id of default interaction controller in map.
* @property {Kekule.Widget.InteractionController} defIaController Default interaction controller object.
* @property {String} activeIaControllerId Id of active interaction controller in map.
* @property {Kekule.Widget.InteractionController} activeIaController Active interaction controller object.
*/
/**
* Invoked when a widget object is bind to an HTML element.
* event param of it has fields: {widget, element}
* @name Kekule.Widget.BaseWidget#bind
* @event
*/
/**
* Invoked when a widget object is unbind from an HTML element.
* event param of it has fields: {widget, element}
* @name Kekule.Widget.BaseWidget#unbind
* @event
*/
/**
* Invoked when a widget is executed (such as click on button, select on menu and so on).
* event param of it has field: {widget}
* @name Kekule.Widget.BaseWidget#execute
* @event
*/
/**
* Invoked when a widget is activated (such as mouse down or enter key down on button).
* event param of it has field: {widget}
* @name Kekule.Widget.BaseWidget#activate
* @event
*/
/**
* Invoked when a widget is deactivated (such as mouse up or enter key up on button).
* event param of it has field: {widget}
* @name Kekule.Widget.BaseWidget#deactivate
* @event
*/
/**
* Invoked when a widget is shown or hidden.
* event param of it has field: {widget, isShown, isDismissed}
* @name Kekule.Widget.BaseWidget#showStateChange
* @event
*/
/**
* Invoked when a widget's width or height changed.
* event param of it has field: {widget}
* Note: This event will only be invoked when using width/height property or setDimension method to change size.
* Set CSS styles directly will not fire this event.
* @name Kekule.Widget.BaseWidget#resize
* @event
*/
/**
* Invoked when a widget is being dragged.
* event param of it has field: {widget, srcElem, htmlEvent}
* @name Kekule.Widget.BaseWidget#dragStart
* @event
*/
/**
* Invoked when dragging of this widget is ended.
* event param of it has field: {widget, srcElem, htmlEvent}
* @name Kekule.Widget.BaseWidget#dragEnd
* @event
*/
/**
* Invoked when object are dragging over this widget.
* event param of it has field: {widget, srcElem, srcWidget, srcFiles, dataTransfer, htmlEvent}
* @name Kekule.Widget.BaseWidget#dragOver
* @event
*/
/**
* Invoked when the dragging is leaving off this widget.
* event param of it has field: {widget, srcElem, srcWidget, srcFiles, dataTransfer, htmlEvent}
* @name Kekule.Widget.BaseWidget#dragLeave
* @event
*/
/**
* Invoked when object are dropping on this widget.
* event param of it has field: {widget, srcElem, srcWidget, srcFles, dataTransfer, htmlEvent}
* @name Kekule.Widget.BaseWidget#dragDrop
* @event
*/
Kekule.Widget.BaseWidget = Class.create(ObjectEx,
/** @lends Kekule.Widget.BaseWidget# */
{
/** @private */
CLASS_NAME: 'Kekule.Widget.BaseWidget',
/** @private */
BINDABLE_TAG_NAMES: null,
/** @private */
DEF_PERIODICAL_EXEC_DELAY: 500,
/** @private */
DEF_PERIODICAL_EXEC_INTERVAL: 100,
/** @private */
STYLE_RES_FIELD: '__$style_resources__',
/** @constructs */
initialize: function(/*$super, */parentOrElementOrDocument, isDumb)
{
this._stateClassName = null;
this._isDismissed = false;
this._pendingHtmlClassNames = '';
this._enableShowHideEvents = true;
this._reactElemAttribMutationBind = this._reactElemAttribMutation.bind(this);
this.reactTouchGestureBind = this.reactTouchGesture.bind(this);
this.setPropStoreFieldValue('inheritEnabled', true);
this.setPropStoreFieldValue('inheritStatic', true);
this.setPropStoreFieldValue('selfEnabled', true);
this.setPropStoreFieldValue('selfStatic', false);
this.setPropStoreFieldValue('periodicalExecDelay', this.DEF_PERIODICAL_EXEC_DELAY);
this.setPropStoreFieldValue('periodicalExecInterval', this.DEF_PERIODICAL_EXEC_INTERVAL);
this.setPropStoreFieldValue('useNormalBackground', true);
this.setPropStoreFieldValue('inheritedStyles', []);
//this.setPropStoreFieldValue('touchAction', 'none'); // debug: set to none disable default touch actions
this.setPropStoreFieldValue('droppableDataKinds', ['string']); // defaultly disallow file drop
this.setPropStoreFieldValue('htmlEventDispatcher', new Kekule.Widget.HtmlEventDispatcher());
this._touchActionNoneTouchStartHandlerBind = this._touchActionNoneTouchStartHandler.bind(this);
this.tryApplySuper('initialize') /* $super() */;
this.setPropStoreFieldValue('isDumb', !!isDumb);
if (!isDumb)
this.reactUiEventBind = this.reactUiEvent.bind(this);
/*
this.setShowText(true);
this.setShowGlyph(true);
*/
if (parentOrElementOrDocument)
{
if (parentOrElementOrDocument instanceof Kekule.Widget.BaseWidget)
{
this.setDocument(parentOrElementOrDocument.getDocument());
this.createElement();
this.setParent(parentOrElementOrDocument);
}
else if (parentOrElementOrDocument.documentElement) // is document
{
this.setDocument(parentOrElementOrDocument);
this.createElement();
}
else // is HTML element
{
this.setDocument(parentOrElementOrDocument.ownerDocument);
this.setElement(parentOrElementOrDocument);
}
}
this._stateClassName = null;
this._layoutClassName = null;
if (!this.getLayout())
this.setLayout(Kekule.Widget.Layout.HORIZONTAL);
//this.setDraggable(false);
this._periodicalExecBind = this._periodicalExec.bind(this);
//this.setBubbleEvent(false); // disallow event bubble
this.setBubbleEvent(true);
this.setInheritBubbleUiEvents(true);
this.stateChanged();
/*
if (Kekule.Widget.globalManager)
Kekule.Widget.globalManager.notifyWidgetCreated(this);
*/
var gm = this.getGlobalManager();
if (gm)
gm.notifyWidgetCreated(this);
},
/** @private */
initProperties: function()
{
this.defineProp('isDumb', {'dataType': DataType.BOOL, 'serializable': false,
'scope': Class.PropertyScope.PUBLIC,
'setter': function(value)
{
if (this.getIsDumb() != value)
{
this.setPropStoreFieldValue('isDumb', value);
var elem = this.getElement();
if (elem)
{
if (value)
this.uninstallUiEventHandlers(elem);
else
this.installUiEventHandlers(elem);
}
}
}
});
this.defineProp('bubbleUiEvents', {'dataType': DataType.BOOL, 'scope': Class.PropertyScope.PUBLIC});
this.defineProp('inheritBubbleUiEvents', {'dataType': DataType.BOOL, 'scope': Class.PropertyScope.PUBLIC});
this.defineProp('touchAction', {'dataType': DataType.STRING, 'scope': Class.PropertyScope.PUBLIC,
'setter': function(value)
{
//var elem = this.getElement();
var elem = this.getCoreElement();
if (elem)
{
//elem.setAttribute('touch-action', value); // for polyfill pep.js lib (PointerEvent)
elem.style.touchAction = value; // CSS touch-action
if (value === 'none')
{
//console.log('add none handler', this.getClassName());
// Add a dummy touchstart handler to prevent default action
Kekule.X.Event.addListener(elem, 'touchstart', this._touchActionNoneTouchStartHandlerBind, {passive: false});
}
else
{
// remove the dummy touchstart handler to prevent default action
Kekule.X.Event.removeListener(elem, 'touchstart', this._touchActionNoneTouchStartHandlerBind, {passive: false});
}
}
}
});
this.defineProp('parent', {'dataType': 'Kekule.Widget.BaseWidget', 'serializable': false,
'scope': Class.PropertyScope.PUBLISHED,
'setter': function(value)
{
var old = this.getParent();
if (old) // remove from old
old._removeChild(this);
if (value) // append to new parent
value._addChild(this);
this.setPropStoreFieldValue('parent', value);
}
});
this.defineProp('childWidgets', {'dataType': DataType.ARRAY, 'serializable': false, 'setter': null,
'scope': Class.PropertyScope.PUBLIC,
'getter': function()
{
var r = this.getPropStoreFieldValue('childWidgets');
if (!r)
{
r = [];
this.setPropStoreFieldValue('childWidgets', r);
}
return r;
}
});
this.defineProp('document', {'dataType': DataType.OBJECT, 'serializable': false, 'scope': Class.PropertyScope.PUBLIC});
this.defineProp('element', {'dataType': DataType.OBJECT, 'serializable': false,
'scope': Class.PropertyScope.PUBLIC,
'setter': function(value)
{
var old = this.getElement();
if (value !== old)
{
this.setPropStoreFieldValue('element', value);
this.elementChanged(value, old);
}
}
});
this.defineProp('observeElementAttribChanges', {'dataType': DataType.BOOL, //'scope': Class.PropertyScope.PUBLIC,
'setter': function(value)
{
if (!!value !== !!this.getObserveElementAttribChanges())
{
this.setPropStoreFieldValue('observeElementAttribChanges', value);
this.observeElementAttribChangesChanged(!!value);
}
}
});
this.defineElemAttribMappingProp('id', 'id');
//this.defineElemAttribMappingProp('draggable', 'draggable');
this.defineProp('draggable', {'dataType': DataType.BOOL, 'serializable': false,
'scope': Class.PropertyScope.PUBLIC,
'getter': function() { return Kekule.StrUtils.strToBool(this.getElement().getAttribute('draggable') || ''); },
'setter': function(value) { this.getElement().setAttribute('draggable', value? 'true': 'false')}
});
this.defineProp('droppable', {'dataType': DataType.BOOL, //'serializable': false,
'scope': Class.PropertyScope.PUBLIC
});
this.defineProp('droppableDataKinds', {'dataType': DataType.ARRAY,
'scope': Class.PropertyScope.PUBLIC
});
this.defineProp('fileDroppable', {'dataType': DataType.ARRAY, 'serializable': false,
'scope': Class.PropertyScope.PUBLIC,
'getter': function() {
if (!this.getDroppable())
return false;
var kinds = this.getDroppableDataKinds();
return !kinds || (kinds && kinds.indexOf && kinds.indexOf('file') >= 0);
},
'setter': function(value) {
if (value && !this.getDroppable())
this.setDroppable(true);
var kinds = this.getPropStoreFieldValue('droppableDataKinds');
if (!kinds)
{
if (!value)
this.setDroppableDataKinds(['string']);
}
else
{
var index = kinds.indexOf('file');
if (!value && index >= 0) // remove file from kinds
kinds.splice(index, 1);
else if (value && index < 0) // add file to kinds
kinds.push('file');
}
}
});
this.defineElemStyleMappingProp('width', 'width');
this.defineElemStyleMappingProp('height', 'height');
this.defineProp('offsetParent', {'dataType': DataType.OBJECT, 'serializable': false,
'scope': Class.PropertyScope.PUBLIC,
'getter': function() { return this.getElement().offsetParent; },
'setter': null
});
this.defineProp('offsetLeft', {'dataType': DataType.INT, 'serializable': false,
'getter': function() { return this.getElement().offsetLeft; },
'setter': null
});
this.defineProp('offsetTop', {'dataType': DataType.INT, 'serializable': false,
'getter': function() { return this.getElement().offsetTop; },
'setter': null
});
this.defineProp('offsetWidth', {'dataType': DataType.INT, 'serializable': false,
'getter': function() { return this.getElement().offsetWidth; },
'setter': null
});
this.defineProp('offsetHeight', {'dataType': DataType.INT, 'serializable': false,
'getter': function() { return this.getElement().offsetHeight; },
'setter': null
});
this.defineProp('tabIndex', {'dataType': DataType.INT, 'serializable': false,
'scope': Class.PropertyScope.PUBLIC,
'getter': function() { return this.getElement().tabIndex; },
'setter': function(value) { this.getElement().tabIndex = value; }
});
this.defineProp('innerHTML', {'dataType': DataType.STRING, 'serializable': false,
'scope': Class.PropertyScope.PUBLIC, // this prop value usually should not be shown in objInspector to avoid modification of essential HTML structure
'getter': function() { return this.getElement().innerHTML; },
'setter': function(value) { this.getElement().innerHTML = value; }
});
this.defineProp('style', {'dataType': DataType.OBJECT, 'serializable': false,
'scope': Class.PropertyScope.PUBLIC,
'getter': function() { return this.getElement().style; },
'setter': null
});
this.defineProp('cssText', {'dataType': DataType.STRING, 'serializable': false,
'scope': Class.PropertyScope.PUBLIC,
'getter': function() { return this.getElement().style.cssText; },
'setter': function(value)
{
this.getElement().style.cssText = value;
}
});
this.defineProp('htmlClassName', {'dataType': DataType.STRING, 'serializable': false,
'scope': Class.PropertyScope.PUBLIC,
'getter': function() { return this.getElement().className; },
'setter': function(value) { this.getElement().className = value; }
});
this.defineProp('customHtmlClassName', {'dataType': DataType.STRING,
'scope': Class.PropertyScope.PUBLIC,
'setter': function(value)
{
var elem = this.getElement();
var old = this.getCustomHtmlClassName();
if (elem && (old !== value))
{
if (old)
EU.removeClass(elem, old);
if (value)
EU.addClass(elem, value);
this.setPropStoreFieldValue('customHtmlClassName', value);
}
}
});
/*
this.defineProp('outlookStyleClassNames', {'dataType': DataType.ARRAY, 'serializable': false,
'setter': function(value)
{
var old = this.getOutlookStyleClassNames();
if (old)
this.removeClassName(old);
this.addClassName(value);
this.setPropStoreFieldValue('outlookStyleClassNames', value);
}
});
*/
this.defineProp('visible', {'dataType': DataType.BOOL, 'serializable': false,
'getter': function()
{
return Kekule.StyleUtils.isVisible(this.getElement());
},
'setter': function(value, byPassShowStateChange)
{
Kekule.StyleUtils.setVisibility(this.getElement(), value);
if (!byPassShowStateChange)
this.widgetShowStateChanged(this.isShown());
}
});
this.defineProp('displayed', {'dataType': DataType.BOOL, 'serializable': false,
'getter': function()
{
return Kekule.StyleUtils.isDisplayed(this.getElement());
},
'setter': function(value, byPassShowStateChange)
{
//console.log('set displayed', value, byPassShowStateChange);
Kekule.StyleUtils.setDisplay(this.getElement(), value);
if (!byPassShowStateChange)
this.widgetShowStateChanged(this.isShown());
}
});
// stores show/hide information
this.defineProp('showHideType', {'dataType': DataType.INT, 'serializable': false, 'scope': Class.PropertyScope.PRIVATE}); // private
this.defineProp('showHideCaller', {'dataType': 'Kekule.Widget.BaseWidget', 'serializable': false, 'scope': Class.PropertyScope.PRIVATE}); // private
this.defineProp('showHideCallerPageRect', {'dataType': DataType.HASH, 'serializable': false, 'scope': Class.PropertyScope.PRIVATE}); // private
this.defineProp('standaloneOnShowHide', {'dataType': DataType.BOOL, 'serializable': false, 'scope': Class.PropertyScope.PUBLIC}); // whether the caller widget is ignored when execute show/hide animation
this.defineProp('finalizeAfterHiding', {'dataType': DataType.BOOL, 'scope': Class.PropertyScope.PUBLIC});
this.defineProp('layout', {'dataType': DataType.INT,
'setter': function(value)
{
if (this.getPropStoreFieldValue('layout') !== value)
{
this.setPropStoreFieldValue('layout', value);
this.layoutChanged();
}
}
});
this.defineProp('useCornerDecoration', {'dataType': DataType.BOOL,
'setter': function(value)
{
this.setPropStoreFieldValue('useCornerDecoration', value);
if (!value)
{
//console.log('setNonROund');
this.removeClassName(CNS.CORNER_ALL);
}
else
{
//console.log('setROund');
this.addClassName(CNS.CORNER_ALL);
}
}
});
this.defineProp('useNormalBackground', {'dataType': DataType.BOOL,
'setter': function(value)
{
this.setPropStoreFieldValue('useNormalBackground', value);
if (!value)
{
this.removeClassName(CNS.NORMAL_BACKGROUND);
}
else
{
this.addClassName(CNS.NORMAL_BACKGROUND);
}
}
});
this.defineProp('styleMode', {'dataType': DataType.INT, 'enumSource': Kekule.Widget.StyleMode,
'setter': function(value)
{
if (value !== this.getStyleMode())
{
if (this.getElement())
{
if (value === Kekule.Widget.StyleMode.INHERITED)
{
this.removeClassName(CNS.STYLE_UNDEPENDENT);
this.addClassName(CNS.STYLE_INHERITED);
}
else // if (value === Kekule.Widget.StyleMode.UNDEPENDENT)
{
this.addClassName(CNS.STYLE_UNDEPENDENT);
this.removeClassName(CNS.STYLE_INHERITED);
}
}
this.setPropStoreFieldValue('styleMode', value);
}
}
});
this.defineProp('inheritedStyles', {'dataType': DataType.ARRAY,
'setter': function(value)
{
this._applyToAllInheritedStyles(this.getInheritedStyles(), false); // clear old
this.setPropStoreFieldValue('inheritedStyles', value || []);
this._applyToAllInheritedStyles(this.getInheritedStyles(), true); // apply new
}
});
this.defineProp('showText', {'dataType': DataType.BOOL,
'setter': function(value)
{
this.setPropStoreFieldValue('showText', value);
//this._elemTextPart.style.display = value? '': 'none';
if (value)
{
this.removeClassName(CNS.HIDE_TEXT);
this.addClassName(CNS.SHOW_TEXT);
}
else
{
this.addClassName(CNS.HIDE_TEXT);
this.removeClassName(CNS.SHOW_TEXT);
}
}
});
this.defineProp('showGlyph', {'dataType': DataType.BOOL,
'setter': function(value)
{
this.setPropStoreFieldValue('showGlyph', value);
if (value)
{
this.removeClassName(CNS.HIDE_GLYPH);
this.addClassName(CNS.SHOW_GLYPH);
}
else
{
this.addClassName(CNS.HIDE_GLYPH);
this.removeClassName(CNS.SHOW_GLYPH);
}
}
});
this.defineProp('allowTextWrap', {'dataType': DataType.BOOL, 'serialzable': false,
'setter': function(value)
{
if (value)
this.removeClassName(CNS.TEXT_NO_WRAP);
else
this.addClassName(CNS.TEXT_NO_WRAP);
}
});
this.defineProp('selfEnabled', {'dataType': DataType.BOOL, 'scope': Class.PropertyScope.PRIVATE}); // private properties
this.defineProp('inheritEnabled', {'dataType': DataType.BOOL,
'setter': function(value)
{
this.setPropStoreFieldValue('inheritEnabled', value);
this.stateChanged();
}
});
this.defineProp('enabled', {'dataType': DataType.BOOL, 'serializable': false,
'getter': function()
{
var result = this.getPropStoreFieldValue('selfEnabled');
if (this.getInheritEnabled())
{
var p = this.getParent();
if (p)
result = result && p.getEnabled();
}
return result;
},
'setter': function(value)
{
//this.getCoreElement().disabled = !value;
//console.log('set disabled: ' + this.getClassName() + ' ' + !value);
var elem = this.getElement();
if (!value)
elem.setAttribute('disabled', 'true');
else
elem.removeAttribute('disabled');
var elem = this.getCoreElement();
if (elem != this.getElement())
{
if (!value)
elem.setAttribute('disabled', 'true');
else
elem.removeAttribute('disabled');
}
this.setPropStoreFieldValue('selfEnabled', value);
this.stateChanged();
}
});
this.defineProp('selfStatic', {'dataType': DataType.BOOL, 'scope': Class.PropertyScope.PRIVATE}); // private properties
this.defineProp('inheritStatic', {'dataType': DataType.BOOL,
'setter': function(value)
{
this.setPropStoreFieldValue('inheritStatic', value);
this.stateChanged();
}
});
this.defineProp('static', {'dataType': DataType.BOOL, 'serializable': false,
'getter': function()
{
var result = this.getPropStoreFieldValue('selfStatic');
if (this.getInheritStatic())
{
var p = this.getParent();
if (p)
result = result || p.getStatic();
}
return result;
},
'setter': function(value)
{
this.setPropStoreFieldValue('selfStatic', value);
this.stateChanged();
}
});
this.defineProp('inheritState', {'dataType': DataType.BOOL,
'setter': function(value)
{
this.setPropStoreFieldValue('inheritState', value);
this.stateChanged();
}
});
this.defineProp('state', {'dataType': DataType.INT, 'serializable': false,
'scope': Class.PropertyScope.PUBLIC,
'setter': null,
'getter': function()
{
var result;
if (this.getInheritState())
{
var p = this.getParent();
if (p)
result = p.getState();
}
else
{
if (!this.getEnabled())
result = WS.DISABLED;
else
result =
//(!this.getEnabled())? WS.DISABLED:
this.getIsActive()? WS.ACTIVE:
this.getIsHover()? WS.HOVER:
this.getIsFocused()? WS.FOCUSED:
WS.NORMAL;
}
return result;
}
});
this.defineProp('shortcuts', {'dataType': DataType.ARRAY, 'serializable': false, 'setter': null, 'scope': Class.PropertyScope.PUBLIC});
this.defineProp('shortcutKeys', {
'dataType': DataType.ARRAY,
'getter': function()
{
var result = [];
var shortcuts = this.getShortcuts() || [];
for (var i = 0, l = shortcuts.length; i < l; ++i)
result.push(shortcuts[i].key);
return result;
},
'setter': function(value)
{
this._updateShortcuts(value && AU.toArray(value));
}
});
this.defineProp('shortcutKey', {
'dataType': DataType.STRING, 'serializable': false,
'getter': function() { return this.getShortcutKeys()[0]; },
'setter': function(value) { this.setShortcutKeys(value); }
});
//this.defineElemStyleMappingProp('cursor', 'cursor');
this.defineProp('cursor', {
'dataType': DataType.VARIANT,
'serializable': false,
'getter': function() { return this.getStyleProperty('cursor'); },
'setter': function(value) {
if (DataType.isArrayValue(value)) // try each cursor keywords
Kekule.StyleUtils.setCursor(this.getElement(), value);
else // normal string value
this.setStyleProperty('cursor', value);
}
});
this.defineProp('isHover', {'dataType': DataType.BOOL, 'serializable': false,
'scope': Class.PropertyScope.PUBLIC,
'setter': function(value)
{
this.setPropStoreFieldValue('isHover', value);
// if not hover, the active state should also be turned off
if (!value && !this.isCaptureMouse())
this.setPropStoreFieldValue('isActive', false);
var m = this.getGlobalManager();
if (m)
m.notifyWidgetHoverChanged(this, value);
this.stateChanged();
}
});
this.defineProp('isActive', {'dataType': DataType.BOOL, 'serializable': false,
'scope': Class.PropertyScope.PUBLIC,
'setter': function(value)
{
this.setPropStoreFieldValue('isActive', value);
if (value) // active widget should always be focused
{
this.focus();
}
var m = this.getGlobalManager();
if (m)
m.notifyWidgetActiveChanged(this, value);
this.stateChanged();
}
});
this.defineProp('isFocused', {'dataType': DataType.BOOL, 'serializable': false,
'scope': Class.PropertyScope.PUBLIC,
'getter': function()
{
var doc = this.getDocument();
var elem = this.getCoreElement();
if (doc && elem && doc.activeElement)
return (doc.activeElement === elem);
},
'setter': function(value)
{
this.setPropStoreFieldValue('isFocused', value);
var m = this.getGlobalManager();
if (m)
m.notifyWidgetFocusChanged(this, value);
var elem = this.getCoreElement();
if (elem)
{
// TODO: currently restrict focus element to form controls, avoid normal element IE focused on auto scrolling to top-left
var doc = this.getDocument();
if (value && doc.activeElement && doc.activeElement !== elem && doc.activeElement.blur) // blur old active element before focusing this one
{
doc.activeElement.blur();
}
if (elem.focus && value && Kekule.HtmlElementUtils.isFormCtrlElement(elem))
elem.focus();
if (elem.blur && (!value))
elem.blur();
}
this.stateChanged();
}
});
this.defineProp('minDimension', {'dataType': DataType.HASH});
this.defineProp('enableDimensionTransform', {'dataType': DataType.BOOL});
this.defineProp('autoResizeConstraints', {'dataType': DataType.HASH,
'setter': function(value){
this.setPropStoreFieldValue('autoResizeConstraints', value);
var gm = this.getGlobalManager() || Kekule.Widget.globalManager;
if (value)
{
this.autoResizeToClient();
gm.registerAutoResizeWidget(this);
}
else
gm.unregisterAutoResizeWidget(this);
}
});
this.defineProp('observeElemResize', {'dataType': DataType.BOOL,
'getter': function()
{
return this.getPropStoreFieldValue('observeElemResize') && Kekule.BrowserFeature.resizeObserver;
},
'setter': function(value)
{
if (this.getPropStoreFieldValue('observeElemResize') != (!!value))
{
this.setPropStoreFieldValue('observeElemResize', !!value);
if (value)
{
this._installElemResizeObserver();
}
else
{
this._uninstallElemResizeObserver();
}
}
}
});
this.defineProp('autoAdjustSizeOnPopup', {'dataType': DataType.BOOL, 'scope': Class.PropertyScope.PUBLIC});
this.defineProp('isDialog', {'dataType': DataType.BOOL, 'scope': Class.PropertyScope.PUBLIC});
this.defineProp('isPopup', {'dataType': DataType.BOOL, 'scope': Class.PropertyScope.PUBLIC});
this.defineProp('popupCaller', {'dataType': DataType.BOOL, 'scope': Class.PropertyScope.PRIVATE}); // private, record who calls this popup
this.defineProp('modalInfo', {'dataType': DataType.HASH, 'scope': Class.PropertyScope.PUBLIC}); // for dialog only
this.defineProp('enablePeriodicalExec', {'dataType': DataType.BOOL});
this.defineProp('periodicalExecDelay', {'dataType': DataType.INT});
this.defineProp('periodicalExecInterval', {'dataType': DataType.INT});
this.defineElemAttribMappingProp('hint', 'title');
this.defineProp('action', {'dataType': 'Kekule.Action', 'serializable': false,
'setter': function(value)
{
var old = this.getAction();
if (old !== value)
{
if (old && old.unlinkWidget)
{
old.unlinkWidget(this);
this.unlinkAction(old);
}
this.setPropStoreFieldValue('action', value);
if (value && value.linkWidget)
{
value.linkWidget(this);
this.linkAction(value);
}
}
}
});
// private, stores defaulty created child actions
this.defineProp('defaultChildActions', {'dataType': 'Kekule.ActionList', 'serializable': false, 'setter': null,
'getter': function(canCreate)
{
var result = this.getPropStoreFieldValue('defaultChildActions');
if (!result && canCreate)
{
result = new Kekule.ActionList();
this.setPropStoreFieldValue('defaultChildActions', result);
}
return result;
}
});
// private, stores defaulty created child action and actionClass map
this.defineProp('defaultChildActionMap', {'dataType': DataType.OBJECT, 'serializable': false, 'setter': null,
'getter': function(canCreate)
{
var result = this.getPropStoreFieldValue('defaultChildActionMap');
if (!result && canCreate)
{
result = new Kekule.MapEx();
this.setPropStoreFieldValue('defaultChildActionMap', result);
}
return result;
}
});
this.defineProp('iaControllerMap', {'dataType': DataType.OBJECT, 'serializable': false, 'setter': null,
'scope': Class.PropertyScope.PUBLIC,
'getter': function()
{
var result = this.getPropStoreFieldValue('iaControllerMap');
if (!result)
{
result = new Kekule.HashEx();
this.setPropStoreFieldValue('iaControllerMap', result);
}
return result;
}
});
this.defineProp('defIaControllerId', {'dataType': DataType.STRING, 'serializable': false, 'scope': Class.PropertyScope.PUBLIC});
this.defineProp('defIaController', {'dataType': DataType.STRING, 'serializable': false,
'scope': Class.PropertyScope.PUBLIC,
'setter': null,
'getter': function() { return this.getIaControllerMap().get(this.getDefIaControllerId()); } });
this.defineProp('activeIaControllerId', {'dataType': DataType.STRING, 'serializable': false, 'scope': Class.PropertyScope.PUBLIC,
'setter': function(value)
{
if (value !== this.getActiveIaControllerId())
{
this.setPropStoreFieldValue('activeIaControllerId', value);
var currController = this.getActiveIaController();
if (currController && currController.activated) // call some init method of controller
{
currController.activated(this);
}
}
}
});
this.defineProp('activeIaController', {'dataType': DataType.OBJECT, 'serializable': false,
'scope': Class.PropertyScope.PUBLIC,
'setter': null,
'getter': function() { return this.getIaControllerMap().get(this.getActiveIaControllerId()); }});
this.defineProp('htmlEventDispatcher', {'dataType': 'Kekule.Widget.HtmlEventDispatcher', 'serializable': false,
'scope': Class.PropertyScope.PRIVATE,
'setter': null
});
this.defineProp('observingGestureEvents', {'dataType': DataType.ARRAY, 'serializable': false,
'scope': Class.PropertyScope.PUBLIC,
'setter': null
});
},
/** @private */
doFinalize: function(/*$super*/)
{
if (this._elemResizeObserver)
{
if (this._elemResizeObserver.disconnect)
this._elemResizeObserver.disconnect();
this._elemResizeObserver = null;
}
this._clearShortcuts();
this.getHtmlEventDispatcher().finalize();
var childActionMap = this.getDefaultChildActionMap();
if (childActionMap)
{
childActionMap.finalize();
this.setPropStoreFieldValue('defaultChildActionMap', null);
}
var childActions = this.getDefaultChildActions();
if (childActions)
{
childActions.finalize();
this.setPropStoreFieldValue('defaultChildActions', null);
}
this.setAction(null);
this.setParent(null);
this.releaseChildWidgets();
var elem = this.getElement();
this.setElement(null);
this.destroyElement(elem);
if (this.getGlobalManager())
this.getGlobalManager().notifyWidgetFinalized(this);
this.tryApplySuper('doFinalize') /* $super() */;
},
/** @ignore */
initPropValues: function(/*$super*/)
{
this.tryApplySuper('initPropValues') /* $super() */;
this.setEnableObjectChangeEvent(true);
},
/** @ignore */
doObjectChange: function(modifiedPropNames)
{
this.tryApplySuper('doObjectChange', [modifiedPropNames]);
if (modifiedPropNames.indexOf('readOnly') >= 0 && this.hasProperty('readOnly'))
{
// when read only property exists in widget and has been changed, modify the class name
var readOnly = this.getPropValue('readOnly');
if (readOnly)
this.addClassName(CNS.STATE_READONLY);
else
this.removeClassName(CNS.STATE_READONLY);
}
},
/** @ignore */
invokeEvent: function(/*$super, */eventName, event)
{
if (!event)
event = {};
// add a 'widget' param
if (!event.widget)
event.widget = this;
this.tryApplySuper('invokeEvent', [eventName, event]) /* $super(eventName, event) */;
// notify global manager when a widget event occurs
var m = this.getGlobalManager(); // Kekule.Widget.globalManager;
if (m)
{
m.notifyWidgetEventFired(this, eventName, event);
}
},
/**
* Returns global widget manager in current document.
* @returns {Object}
*/
getGlobalManager: function()
{
//return Kekule.Widget.globalManager;
/*
var doc = this.getDocument();
var win = doc && Kekule.DocumentUtils.getDefaultView(doc);
var kekuleRoot = win && win.Kekule;
if (!kekuleRoot)
kekuleRoot = Kekule;
return kekuleRoot.Widget.globalManager;
*/
return Kekule.Widget.Utils.getGlobalManager(this.getDocument());
},
/**
* Returns core element of widget.
* Usually core element is the element widget bound to, but in some
* cases, core element may be a child of widget element. Descendants
* can override this method to reflect that situation.
* @returns {HTMLElement}
*/
getCoreElement: function()
{
return this.getElement();
},
/**
* Returns the element that be used as root to insert child widgets.
* Descendants can override this method to reflect that situation.
* @returns {HTMLElement}
*/
getChildrenHolderElement: function()
{
return this.getCoreElement();
},
/**
* Whether the text content inside widget element can be user selected.
* Most widget (like tree, button) should return false, but form controls (like textbox) should return true.
* Descendants may override this method.
* @returns {Bool}
*/
getTextSelectable: function()
{
return false;
},
/**
* Returns whether a placeholder widget can be bind to element to represent this widget.
* This method is used when auto-launching widget on HTML element. Descendants can override this method.
* @param {HTMLElement} elem
* @returns {Bool}
*/
canUsePlaceHolderOnElem: function(elem)
{
return false;
},
/** @private */
doPropChanged: function(propName, newValue)
{
if ((propName === 'width') || (propName === 'height'))
{
this.resized();
}
},
/** @private */
getActualBubbleUiEvents: function()
{
var parent = this.getParent();
if (this.getBubbleUiEvents())
return true;
else if (this.getInheritBubbleUiEvents() && parent && parent.getActualBubbleUiEvents)
return parent.getActualBubbleUiEvents();
else
false;
},
/*
* Report an exception (error/warning and so on) occurs related to widget.
* @param {Variant} e Error object or message.
*/
/*
reportException: function(e)
{
if (!this.doReportException(e))
Kekule.error(e);
},
*/
/*
* Do actual job of reportError. If error is handled (and should not raise to browser),
* this method should return true. Descendant can override this method.
* @param {Variant} e Error object or message.
*/
/*
doReportException: function(e)
{
return false;
},
*/
/** @private */
releaseChildWidgets: function()
{
var children = this.getChildWidgets();
for (var i = children.length - 1; i >= 0; --i)
children[i].finalize();
},
/**
* Create a property that read/write attribute of HTML element.
* @param {String} propName
* @param {String} elemAttribName Attribute name of HTML element.
* @param {Hash} options Options to define property. If not set, default option will be used.
* @return {Object} Property info object added to property list.
*/
defineElemAttribMappingProp: function(propName, elemAttribName, options)
{
var ops = Object.extend({
'dataType': DataType.STRING,
'serializable': false,
'getter': function() { return this.getElement() && this.getElement().getAttribute(elemAttribName); },
'setter': function(value) { this.getElement() && this.getElement().setAttribute(elemAttribName, value); }
}, options || {});
return this.defineProp(propName, ops);
},
/**
* Create a property that read/write style property of HTML element.
* @param {String} propName
* @param {String} stylePropName Property name of element.style.
* @param {Hash} options Options to define property. If not set, default option will be used.
* @return {Object} Property info object added to property list.
*/
defineElemStyleMappingProp: function(propName, stylePropName, options)
{
var ops = Object.extend