/* RSence * Copyright 2006 Riassence Inc. * http://riassence.com/ * * You should have received a copy of the GNU General Public License along * with this software package. If not, contact licensing@riassence.com */ /*** = Description ** HView is the foundation class for all views. HView is useful for ** any type of view and control grouping. It is designed for easy extension ** and it's the foundation for HControl and all other controls. ** ** The major differences between HView and HControl is that HView handles ** only visual representation and structurization. In addition to HView's ** features, HControl handles labels, values, events, states and such. ** However, HControl is more complex, so use HView instead whenever you don't ** need the additional features of HControl. HView implements the HMarkupView ** interface for template-related task. ** ** = Usage ** var myAppInstance = HApplication.nu(); ** var rect1 = [10, 10, 100, 100]; ** var myViewInstance = HView.nu( rect1, myAppInstance ); ** var myViewInstance.setStyle('background-color','#ffcc00'); ** var rect2 = [10, 10, 70, 70]; ** var mySubView1 = HView.nu( rect2, myViewIntance ); ** var rect3 [20, 20, 50, 50]; ** var mySubView2 = HView.nu( rect3, mySubView1 ); ** ***/ var//RSence.Foundation HView = HClass.extend({ /** Component specific theme path. **/ themePath: null, /** True, if the component using absolute positioning. * False, if the component is using relative positioning. **/ isAbsolute: true, /** The display mode to use. * Defaults to 'block'. * The other sane alternative is 'inline'. **/ displayMode: 'block', /** The visual value of a component, usually a String. * See +#setLabel+. **/ label: null, /** When true, calls the +refreshLabel+ method whenever * +self.label+ is changed. **/ refreshOnLabelChange: true, /** Escapes HTML in the label when true. **/ escapeLabelHTML: false, /** True, if the coordinates are right-aligned. * False, if the coordinates are left-aligned. * Uses flexRightOffset if true. Defined with 6-item arrays * for the _rect parameter of setRect or the constructor. * Can be set directly using the setFlexRight method. **/ flexRight: false, /** True, if the coordinates are left-aligned. * False, if the coordinates are right-aligned. * Uses the X-coordinate of rect, if true. * Disabled using 6-item arrays with null x-coordinate * for the _rect parameter of setRect or the constructor. * Can be set directly using the setFlexLeft method. **/ flexLeft: true, /** True, if the coordinates are top-aligned. * False, if the coordinates are bottom-aligned. * Uses the Y-coordinate of rect, if true. * Disabled using 6-item arrays with null x-coordinate * for the _rect parameter of setRect or the constructor. * Can be set directly using the setFlexTop method. **/ flexTop: true, /** True, if the coordinates are bottom-aligned. * False, if the coordinates are top-aligned. * Uses flexBottomOffset if true. Defined with 6-item arrays * for the _rect parameter of setRect or the constructor. * Can be set directly using the setFlexRight method. **/ flexBottom: false, /** The amount of pixels to offset from the right edge when * flexRight is true. Defined with 6-item arrays * for the _rect parameter of setRect or the constructor. * Can be set directly using the setFlexRight method. **/ flexRightOffset: 0, /** The amount of pixels to offset from the bottom edge when * flexBottom is true.Defined with 6-item arrays * for the _rect parameter of setRect or the constructor. * Can be set directly using the setFlexBottom method. **/ flexBottomOffset: 0, /** The drawn flag is false before the component is visually * drawn, it's true after it's drawn. **/ drawn: false, /** The theme the component is constructed with. By default, * uses the HThemeManager.currentTheme specified at the time * of construction. **/ theme: null, /** The preserveTheme flag prevents the view from being redrawn * if HThemeManager.currentTheme is changed after the view * has been drawn. Is true, if theme has been set. **/ preserveTheme: false, /** The optimizeWidthOnRefresh flag, when enabled, allows * automatic width calculation for components that support * that feature. **/ optimizeWidthOnRefresh: true, /** The parent is the +_parent+ supplied to the constructor. * This is a complete object reference to the parent's namespace. **/ parent: null, /** The parents is an array containing parent instances up to * the root controller level. The root controller is almost * always an instance of HApplication. **/ parents: null, /** The viewId is the unique ID (serial number) of this view. * This means the view can be looked up globally based on its * id by using the +HSystem.views+ array. **/ viewId: null, /** The appId is the unique ID (serial number) of the app process * acting as the root controller of the view tree of which this * view is a member. * This means the app can be looked up globally based on this * id by using the +HSystem.apps+ array. **/ appId: null, /** The app is the reference of the app process acting as * the root controller of the view tree of which this view is a * member. * This is a complete object reference to the app's namespace. **/ app: null, /** The views array contains a list of subviews of this view * by id. To access the object reference, use the +HSystem.views+ * array with the id. **/ views: null, /** The viewsZOrder array contains a list of subviews ordered by * zIndex. To change the order, use the bringToFront, * sendToBack, bringForwards, sendBackwards, bringToFrontOf and * sentToBackOf methods. **/ viewsZOrder: null, /** The isHidden flog reflects the visibility of the view. **/ isHidden: false, /** The +HRect+ instance bound to +self+ using the +constructor+ or +setRect+. **/ rect: null, /** An reference to the options block given as the constructor * parameter _options. **/ options: null, /** The viewDefaults is a HViewDefaults object that is extended * in the constructor with the options block given. The format of * it is an Object. * It's only used when not extended via HControl, see HControl#controlDefaults. **/ viewDefaults: HViewDefaults, /** = Description * Constructs the logic part of a HView. * The view still needs to be drawn on screen. To do that, call draw after * subcomponents of the view are initialized. * * = Parameters * +_rect+:: An instance of +HRect+, defines the position and size of views. * It can be also defined with an array, see below. * +_parent+:: The parent instance this instance will be contained within. * A valid parent can be another HView compatible instance, * an HApplication instance, a HControl or a similar extended * HView instance. The origin of the +_rect+ is the same as the * parent's offset. For HApplication instances, the web browser's * window's left top corner is the origin. * * == The +_rect+ dimensions as arrays * Instead of an instance of +HRect+, dimensions can also be supplied as arrays. * The array length must be either 4 or 6. If the length is 4, the dimensions are * specified as follows: +[ x, y, width, height ]+. Note that this is different * from the construction parameters of +HRect+ that takes the coordinates as two * points, like: +( left, top, right, bottom )+. * Arrays with 6 items are a bit more complex (and powerful) as they can specify * the flexible offsets too. * * === The array indexes for a +_rect+ configured as an 4-item array: * Always left/top aligned, all items must be specified. * Index:: Description * +0+:: The X-coordinate (measured from the parent's left edge) * +1+:: The Y-coordinate (measured from the parent's top edge) * +2+:: The width. * +3+:: The height. * * === The array indexes a +_rect+ configured as an 6-item array: * Can be any configuration of left/top/right/bottom alignment and supports * flexible widths. At least 4 items must be specified. * Index:: Description * +0+:: The left-aligned X-coordinate or +null+ if the view is * right-aligned and using a right-aligned X-coordinate at * index +4+ as well as the width specified at index +2+. * +1+:: The top-aligned Y-coordinate or +null+ if the view is * bottom-aligned and using a right-aligned X-coordinate at * index +5+ as well as the height specified at index +3+. * +2+:: The width, if only one X-coordinate specifies the * position (at indexes +0+ or +4+). * If both X-coordinates (at indexes +0+ and +4+) are * specified, the width can be specified with a +null+ for * automatic (flexible) width. If the width is specified, * it's used as the minimum width. * +3+:: The height, if only one Y-coordinate specifies the * position (at indexes +1+ or +5+). * If both Y-coordinates (at indexes +1+ and +5+) are * specified, the height can be specified with a +null+ for * automatic (flexible) height. if the height is specified, * it's used as the minimum height. * +4+:: The right-aligned X-coordinate or +null+ if the view is * left-aligned and using a left-aligned X-coordinate at * index +0+ as well as the width specified at index +2+. * +5+:: The bottom-aligned Y-coordinate or +null+ if the view is * top-aligned and using a top-aligned X-coordinate at * index +1+ as well as the height specified at index +3+. * == Usage examples of +_rect+: * Specified as two instances of +HPoint+, * x: 23, y: 75, width: 200, height: 100: * HRect.nu( HPoint.nu( 23, 75 ), HPoint.nu( 223, 175 ) ) * * The same as above, but without +HPoint+ instances: * HRect.nu( 23, 75, 223, 175 ) * * The same as above, but with an array as the constructor * parameter for +HRect+: * HRect.nu( [ 23, 75, 223, 175 ] ) * * The same as above, but with an array instead of a +HRect+ instance: * [ 23, 75, 200, 100 ] * * The same as above, but with a 6-item array: * [ 23, 75, 200, 100, null, null ] * * The same as above, but aligned to the right instead of left: * [ null, 75, 200, 100, 23, null ] * * The same as above, but aligned to the right/bottom edges: * [ null, null, 200, 100, 23, 75 ] * * The same as above, but aligned to the left/bottom edges: * [ 23, null, 200, 100, null, 75 ] * * Flexible width (based on the parent's dimensions): * [ 23, 75, null, 100, 23, null ] * * Flexible height (based on the parent's dimensions): * [ 23, 75, 200, null, null, 75 ] * * Flexible width and height (based on the parent's dimensions): * [ 23, 75, null, null, 23, 75 ] * * Flexible width and height, but limited to a minimum width * of 200 and a minimum height of 100 (based on the parent's dimensions): * [ 23, 75, 200, 100, 23, 75 ] * **/ constructor: function(_rect, _parent, _options) { if( !_options ){ _options = {}; } if(!this.isinherited){ _options = (this.viewDefaults.extend(_options)).nu(this); } this.options = _options; this.label = _options.label; // Moved these to the top to ensure safe theming operation if( _options.theme ){ this.theme = _options.theme; this.preserveTheme = true; } else if(!this.theme){ this.theme = HThemeManager.currentTheme; this.preserveTheme = false; } else { this.preserveTheme = true; } if(_options.visible === false) { this.isHidden = true; } // adds the parentClass as a "super" object this.parent = _parent; this.viewId = this.parent.addView(this); // the parent addView method adds this.parents this.appId = this.parent.appId; this.app = HSystem.apps[this.appId]; // subview-ids, index of HView-derived objects that are found in HSystem.views[viewId] this.views = []; // Subviews in Z order. this.viewsZOrder = []; // Keep the view (and its subviews) hidden until its drawn. this._createElement(); // Set the geometry this.setRect(_rect); this._cachedLeft = _rect.left; this._cachedTop = _rect.top; // Additional DOM element bindings are saved into this array so they can be // deleted from the element manager when the view gets destroyed. this._domElementBindings = []; if(!this.isinherited) { this.draw(); } }, /** = Description * When the +_flag+ is true, the view will be aligned to the right. * The +_px+ offset defines how many pixels from the parent's right * edge the right edge of this view will be. If both setFlexRight * and setFlexLeft are set, the width is flexible. * Use the constructor or setRect instead of calling this method * directly. * * = Parameters * +_flag+:: Boolean flag (true/false). Enables * right-alignment when true. * +_px+:: The amount of pixels to offset from the right * edge of the parent's right edge. * * = Returns * +self+ **/ setFlexRight: function(_flag,_px){ if(_flag===undefined){_flag=true;} this.flexRight = _flag; if(_px===undefined){_px=0;} this.flexRightOffset = _px; return this; }, /** = Description * When the +_flag+ is true, the view will be aligned to the left (default). * The +_px+ offset defines how many pixels from the parent's left * edge the left edge of this view will be. If both setFlexLeft * and setFlexRight are set, the width is flexible. * Use the constructor or setRect instead of calling this method * directly. * * = Parameters * +_flag+:: Boolean flag (true/false). Enables * left-alignment when true. * +_px+:: The amount of pixels to offset from the left * edge of the parent's left edge. * * = Returns * +self+ **/ setFlexLeft: function(_flag,_px){ if(_flag===undefined){_flag=true;} this.flexLeft = _flag; if((_px || _px === 0) && this.rect){ this.rect.setLeft(_px); } return this; }, /** = Description * When the +_flag+ is true, the view will be aligned to the top (default). * The +_px+ offset defines how many pixels from the parent's top * edge the top edge of this view will be. If both setFlexTop * and setFlexBottom are set, the height is flexible. * Use the constructor or setRect instead of calling this method * directly. * * = Parameters * +_flag+:: Boolean flag (true/false). Enables * top-alignment when true. * +_px+:: The amount of pixels to offset from the top * edge of the parent's top edge. * * = Returns * +self+ **/ setFlexTop: function(_flag,_px){ if(_flag===undefined){_flag=true;} this.flexTop = _flag; if((_px || _px === 0) && this.rect){ this.rect.setTop(_px); } return this; }, /** = Description * When the +_flag+ is true, the view will be aligned to the bottom. * The +_px+ offset defines how many pixels from the parent's bottom * edge the bottom edge of this view will be. If both setFlexBottom * and setFlexTop are set, the height is flexible. * Use the constructor or setRect instead of calling this method * directly. * * = Parameters * +_flag+:: Boolean flag (true/false). Enables * bottom-alignment when true. * +_px+:: The amount of pixels to offset from the bottom * edge of the parent's bottom edge. * * = Returns * +self+ **/ setFlexBottom: function(_flag,_px){ if(_flag===undefined){_flag=true;} this.flexBottom = _flag; if(_px===undefined){_px=0;} this.flexBottomOffset = _px; return this; }, /** = Description * The +_flag+ enables or disables the absolute positioning mode. * (It's enabled by default). If absolute positioning mode is * off, the coordinate system has little or no effect. * * = Parameters * +_flag+:: Boolean flag (true/false). Enables * absolute positioning when true. * Enables relative positioning when false. * * = Returns * +self+ **/ setAbsolute: function(_flag){ if(_flag===undefined){_flag=true;} this.isAbsolute = _flag; return this; }, /** = Description * The +_flag+ enables or disables the relative positioning mode. * (It's disabled by default). If relative positioning mode is * on, the coordinate system has little or no effect. * * = Parameters * +_flag+:: Boolean flag (true/false). Enables * absolute relative when true. * Enables absolute positioning when false. * * = Returns * +self+ **/ setRelative: function(_flag){ if(_flag===undefined){_flag=true;} this.isAbsolute = (!_flag); return this; }, /** = Description * Used by html theme templates to get the theme-specific full image path. * * = Returns * The full path of the theme-specific gfx path as a string. **/ getThemeGfxPath: function() { var _themeName; if( this.preserveTheme ){ _themeName = this.theme; } else { _themeName = HThemeManager.currentTheme; } return HThemeManager._componentGfxPath( _themeName, this.componentName, this.themePath ); }, /** = Description * Used by html theme templates to get the theme-specific full path * of the _fileName given. * * = Returns * The full path of the file. **/ getThemeGfxFile: function( _fileName ) { if( this.preserveTheme ){ _themeName = this.theme; } else { _themeName = HThemeManager.currentTheme; } return HThemeManager._componentGfxFile( _themeName, this.componentName, this.themePath, _fileName ); }, /** -- * = Description * The _makeElem method does the ELEM.make call to create * the
element of the component. It assigns the elemId. * It's a separate method to ease creating component that require * other element types. * ++ **/ _makeElem: function(_parentElemId){ this.elemId = ELEM.make(_parentElemId,'div'); }, /** -- * = Description * The _setCSS method does the initial styling of the element. * It's a separate method to ease creating component that require * other initial styles. * ++ **/ _setCSS: function(_additional){ var _cssStyle = 'overflow:hidden;visibility:hidden;'; if(this.isAbsolute){ _cssStyle += 'position:absolute;'; } else { _cssStyle += 'position:relative;'; } _cssStyle += _additional; ELEM.setCSS(this.elemId,_cssStyle); }, /** -- * = Description * The _getParentElemId method returns the ELEM ID of the parent. * ++ **/ _getParentElemId: function(){ var _parent = this.parent; return ((_parent.elemId === undefined)?0:((_parent._getSubviewId===undefined)?0:_parent._getSubviewId())); }, _getSubviewId: function(){ if(this.markupElemIds&&this.markupElemIds.subview!==undefined){ return this.markupElemIds.subview; } else if(this.elemId !== undefined) { return this.elemId; } return 0; }, /** -- * = Description * The _createElement method calls the methods required to initialize the * main DOM element of the view. * ++ **/ _createElement: function() { if(!this.elemId) { this._makeElem(this._getParentElemId()); this._setCSS(''); // Theme name == CSS class name if(this.preserveTheme){ ELEM.addClassName( this.elemId, this.theme ); } else { ELEM.addClassName( this.elemId, HThemeManager.currentTheme ); } } }, /** = Description * The +drawRect+ method refreshes the dimensions of the view. * It needs to be called to affect changes in the rect. * It enables the drawn flag. * * = Returns * +self+ * **/ drawRect: function() { if(!this.rect.isValid){ console.log('invalid rect:',this.rect);//,ELEM.get(this.elemId)); } if(!this.parent){ console.log('no parent:',ELEM.get(this.elemId)); } if (this.parent && this.rect.isValid) { var i = 0, _this = this, _elemId = _this.elemId, _styl = ELEM.setStyle, _rect = _this.rect, _auto = 'auto', _left = _this.flexLeft?_rect.left:_auto, _top = _this.flexTop?_rect.top:_auto, _right = _this.flexRight?_this.flexRightOffset:_auto, _bottom = _this.flexBottom?_this.flexBottomOffset:_auto, _width = (_this.flexLeft&&_this.flexRight)?_auto:_rect.width, _height = (_this.flexTop&&_this.flexBottom)?_auto:_rect.height, _styles = [ [ 'left', _left ], [ 'top', _top ], [ 'right', _right ], [ 'bottom', _bottom ], [ 'width', _width ], [ 'height', _height ], [ 'display', _this.displayMode ] ], _key, _value; // Show the rectangle once it gets created, unless visibility was set to // hidden in the constructor. if(!_this.isHidden) { _styles.push( [ 'visibility', 'inherit' ] ); } for(;i<_styles.length;i++){ _key = _styles[i][0]; _value = _styles[i][1]; if( i < 6 && _value !== _auto ){ _value += 'px'; } _styl(_elemId,_key,_value,true); } _this._updateZIndex(); if ( _this._cachedLeft !== _rect.left || _this._cachedTop !== _rect.top) { _this.invalidatePositionCache(); _this._cachedLeft = _rect.left; _this._cachedTop = _rect.top; } _this.drawn = true; } return this; }, /** -- * This method updates the z-index property of the children of self. * It's essentially a wrapper for HSystem.updateZIndexOfChildren passed * with the viewId of self. * ++ **/ _updateZIndex: function() { HSystem.updateZIndexOfChildren(this.viewId); }, /** -- * This method updates the z-index property of the siblings of self. * It's essentially a wrapper for HSystem.updateZIndexOfChildren passed * with the parent's viewId of self. * ++ **/ _updateZIndexAllSiblings: function() { HSystem.updateZIndexOfChildren(this.parent.viewId); }, /** = Description * The higher level draw wrapper for drawRect, drawMarkup and drawSubviews. * Finally calls refresh. * * = Returns * +self+ * **/ draw: function() { var _isDrawn = this.drawn; this.drawRect(); if(!_isDrawn){ this.firstDraw(); if(this['componentName']!==undefined){ this.drawMarkup(); } this.drawSubviews(); if(this.options.style){ this.setStyles( this.options.style ); } if(this.options.html){ this.setHTML(this.options.html); } if(!this.isHidden){ this.show(); } } this.refresh(); return this; }, /** = Description * Called once, before the layout of the view is initially drawn. * Doesn't do anything by itself, but provides an extension point. * **/ firstDraw: function(){ }, /** = Description * Called once, when the layout of the view is initially drawn. * Doesn't do anything by itself, but provides an extension point for making * subviews. * **/ drawSubviews: function(){ }, /** -- * Loads the markup from theme manager. If this.preserveTheme is set to true, * the this.theme is used for loading the markup. Otherwise the currently * active theme is used. * ++ **/ _loadMarkup: function() { var _themeName, _markup; if (this.preserveTheme) { _themeName = this.theme; } else { _themeName = HThemeManager.currentTheme; } _markup = HThemeManager.getMarkup( _themeName, this.componentName, this.themePath ); if(_markup === false){ console.log('Warning: Markup template for "'+this.componentName+'" using theme "'+_themeName+'" not loaded.'); } this.markup = _markup; return (_markup !== false); }, /** = Description * Replaces the contents of the view's DOM element with html from the theme specific html file. * * = Returns * +self+ **/ markupElemNames: ['bg', 'label', 'state', 'control', 'value', 'subview'], drawMarkup: function() { // ELEM.setStyle(this.elemId, 'display', 'none', true); // continue processing from here on: var _markupStatus = this._loadMarkup(); this.bindMarkupVariables(); ELEM.setHTML(this.elemId, this.markup); this.markupElemIds = {}; for(var i=0; i < this.markupElemNames.length; i++ ) { var _partName = this.markupElemNames[ i ], _elemName = _partName + this.elemId, _htmlIdMatch = ' id="' + _elemName + '"'; if( this.markup.indexOf( _htmlIdMatch ) !== -1 ) { this.markupElemIds[ _partName ] = this.bindDomElement( _elemName ); } } // ELEM.setStyle(this.elemId, 'display', this.displayMode ); return this; }, /** = Description * Replaces the contents of the view's DOM element with custom html. * * = Parameters * +_html+:: The HTML (string-formatted) to replace the content with. * * = Returns * +self+ * **/ setHTML: function( _html ) { ELEM.setHTML( this.elemId, _html ); return this; }, /** = Description * Wrapper for setHTML, sets escaped html, if tags and such are present. * * = Parameters * +_text+:: The text to set. If it contains any html, it's escaped. * * = Returns * +self+ **/ setText: function( _text ) { return this.setHTML( this.escapeHTML( _text ) ); }, /** = Description * Method to escape HTML from text. * * Converts < to < and > to > and & to & * * = Parameters * +_html+:: The html to escape. * * = Returns * A string with the html escaped. **/ escapeHTML: function( _html ) { if( typeof _html !== 'string' ) { return _html.toString(); } for( var i=0, _reFrom, _reTo, _reArr = this._escapeHTMLArr; i < _reArr.length; i++ ){ _reFrom = _reArr[i][0]; _reTo = _reArr[i][1]; _html = _html.replace( _reFrom, _reTo ); } return _html; }, _escapeHTMLArr: [ [ new RegExp( /&/gmi ), '&' ], [ new RegExp( />/gmi ), '>' ], [ new RegExp( /' ] ], /** = Description * * This method should be extended in order to redraw only specific parts. The * base implementation calls optimizeWidth when optimizeWidthOnRefresh is set * to true. * * = Returns * +self+ * **/ refresh: function() { if(this.drawn) { // this.drawn is checked here so the rectangle doesn't get drawn by the // constructor when setRect() is initially called. this.drawRect(); } if(this.optimizeWidthOnRefresh) { this.optimizeWidth(); } if(this.refreshOnLabelChange){ this.refreshLabel(); } return this; }, /** Gets the size of the parent. If the parent is the document body, uses the browser window size. **/ parentSize: function(){ if(this.parent.elemId === 0){ var _winSize = ELEM.windowSize(); return [ _winSize[0], _winSize[1] ]; } else { var _rect = this.parent.rect, _width = _rect.width, _height = _rect.height; if (this.parent.flexLeft && this.parent.flexRight){ _width = parseInt( ELEM.getStyle(this.parent.elemId,'width',true), 10 ); } if (this.parent.flexBottom && this.parent.flexTop){ _height = parseInt( ELEM.getStyle(this.parent.elemId,'height',true), 10 ); } return [ _width, _height ]; } }, /** Returns the maximum rect using the #parentSize. **/ maxRect: function(){ var _parentSize = this.parentSize(); return [ 0, 0, _parentSize[0], _parentSize[1] ]; }, minWidth: 0, setMinWidth: function(_minWidth){ this.minWidth = _minWidth; ELEM.setStyle( this.elemId, 'min-width', this.minWidth+'px', true); }, minHeight: 0, setMinHeight: function(_minHeight){ this.minHeight = _minHeight; ELEM.setStyle( this.elemId, 'min-height', this.minHeight+'px', true); }, /** = Description * Replaces the rect of the component with a new HRect instance and * then refreshes the display. * * = Parameters * +_rect+:: The new HRect instance to replace the old rect instance with. * +_rect+:: Array format, see HView#constructor for further details. * * = Returns * +self+ * **/ setRect: function(_rect) { if (this.rect) { this.rect.release(this); } if(typeof _rect === 'string'){ _rect = this[_rect](); } if(_rect instanceof Array){ var _arrLen = _rect.length, _throwPrefix = 'HView.setRect: If the HRect instance is replaced by an array, '; if((_arrLen === 4) || (_arrLen === 6)){ var _leftOffset = _rect[0], _topOffset = _rect[1], _width = _rect[2], _height = _rect[3], _rightOffset = ((_arrLen === 6)?_rect[4]:null), _bottomOffset = ((_arrLen === 6)?_rect[5]:null), _validLeftOffset = (typeof _leftOffset === 'number'), _validTopOffset = (typeof _topOffset === 'number'), _validRightOffset = (typeof _rightOffset === 'number'), _validBottomOffset = (typeof _bottomOffset === 'number'), _validWidth = (typeof _width === 'number'), _validHeight = (typeof _height === 'number'), _right, _bottom; if(_arrLen === 6){ var _parentSize = this.parentSize(), _parentWidth = _parentSize[0], _parentHeight = _parentSize[1]; } if( (!_validLeftOffset && !_validRightOffset) || (!_validTopOffset && !_validBottomOffset) ){ console.log(_throwPrefix + '(left or top) and (top or bottom) must be specified.'); } else if( (!_validWidth && !(_validLeftOffset && _validRightOffset)) || (!_validHeight && !(_validTopOffset && _validBottomOffset)) ){ console.log(_throwPrefix + 'the (height or width) must be specified unless both (left and top) or (top and bottom) are specified.'); } this.setFlexLeft(_validLeftOffset,_leftOffset); this.setFlexTop(_validTopOffset,_topOffset); this.setFlexRight(_validRightOffset,_rightOffset); this.setFlexBottom(_validBottomOffset,_bottomOffset); if(_validLeftOffset && _validWidth && !_validRightOffset){ _right = _leftOffset + _width; } else if(!_validLeftOffset && _validWidth && _validRightOffset){ _right = _parentWidth-_validRightOffset; _leftOffset = _right-_width; } else if(_validLeftOffset && _validRightOffset){ _right = _parentWidth - _rightOffset; _validWidth && this.setMinWidth( _width ); _right = _parentWidth - _rightOffset; } if(_validTopOffset && _validHeight && !_validBottomOffset){ _bottom = _topOffset + _height; } else if(!_validTopOffset && _validHeight && _validBottomOffset){ _bottom = _parentHeight-_validBottomOffset; _topOffset = _bottom-_height; } else if(_validTopOffset && _validBottomOffset){ _bottom = _parentHeight - _bottomOffset; _validHeight && this.setMinHeight( _height ); _bottom = _parentHeight - _bottomOffset; } this.rect = HRect.nu(_leftOffset,_topOffset,_right,_bottom); } else { console.log(_throwPrefix + 'the length has to be either 4 or 6.'); } } else { this.rect = _rect; } this.rect.bind(this); this.refresh(); return this; }, /** = Description * Sets any arbitary style of the main DOM element of the component. * Utilizes Element Manager's drawing queue/cache to perform the action. * * = Parameters * +_name+:: The style name (css syntax, eg. 'background-color') * +_value+:: The style value (css syntax, eg. 'rgb(255,0,0)') * +_cacheOverride+:: Cache override flag. * * = Returns * +self+ * **/ setStyle: function(_name, _value, _cacheOverride) { if (this.elemId) { ELEM.setStyle(this.elemId, _name, _value, _cacheOverride); } return this; }, setStyles: function(_styles){ var _styleItem, _styleKey, _styleValue, i = 0; for(;i<_styles.length;i++){ _styleItem = _styles[i]; _styleKey = _styleItem[0]; _styleValue = _styleItem[1]; this.setStyle(_styleKey,_styleValue); } return this; }, /** = Description * Returns a style of the main DOM element of the component. * Utilizes +ELEM+ cache to perform the action. * * = Parameters * +_name+:: The style name (css syntax, eg. 'background-color') * * = Returns * The style property value (css syntax, eg. 'rgb(255,0,0)') * **/ style: function(_name) { if (this.elemId) { return ELEM.getStyle(this.elemId, _name); } return ''; }, /** = Description * Sets a style for a specified markup element that has been bound to this * view. * * = Parameters * +_partName+:: The identifier of the markup element. * +_name+:: The style name * +_value+:: The style value * * = Returns * +self+ * **/ setStyleOfPart: function(_partName, _name, _value, _cacheOverride) { if (!this['markupElemIds']){ console.log('Warning, setStyleOfPart: no markupElemIds'); } else if (this.markupElemIds[_partName]===undefined) { console.log('Warning, setStyleOfPart: partName "'+_partName+'" does not exist for viewId '+this.viewId+'.'); } else { ELEM.setStyle(this.markupElemIds[_partName], _name, _value, _cacheOverride); } return this; }, /** = Description * Returns a style of a specified markup element that has been bound to this * view. * * = Parameters * +_partName+:: The identifier of the markup element. * +_name+:: The style name * * = Returns * The style of a specified markup element. * **/ styleOfPart: function(_partName, _name) { if (this.markupElemIds[_partName]===undefined) { console.log('Warning, styleOfPart: partName "'+_partName+'" does not exist for viewId '+this.viewId+'.'); return ''; } return ELEM.getStyle(this.markupElemIds[_partName], _name); }, /** = Description * Sets a style of a specified markup element that has been bound to this * view. * * = Parameters * +_partName+:: The identifier of the markup element. * +_value+:: Value for markup element. * * = Returns * +self+ * **/ setMarkupOfPart: function( _partName, _value ) { if (this.markupElemIds[_partName]===undefined) { console.log('Warning, setMarkupOfPart: partName "'+_partName+'" does not exist for viewId '+this.viewId+'.'); } else { ELEM.setHTML( this.markupElemIds[_partName], _value ); } return this; }, /** = Description * Returns a style of a specified markup element that has been bound to this * view. * * = Parameters * +_partName+:: The identifier of the markup element. * * = Returns * The style of a specified markup element. * **/ markupOfPart: function(_partName) { if (this.markupElemIds[_partName]===undefined) { console.log('Warning, markupOfPart: partName "'+_partName+'" does not exist for viewId '+this.viewId+'.'); return ''; } return ELEM.getHTML(this.markupElemIds[_partName]); }, /** = Description * Hides the component's main DOM element (and its children). * * = Returns * +self+ * **/ hide: function() { if(!this.isHidden) { var _setStyl = ELEM.setStyle, _elemId = this.elemId; _setStyl(_elemId,'visibility', 'hidden'); // Required for the old, buggy Mozilla engines ( Firefox versions below 3.0 ) // At least text fields would show through from hidden parent elements. // Disabled, because keeping the display as none causes hidden views to have no dimensions at all. // _setStyl(_elemId,'display', 'none'); this.isHidden = true; } return this; }, /** = Description * Restores the visibility of the component's main DOM element (and its children). * * = Return * +self+ * **/ show: function() { if(this.isHidden) { var _setStyl = ELEM.setStyle, _elemId = this.elemId; _setStyl(_elemId,'visibility', 'inherit'); _setStyl(_elemId,'display', this.displayMode); this.isHidden = false; } return this; }, /** = Description * Toggles between hide and show. * * = Returns * +self+ * **/ toggle: function() { if(this.isHidden) { this.show(); } else { this.hide(); } return this; }, /** = Description * Call this if you need to remove a component from its parent's views array without * destroying the DOM element itself, making it in effect a view without parent. * Useful, for example, for moving a view from one parent component to another. * * = Returns * +self+ * **/ remove: function() { if( this.parent ) { var _viewZIdx = this.parent.viewsZOrder.indexOf(this.viewId), _viewPIdx = this.parent.views.indexOf(this.viewId); this.parent.views.splice(_viewPIdx,1); HSystem.delView(this.viewId); this.parent.viewsZOrder.splice( _viewZIdx, 1 ); var _sysUpdateZIndexOfChildrenBufferIndex = HSystem._updateZIndexOfChildrenBuffer.indexOf( this.viewId ); if(_sysUpdateZIndexOfChildrenBufferIndex !== -1){ HSystem._updateZIndexOfChildrenBuffer.splice( _sysUpdateZIndexOfChildrenBufferIndex, 1 ); } this._updateZIndexAllSiblings(); this.parent = null; this.parents = []; } return this; }, /** = Description * Deletes the component and all its children. * Should normally be called from the parent. * **/ die: function() { // hide self, makes destruction seem faster this.hide(); this.drawn = false; this.stopAnimation(); // Delete the children first. var _childViewId, i; while (this.views.length !== 0) { _childViewId = this.views[0]; this.destroyView(_childViewId); } // Remove this object's bindings, except the DOM element. this.remove(); // Remove the DOM element bindings. for ( i = 0; i < this._domElementBindings.length; i++) { ELEM.del(this._domElementBindings[i]); } this._domElementBindings = []; // Remove the DOM object itself ELEM.del(this.elemId); this.rect = null; var _this = this; for( i in _this ){ _this[i] = null; delete _this[i]; } }, /** Recursive idle poller. Should be extended if functionality is desired. **/ onIdle: function() { for(var i = 0; i < this.views.length; i++) { HSystem.views[this.views[i]].onIdle(); } }, /** Used by addView to build a parents array of parent classes. **/ buildParents: function(_viewId){ var _view = HSystem.views[_viewId]; _view.parent = this; _view.parents = []; for(var _parentNum = 0; _parentNum < this.parents.length; _parentNum++) { _view.parents.push(this.parents[_parentNum]); } _view.parents.push(this); }, /** = Description * Adds a sub-view/component to the view. Called from inside the * HView#constructor and should be automatic for all components that accept * the 'parent' parameter, usually the second argument, after the HRect. May * also be used to attach a freely floating component (removed with remove) * to another component. * * = Parameter * +_view+:: Usually this inside HView derivate components. * * = Returns * The view id. * **/ addView: function(_view) { var _viewId = HSystem.addView(_view); this.views.push(_viewId); this.buildParents(_viewId); this.viewsZOrder.push(_viewId); return _viewId; }, /** = Description * Call this if you need to remove a child view from this view without * destroying its element, making it in effect a view without parent. * Useful, for example, for moving a view from one parent component to another. * * = Parameters * +_viewId+:: The parent-specific view id. Actually an array index. * * = Returns * +self+ * **/ removeView: function(_viewId) { HSystem.views[_viewId].remove(); return this; }, /** = Description * Call this if you need to remove a child view from this view, destroying its * child elements recursively and removing all DOM elements too. * * = Parameters * +_viewId+:: The parent-specific view id. Actually an array index. * * = Returns * +self+ **/ destroyView: function(_viewId) { HSystem.views[_viewId].die(); return this; }, /** = Description * Returns bounds rectangle that defines the size and coordinate system * of the component. This should be identical to the rectangle used in * constructing the object, unless it has been changed after construction. * * = Returns * A new instance with identical values to this component's rect. * **/ bounds: function() { // Could be cached. var _bounds = new HRect(this.rect); _bounds.right -= _bounds.left; _bounds.left = 0; _bounds.bottom -= _bounds.top; _bounds.top = 0; return _bounds; }, /** = Description * This method resizes the view, without moving its left and top sides. * It adds horizontal coordinate units to the width and vertical units to * the height of the view. * Since a View's frame rectangle must be aligned on screen pixels, only * integral values should be passed to this method. Values with * fractional components will be rounded to the nearest whole integer. * If the View is attached to a window, this method causes its parent view * to be updated, so the View is immediately displayed in its new size. If it * doesn't have a parent or isn't attached to a window, this method * merely alter its frame and bounds rectangle. * * = Parameters * +_horizonal+:: Horizonal units to add to the width (negative units subtract) * +_vertical+:: Vertical units to add to the height (negative units subtract) * * = Returns * +self+ * **/ resizeBy: function(_horizontal, _vertical) { var _rect = this.rect; _rect.right += _horizontal; _rect.bottom += _vertical; _rect.updateSecondaryValues(); this.drawRect(); return this; }, /** = Description * This method makes the view width units wide * and height units high. This method adjust the right and bottom * components of the frame rectangle accordingly. * Since a View's frame rectangle must be aligned on screen pixels, only * integral values should be passed to this method. Values with * fractional components will be rounded to the nearest whole integer. * If the View is attached to a window, this method causes its parent view * to be updated, so the View is immediately displayed in its new size. If it * doesn't have a parent or isn't attached to a window, this method * merely alter its frame and bounds rectangle. * * = Parameters * +_width+:: The new width of the view. * +_height+:: The new height of the view. * * = Returns * +self+ * **/ resizeTo: function(_width, _height) { var _rect = this.rect; _rect.right = _rect.left + _width; _rect.bottom = _rect.top + _height; _rect.updateSecondaryValues(); this.drawRect(); return this; }, /** = Descripion * This method moves the view to a new coordinate. It adjusts the * left and top components of the frame rectangle accordingly. * Since a View's frame rectangle must be aligned on screen pixels, only * integral values should be passed to this method. Values with * fractional components will be rounded to the nearest whole integer. * If the View is attached to a window, this method causes its parent view * to be updated, so the View is immediately displayed in its new size. If it * doesn't have a parent or isn't attached to a window, this method * merely alter its frame and bounds rectangle. * * = Parameters * +_x+:: The new x-coordinate of the view. * +_y+:: The new y-coordinate of the view. * * +_point+:: The new coordinate point of the view. * * = Returns * +self+ * **/ offsetTo: function() { this.rect.offsetTo.apply(this.rect, arguments); this.drawRect(); return this; }, /** = Description * Alias method for offsetTo. * * = Returns * +self+ * **/ moveTo: function() { this.offsetTo.apply(this, arguments); return this; }, /** = Description * This method re-positions the view without changing its size. * It adds horizontal coordinate units to the x coordinate and vertical * units to the y coordinate of the view. * Since a View's frame rectangle must be aligned on screen pixels, only * integral values should be passed to this method. Values with * fractional components will be rounded to the nearest whole integer. * If the View is attached to a window, this method causes its parent view * to be updated, so the View is immediately displayed in its new size. If it * doesn't have a parent or isn't attached to a window, this method * merely alter its frame and bounds rectangle. * * = Parameters * +_horizonal+:: Horizonal units to change the x coordinate (negative units subtract) * +_vertical+:: Vertical units to add to change the y coordinate (negative units subtract) * * = Returns * +self+ * **/ offsetBy: function(_horizontal, _vertical) { this.rect.offsetBy(_horizontal, _vertical); this.drawRect(); return this; }, /** = Description * Alias method for offsetBy. * * = Returns * +self+ * **/ moveBy: function() { this.offsetBy.apply(this, arguments); return this; }, /** = Description * Brings the view to the front by changing its Z-Index. * * = Returns * +self+ * **/ bringToFront: function() { if (this.parent) { var _index = this.zIndex(); this.parent.viewsZOrder.splice(_index, 1); this.parent.viewsZOrder.push(this.viewId); this._updateZIndexAllSiblings(); } return this; }, /** = Description * Brings itself to the front of the given view by changing its Z-Index. * Only works on sibling views. * * = Parameters * +_view+:: The view to bring to the front of. * * = Returns * +self+ * **/ bringToFrontOf: function(_view){ if(this.parent.viewId === _view.parent.viewId){ this.parent.viewsZOrder.splice( this.zIndex(), 1 ); // removes selfs index from the array this.parent.viewsZOrder.splice( _view.zIndex()+1, 0, this.viewId); // sets itself in front of to _view this._updateZIndexAllSiblings(); } return this; }, /** = Description * Sends itself to the back of the given view by changing its Z-Index. * Only works on sibling views. * * = Parameters * +_view+:: The view to send to the back of. * * = Returns * +self+ * **/ sendToBackOf: function(_view){ if(this.parent.viewId === _view.parent.viewId){ this.parent.viewsZOrder.splice( this.zIndex(), 1 ); // removes selfs index from the array this.parent.viewsZOrder.splice( _view.zIndex(), 0, this.viewId); // sets itself in back of to _view this._updateZIndexAllSiblings(); } return this; }, /** = Description * Sends itself one step backward by changing its Z-Index. * * = Returns * +self+ * **/ sendBackward: function(){ var _index = this.zIndex(); if(_index!==0){ this.parent.viewsZOrder.splice( _index, 1 ); // removes selfs index from the array this.parent.viewsZOrder.splice( _index-1, 0, this.viewId); // moves selfs position to one step less than where it was this._updateZIndexAllSiblings(); } return this; }, /** = Description * Brings itself one step forward by changing its Z-Index. * * = Returns * +self+ * **/ bringForward: function(){ var _index = this.zIndex(); if(_index!==this.parent.viewsZOrder.length-1){ this.parent.viewsZOrder.splice( _index, 1 ); // removes selfs index from the array this.parent.viewsZOrder.splice( _index+1, 0, this.viewId); // moves selfs position to one step more than it was this._updateZIndexAllSiblings(); } return this; }, /** = Description * Sends the view to the back by changing its Z-Index. * * = Returns * +self+ * **/ sendToBack: function() { if (this.parent) { var _index = this.zIndex(); this.parent.viewsZOrder.splice(_index, 1); // removes this index from the arr this.parent.viewsZOrder.splice(0, 0, this.viewId); // unshifts viewId this._updateZIndexAllSiblings(); } return this; }, /** = Description * Use this method to get the Z-Index of itself. * * = Returns * The current Z-Index value. * **/ zIndex: function() { if (!this.parent) { return -1; } // Returns the z-order of this item as seen by the parent. return this.parent.viewsZOrder.indexOf(this.viewId); }, /** = Description * Measures the characters encoded in length bytes of the string - or, * if no length is specified, the entire string up to the null character, * '0', which terminates it. The return value totals the width of all the * characters in coordinate units; it's the length of the baseline required * to draw the string. * * = Parameters * +_string+:: The string to measure. * +_length+:: Optional, How many characters to count. * +_elemId+:: Optional, The element ID where the temporary string is created * in. * +_wrap+:: Optional boolean value, wrap whitespaces? * +_extraCss+:: Optional, extra css to add. * * = Returns * The width in pixels required to draw a string in the font. * **/ stringSize: function(_string, _length, _elemId, _wrap, _extraCss) { if (_length || _length === 0) { _string = _string.substring(0, _length); } if (!_elemId && _elemId !== 0) { _elemId = 0; //this.elemId; } if (!_extraCss) { _extraCss = ''; } if (!_wrap){ _extraCss += 'white-space:nowrap;'; } var _stringElem = ELEM.make(_elemId,'span'); ELEM.setCSS(_stringElem, "visibility:hidden;"+_extraCss); ELEM.setHTML(_stringElem, _string); // ELEM.flushLoop(); var _visibleSize=ELEM.getSize(_stringElem); // console.log('visibleSize',_visibleSize); ELEM.del(_stringElem); return [_visibleSize[0]+_visibleSize[0]%2,_visibleSize[1]+_visibleSize[1]%2]; }, /** Returns the string width **/ stringWidth: function(_string, _length, _elemId, _extraCss){ return this.stringSize(_string, _length, _elemId, false, _extraCss)[0]; }, /** Returns the string height. **/ stringHeight: function(_string, _length, _elemId, _extraCss){ return this.stringSize(_string, _length, _elemId, true, _extraCss)[1]; }, /** Returns the X coordinate that has the scrolled position calculated. **/ pageX: function() { return ELEM._getVisibleLeftPosition( this.elemId ); }, /** Returns the Y coordinate that has the scrolled position calculated. **/ pageY: function() { return ELEM._getVisibleTopPosition( this.elemId ); }, /** = Description * Sets the label on a control component: the text that's displayed in * HControl extensions. Visual functionality is implemented in component * theme templates and refreshLabel method extensions. * * Avoid extending directly, extend +refreshLabel+ instead. * * = Parameters * +_label+:: The text the component should display. * * = Returns * +self+ * **/ setLabel: function(_label) { if(this.escapeLabelHTML){ _label = this.escapeHTML( _label ); } var _this = this, _differs = (_label !== _this.label); if(_differs){ _this.label = _label; _this.options.label = _label; _this.refresh(); } return this; }, /** = Description * Called when the +self.label+ has been changed. By default * tries to update the label element defined in the theme of * the component. Of course, the HControl itself doesn't * define a theme, so without a theme doesn't do anything. * * = Returns * +self+ * **/ refreshLabel: function(){ if(this.markupElemIds){ if(this.markupElemIds['label']){ ELEM.setHTML(this.markupElemIds.label,this.label); } } return this; }, /** Returns the HPoint that has the scrolled position calculated. **/ pageLocation: function() { return new HPoint(this.pageX(), this.pageY()); }, /** = Description * An abstract method that derived classes may implement, if they are able to * resize themselves so that their content fits nicely inside. * Similar to pack, might be renamed when components are written to * be savvy of this feature. **/ optimizeWidth: function() { }, /** = Description * Invalidates event manager's element position cache for this view and its * subviews. Actual functionality is implemented in HControl. * * = Returns * +self+ * **/ invalidatePositionCache: function() { for(var i=0; i -1) { ELEM.del(_elementId); this._domElementBindings.splice(_indexOfElementId, 1); } } }); HView.implement(HMarkupView); HView.implement(HMorphAnimation);