UNPKG

kekule

Version:

Open source JavaScript toolkit for chemoinformatics

1,452 lines (1,359 loc) 232 kB
/** * @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