UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

1,208 lines (1,050 loc) 34.8 kB
/* ************************************************************************ qooxdoo - the new era of web development http://qooxdoo.org Copyright: 2004-2008 1&1 Internet AG, Germany, http://www.1und1.de License: MIT: https://opensource.org/licenses/MIT See the LICENSE file in the project's top-level directory for details. Authors: * Fabian Jakobs (fjakobs) * Sebastian Werner (wpbasti) ************************************************************************ */ /** * The image class displays an image file * * This class supports image clipping, which means that multiple images can be combined * into one large image and only the relevant part is shown. * * *Example* * * Here is a little example of how to use the widget. * * <pre class='javascript'> * var image = new qx.ui.basic.Image("icon/32/actions/format-justify-left.png"); * * this.getRoot().add(image); * </pre> * * This example create a widget to display the image * <code>icon/32/actions/format-justify-left.png</code>. * * *External Documentation* * * <a href='http://qooxdoo.org/docs/#desktop/widget/image.md' target='_blank'> * Documentation of this widget in the qooxdoo manual.</a> * * NOTE: Instances of this class must be disposed of after use * */ qx.Class.define("qx.ui.basic.Image", { extend: qx.ui.core.Widget, /* ***************************************************************************** CONSTRUCTOR ***************************************************************************** */ /** * @param source {String?null} The URL of the image to display. */ construct(source) { this.__contentElements = {}; super(); if (source) { this.setSource(source); } }, /* ***************************************************************************** PROPERTIES ***************************************************************************** */ properties: { /** The URL of the image. Setting it will possibly abort loading of current image. */ source: { check: "String", init: null, nullable: true, event: "changeSource", apply: "_applySource", themeable: true }, /** * Whether the image should be scaled to the given dimensions * * This is disabled by default because it prevents the usage * of image clipping when enabled. */ scale: { check: "Boolean", init: false, event: "changeScale", themeable: true, apply: "_applyScale" }, /** * Whether to preserve the image ratio (ie prevent distortion), and which dimension * to prioritise */ forceRatio: { init: "auto", check: ["disabled", "height", "width", "auto"], apply: "_applyDimension" }, /** * Whether to allow scaling the image up */ allowScaleUp: { init: false, check: "Boolean", apply: "_applyDimension" }, // overridden appearance: { refine: true, init: "image" }, // overridden allowShrinkX: { refine: true, init: false }, // overridden allowShrinkY: { refine: true, init: false }, // overridden allowGrowX: { refine: true, init: false }, // overridden allowGrowY: { refine: true, init: false }, /** * Source of image to display if the image in the `source` property fails to load. */ fallbackSource: { check: "String", init: null, nullable: true, event: "changeFallbackSource" } }, /* ***************************************************************************** EVENTS ***************************************************************************** */ events: { /** * Fired if the image source can not be loaded. This event can only be * fired for the first loading of an unmanaged resource (external image). */ loadingFailed: "qx.event.type.Event", /** * Fired if the image has been loaded. This is even true for managed * resources (images known by generator). */ loaded: "qx.event.type.Event", /** Fired when the pending request has been aborted. */ aborted: "qx.event.type.Event" }, statics: { PLACEHOLDER_IMAGE: "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" }, /* ***************************************************************************** MEMBERS ***************************************************************************** */ members: { __width: null, __height: null, __mode: null, __contentElements: null, __currentContentElement: null, __wrapper: null, __requestId: 0, /** * If the image in the `source` property failed to load. */ __failedToLoad: false, /** * @type {string} The actual source of the image that we display. * While the `source` property is what we want to display, * this property is what we are actually showing. * This can be the `source` property but it can also be the `fallbackSource` property * if the image in the `source` property failed to load. */ __sourceToDisplay: null, // overridden _onChangeTheme() { super._onChangeTheme(); // restyle source (theme change might have changed the resolved url) this._styleSource(); }, /* --------------------------------------------------------------------------- WIDGET API --------------------------------------------------------------------------- */ // overridden getContentElement() { return this.__getSuitableContentElement(); }, // overridden _createContentElement() { return this.__getSuitableContentElement(); }, // overridden _getContentHint() { return { width: this.__width || 0, height: this.__height || 0 }; }, // overridden _applyDecorator(value, old) { super._applyDecorator(value, old); var source = this.__sourceToDisplay; source = qx.util.AliasManager.getInstance().resolve(source); var el = this.getContentElement(); if (this.__wrapper) { el = el.getChild(0); } this.__setSource(el, source); }, // overridden _applyTextColor(value) { if (this.__getMode() === "font") { var el = this.getContentElement(); if (this.__wrapper) { el = el.getChild(0); } if (value) { el.setStyle( "color", qx.theme.manager.Color.getInstance().resolve(value) ); } else { el.removeStyle("color"); } } }, // overridden _applyPadding(value, old, name) { super._applyPadding(value, old, name); var element = this.getContentElement(); if (this.__wrapper) { element.getChild(0).setStyles({ top: (this.getPaddingTop() || 0) + "px", left: (this.getPaddingLeft() || 0) + "px" }); } else if (this.__getMode() === "font") { let bounds = this.getBounds() || {}; element.setStyles({ top: (bounds.top || 0) + (this.getPaddingTop() || 0) + "px", left: (bounds.left || 0) + (this.getPaddingLeft() || 0) + "px" }); } else { element.setPadding( this.getPaddingLeft() || 0, this.getPaddingTop() || 0 ); } }, renderLayout(left, top, width, height) { super.renderLayout(left, top, width, height); var element = this.getContentElement(); if (this.__wrapper) { element.getChild(0).setStyles({ width: width - (this.getPaddingLeft() || 0) - (this.getPaddingRight() || 0), height: height - (this.getPaddingTop() || 0) - (this.getPaddingBottom() || 0), top: this.getPaddingTop() || 0, left: this.getPaddingLeft() || 0 }); } }, /* --------------------------------------------------------------------------- IMAGE API --------------------------------------------------------------------------- */ // property apply, overridden _applyEnabled(value, old) { super._applyEnabled(value, old); if (this.__sourceToDisplay) { this._styleSource(); } }, // property apply _applySource(value, old) { // abort loading current image if (old) { if (qx.io.ImageLoader.isLoading(old)) { qx.io.ImageLoader.abort(old); } } this.__failedToLoad = false; this.__sourceToDisplay = value; this._styleSource(); }, // property apply _applyScale(value) { this._styleSource(); }, /** * Remembers the mode to keep track which contentElement is currently in use. * @param mode {String} internal mode (alphaScaled|scaled|nonScaled) */ __setMode(mode) { this.__mode = mode; }, /** * Returns the current mode if set. Otherwise checks the current source and * the current scaling to determine the current mode. * * @return {String} current internal mode */ __getMode() { if (this.__mode == null) { var source = this.__sourceToDisplay; if (source && qx.lang.String.startsWith(source, "@")) { this.__mode = "font"; } var isPng = false; if (source != null) { isPng = source.endsWith(".png"); } if ( this.getScale() && isPng && qx.core.Environment.get("css.alphaimageloaderneeded") ) { this.__mode = "alphaScaled"; } else if (this.getScale()) { this.__mode = "scaled"; } else { this.__mode = "nonScaled"; } } return this.__mode; }, /** * Creates a contentElement suitable for the current mode * * @param mode {String} internal mode * @return {qx.html.Image} suitable image content element */ __createSuitableContentElement(mode) { var scale; var tagName; var clazz = qx.html.Image; switch (mode) { case "font": clazz = qx.html.Label; scale = true; tagName = "div"; break; case "alphaScaled": scale = true; tagName = "div"; break; case "nonScaled": scale = false; tagName = "div"; break; default: scale = true; tagName = "img"; break; } var element = new clazz(tagName); element.connectObject(this); element.setStyles({ overflowX: "hidden", overflowY: "hidden", boxSizing: "border-box" }); if (mode == "font") { element.setRich(true); element.setStyle("line-height", "1"); } else { element.setScale(scale); if (qx.core.Environment.get("css.alphaimageloaderneeded")) { var wrapper = (this.__wrapper = new qx.html.Element("div")); element.connectObject(this); wrapper.setStyle("position", "absolute"); wrapper.add(element); return wrapper; } } return element; }, /** * Returns a contentElement suitable for the current mode * * @return {qx.html.Image} suitable image contentElement */ __getSuitableContentElement() { if (this.$$disposed) { return null; } var mode = this.__getMode(); if (this.__contentElements[mode] == null) { this.__contentElements[mode] = this.__createSuitableContentElement(mode); } var element = this.__contentElements[mode]; if (!this.__currentContentElement) { this.__currentContentElement = element; } return element; }, /** * Applies the source to the clipped image instance or preload * an image to detect sizes and apply it afterwards. */ _styleSource() { var AliasManager = qx.util.AliasManager.getInstance(); var ResourceManager = qx.util.ResourceManager.getInstance(); var source = AliasManager.resolve(this.__sourceToDisplay); var element = this.getContentElement(); if (this.__wrapper) { element = element.getChild(0); } if (!source) { this.__resetSource(element); return; } this.__checkForContentElementSwitch(source); if ( qx.core.Environment.get("engine.name") == "mshtml" && (parseInt(qx.core.Environment.get("engine.version"), 10) < 9 || qx.core.Environment.get("browser.documentmode") < 9) ) { var repeat = this.getScale() ? "scale" : "no-repeat"; element.tagNameHint = qx.bom.element.Decoration.getTagName( repeat, source ); } var contentEl = this.__getContentElement(); // Detect if the image registry knows this image if (ResourceManager.isFontUri(source)) { this.__setManagedImage(contentEl, source); var color = this.getTextColor(); if (qx.lang.Type.isString(color)) { this._applyTextColor(color, null); } } else if (ResourceManager.has(source)) { var highResolutionSource = ResourceManager.findHighResolutionSource(source); if (highResolutionSource) { var imageWidth = ResourceManager.getImageWidth(source); var imageHeight = ResourceManager.getImageHeight(source); this.setWidth(imageWidth); this.setHeight(imageHeight); // set background size on current element (div or img) var backgroundSize = imageWidth + "px, " + imageHeight + "px"; this.__currentContentElement.setStyle( "background-size", backgroundSize ); this.setSource(highResolutionSource); source = highResolutionSource; } this.__setManagedImage(contentEl, source); this.__fireLoadEvent(); } else if (qx.io.ImageLoader.isLoaded(source)) { this.__setUnmanagedImage(contentEl, source); this.__fireLoadEvent(); } else { this.__loadUnmanagedImage(contentEl, source); } }, /** * Helper function, which fires <code>loaded</code> event asynchronously. * It emulates native <code>loaded</code> event of an image object. This * helper will be called, if you try to load a managed image or an * previously loaded unmanaged image. */ __fireLoadEvent() { this.__requestId++; qx.bom.AnimationFrame.request( function (rId) { // prevent firing of the event if source changed in the meantime if (rId === this.__requestId) { this.fireEvent("loaded"); } else { this.fireEvent("aborted"); } }.bind(this, this.__requestId) ); }, /** * Returns the content element. * @return {qx.html.Image} content element */ __getContentElement() { var contentEl = this.__currentContentElement; if (this.__wrapper) { contentEl = contentEl.getChild(0); } return contentEl; }, /** * Checks if the current content element is capable to display the image * with the current settings (scaling, alpha PNG) * * @param source {String} source of the image */ __checkForContentElementSwitch: qx.core.Environment.select("engine.name", { mshtml(source) { var alphaImageLoader = qx.core.Environment.get( "css.alphaimageloaderneeded" ); var isPng = source.endsWith(".png"); var isFont = source.startsWith("@"); if (isFont) { this.__setMode("font"); } else if (alphaImageLoader && isPng) { if (this.getScale() && this.__getMode() != "alphaScaled") { this.__setMode("alphaScaled"); } else if (!this.getScale() && this.__getMode() != "nonScaled") { this.__setMode("nonScaled"); } } else { if (this.getScale() && this.__getMode() != "scaled") { this.__setMode("scaled"); } else if (!this.getScale() && this.__getMode() != "nonScaled") { this.__setMode("nonScaled"); } } this.__checkForContentElementReplacement( this.__getSuitableContentElement() ); }, default(source) { var isFont = source && qx.lang.String.startsWith(source, "@"); if (isFont) { this.__setMode("font"); } else if (this.getScale() && this.__getMode() != "scaled") { this.__setMode("scaled"); } else if (!this.getScale() && this.__getMode() != "nonScaled") { this.__setMode("nonScaled"); } this.__checkForContentElementReplacement( this.__getSuitableContentElement() ); } }), /** * Checks the current child and replaces it if necessary * * @param elementToAdd {qx.html.Image} content element to add */ __checkForContentElementReplacement(elementToAdd) { var currentContentElement = this.__currentContentElement; if (currentContentElement != elementToAdd) { if (currentContentElement != null) { var pixel = "px"; var styles = {}; //inherit styles from current element var currentStyles = currentContentElement.getAllStyles(); if (currentStyles) { for (var prop in currentStyles) { styles[prop] = currentStyles[prop]; } } // Don't transfer background image when switching from image to icon font var mode = this.__getMode(); if (mode === "font" || mode === "scaled") { delete styles.backgroundImage; } // Copy dimension and location of the current content element var bounds = this.getBounds(); if (bounds != null) { styles.width = bounds.width + pixel; styles.height = bounds.height + pixel; } var insets = this.getInsets(); styles.left = parseInt(currentContentElement.getStyle("left") || insets.left) + pixel; styles.top = parseInt(currentContentElement.getStyle("top") || insets.top) + pixel; styles.zIndex = 10; var newEl = this.__wrapper ? elementToAdd.getChild(0) : elementToAdd; newEl.setStyles(styles, true); newEl.setSelectable(this.getSelectable()); if (!currentContentElement.isVisible()) { elementToAdd.hide(); } else if (!elementToAdd.isVisible()) { elementToAdd.show(); } if (!currentContentElement.isIncluded()) { elementToAdd.exclude(); } else if (!elementToAdd.isIncluded()) { elementToAdd.include(); } var container = currentContentElement.getParent(); if (container) { var index = container.getChildren().indexOf(currentContentElement); container.removeAt(index); container.addAt(elementToAdd, index); } // force re-application of source so __setSource is called again var hint = newEl.getNodeName(); if (newEl.setSource) { newEl.setSource(null); } else { newEl.setValue(""); } var currentEl = this.__getContentElement(); newEl.tagNameHint = hint; newEl.setAttribute("class", currentEl.getAttribute("class")); var currentDomEl = currentEl.getDomElement(); if (currentDomEl && !elementToAdd.getDomElement()) { // Flush elements to make sure the DOM elements are created. qx.html.Element.flush(); } var newDomEl = elementToAdd.getDomElement(); // copy event listeners var listeners = currentContentElement.getListeners() || []; listeners.forEach(function (listenerData) { elementToAdd.addListener( listenerData.type, listenerData.handler, listenerData.self, listenerData.capture ); }); if (currentDomEl && newDomEl) { // Switch the DOM elements' hash codes. This is required for the event // layer to work [BUG #7447] var currentHash = currentDomEl.$$hash; currentDomEl.$$hash = newDomEl.$$hash; newDomEl.$$hash = currentHash; } this.__currentContentElement = elementToAdd; } } }, /** * Use the ResourceManager to set a managed image * * @param el {Element} image DOM element * @param source {String} source path */ __setManagedImage(el, source) { var ResourceManager = qx.util.ResourceManager.getInstance(); var isFont = ResourceManager.isFontUri(source); // Try to find a disabled image in registry if (!this.getEnabled()) { var disabled = source.replace(/\.([a-z]+)$/, "-disabled.$1"); if (!isFont && ResourceManager.has(disabled)) { source = disabled; this.addState("replacement"); } else { this.removeState("replacement"); } } // Optimize case for enabled changes when no disabled image was found if (!isFont && el.getSource() === source) { return; } // Special case for non resource manager handled font icons if (isFont) { // Don't use scale if size is set via postfix if (this.getScale() && parseInt(source.split("/")[2], 10)) { this.setScale(false); } // Adjust size if scaling is applied var width; var height; if (this.getScale()) { var hint = this.getSizeHint(); width = this.getWidth() || hint.width; height = this.getHeight() || hint.height; } else { var font = this.__getFont(source); var size = parseInt(source.split("/")[2] || font.getSize(), 10); width = ResourceManager.getImageWidth(source) || size; height = ResourceManager.getImageHeight(source) || size; } this.__updateContentHint(width, height); this.__setSource(el, source); // Apply source } else { // Apply source this.__setSource(el, source); // Compare with old sizes and relayout if necessary this.__updateContentHint( ResourceManager.getImageWidth(source), ResourceManager.getImageHeight(source) ); } }, __setFontSize(el, width, height) { if (this.getScale()) { el.setStyle("fontSize", (width > height ? height : width) + "px"); } else { var source = qx.util.AliasManager.getInstance().resolve( this.__sourceToDisplay ); var sparts = source.split("/"); var font = this.__getFont(source); var size = parseInt(sparts[2] || font.getSize()); el.setStyle("fontSize", size + "px"); } }, _applyDimension() { super._applyDimension(); var isFont = this.__sourceToDisplay && qx.lang.String.startsWith(this.__sourceToDisplay, "@"); if (isFont) { var el = this.getContentElement(); if (el) { var hint = this.getSizeHint(); var width = this.getWidth() || hint.width || 40; var height = this.getHeight() || hint.height || 40; this.__setFontSize(el, width, height); } } else { this.__updateContentHint(); } }, /** * Use the infos of the ImageLoader to set an unmanaged image * * @param el {Element} image DOM element * @param source {String} source path */ __setUnmanagedImage(el, source) { var ImageLoader = qx.io.ImageLoader; // Apply source this.__setSource(el, source); // Compare with old sizes and relayout if necessary var width = ImageLoader.getWidth(source); var height = ImageLoader.getHeight(source); this.__updateContentHint(width, height); }, /** * Use the ImageLoader to load an unmanaged image * * @param el {Element} image DOM element * @param source {String} source path */ __loadUnmanagedImage(el, source) { var ImageLoader = qx.io.ImageLoader; if (qx.core.Environment.get("qx.debug")) { // loading external images via HTTP/HTTPS is a common usecase, as is // using data URLs. var sourceLC = source.toLowerCase(); if ( !sourceLC.startsWith("http") && !sourceLC.startsWith("data:image/") ) { var self = this.self(arguments); if (!self.__warned) { self.__warned = {}; } if (!self.__warned[source]) { this.debug("try to load an unmanaged relative image: " + source); self.__warned[source] = true; } } } // only try to load the image if it not already failed if (!ImageLoader.isFailed(source)) { ImageLoader.load(source, this.__loaderCallback, this); } else { this.__resetSource(el); if (!this.__failedToLoad && this.getFallbackSource()) { this.__failedToLoad = true; this.__sourceToDisplay = this.getFallbackSource(); this._styleSource(); } } }, /** * Reset source displayed by the DOM element. * * @param el {Element} image DOM element */ __resetSource(el) { if (el != null) { if (el instanceof qx.html.Image) { el.resetSource(); } else { el.resetValue(); } } }, __getFont(source) { var font = qx.theme.manager.Font.getInstance().resolve( source.match(/@([^/]+)/)[1] ); if (typeof font == "string") { throw new Error( `Cannot find font in virtual image source: '${source}'` ); } return font; }, /** * Sets image source on the DOM element * Combines the decorator's image styles with our own image to make sure * gradient and backgroundImage decorators work on Images. * * @param el {Element} image DOM element * @param source {String} source path */ __setSource(el, source) { var isFont = source && qx.lang.String.startsWith(source, "@"); if (isFont) { var ResourceManager = qx.util.ResourceManager.getInstance(); var font = this.__getFont(source); var fontStyles = qx.lang.Object.clone(font.getStyles()); delete fontStyles.color; el.setStyles(fontStyles); el.setStyle("font"); el.setStyle("display", "table-cell"); el.setStyle("verticalAlign", "middle"); el.setStyle("textAlign", "center"); this.__setFontSize(el, this.__width, this.__height); var charCode = ResourceManager.fromFontUriToCharCode(source); el.setValue(String.fromCharCode(charCode)); return; } else if (el.getNodeName() == "div") { // checks if a decorator already set. // In this case we have to merge background styles var decorator = qx.theme.manager.Decoration.getInstance().resolve( this.getDecorator() ); if (decorator) { var hasGradient = decorator.getStartColor() && decorator.getEndColor(); var hasBackground = decorator.getBackgroundImage(); if (hasGradient || hasBackground) { var repeat = this.getScale() ? "scale" : "no-repeat"; // get the style attributes for the given source var attr = qx.bom.element.Decoration.getAttributes(source, repeat); // get the background image(s) defined by the decorator var decoratorStyle = decorator.getStyles(true); var combinedStyles = { backgroundImage: attr.style.backgroundImage, backgroundPosition: attr.style.backgroundPosition || "0 0", backgroundRepeat: attr.style.backgroundRepeat || "no-repeat", position: "absolute" }; if (hasBackground) { combinedStyles["backgroundPosition"] += "," + decoratorStyle["background-position"] || "0 0"; combinedStyles["backgroundRepeat"] += ", " + decorator.getBackgroundRepeat(); } if (hasGradient) { combinedStyles["backgroundPosition"] += ", 0 0"; combinedStyles["backgroundRepeat"] += ", no-repeat"; } combinedStyles["backgroundImage"] += "," + (decoratorStyle["background-image"] || decoratorStyle["background"]); // apply combined background images el.setStyles(combinedStyles); return; } } else { // force re-apply to remove old decorator styles if (el.setSource) { el.setSource(null); } } } if (el.setSource) { el.setSource(source); el.setStyle("position", "absolute"); } }, /** * Tries to display the image in the `source` property again if it has failed to load. */ tryReload() { if (!this.__failedToLoad) { return; } this.__failedToLoad = false; this.__sourceToDisplay = this.getSource(); qx.io.ImageLoader.load( this.getSource(), () => this._styleSource(), undefined, { retryFailed: true } ); }, /** * @returns {boolean} If the image in the `source` property failed to load. */ hasFailedToLoad() { return this.__failedToLoad; }, /** * Event handler fired after the preloader has finished loading the icon * * @param source {String} Image source which was loaded * @param imageInfo {Map} Dimensions of the loaded image */ __loaderCallback(source, imageInfo) { // Ignore the callback on already disposed images if (this.$$disposed === true) { return; } // Ignore when the source has already been modified if ( source !== qx.util.AliasManager.getInstance().resolve(this.__sourceToDisplay) ) { this.fireEvent("aborted"); return; } /// Output a warning if the image could not loaded and quit if (imageInfo.failed) { this.warn("Image could not be loaded: " + source); this.fireEvent("loadingFailed"); if (!this.__failedToLoad && this.getFallbackSource()) { this.__failedToLoad = true; this.__sourceToDisplay = this.getFallbackSource(); this._styleSource(); return; } } else if (imageInfo.aborted) { this.fireEvent("aborted"); return; } else { this.fireEvent("loaded"); } // Update image this.__setUnmanagedImage(this.__getContentElement(), source); }, /** * Updates the content hint when the image size has been changed * * @param width {Integer} width of the image * @param height {Integer} height of the image */ __updateContentHint(width, height) { if (width === undefined) { width = this.__width; } if (height === undefined) { height = this.__height; } if (this._recalc(width, height)) { qx.ui.core.queue.Layout.add(this); } }, /** * Recalculates the size of the image, according to scaling parameters * @param maxWidth {Integer?} maximum width restriction * @param maxHeight {Integer?} minimum height restriction */ _recalc(originalWidth, originalHeight) { var maxWidth = this.getMaxWidth(); var maxHeight = this.getMaxHeight(); var minWidth = this.getMinWidth(); var minHeight = this.getMinHeight(); var width = originalWidth; var height = originalHeight; var ratio = originalHeight / originalWidth; switch (this.getForceRatio()) { case "height": if (maxHeight !== null && height > maxHeight) { height = maxHeight; width = height / ratio; } else if (height < minHeight) { height = minHeight; width = height / ratio; } if (height < maxHeight && this.isAllowScaleUp()) { height = maxHeight; width = height / ratio; } break; case "width": if (maxWidth !== null && width > maxWidth) { width = maxWidth; height = width * ratio; } else if (width < minWidth) { width = minWidth; height = width * ratio; } if (width < maxWidth && this.isAllowScaleUp()) { width = maxWidth; height = width * ratio; } break; case "auto": case "bestfit": if (maxWidth !== null && width > maxWidth) { width = maxWidth; height = width * ratio; } else if (width < minWidth) { width = minWidth; height = width * ratio; } if (width < maxWidth && this.isAllowScaleUp()) { width = maxWidth; height = width * ratio; } if (maxHeight !== null && height > maxHeight) { height = maxHeight; width = height / ratio; } break; } width = Math.round(width); height = Math.round(height); if (width != this.__width || height != this.__height) { this.__width = width; this.__height = height; return true; } return false; } }, /* ***************************************************************************** DESTRUCTOR ***************************************************************************** */ destruct() { for (var mode in this.__contentElements) { if (this.__contentElements.hasOwnProperty(mode)) { this.__contentElements[mode].disconnectObject(this); } } delete this.__currentContentElement; if (this.__wrapper) { delete this.__wrapper; } this._disposeMap("__contentElements"); } });