UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

363 lines (300 loc) 9.72 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: * Sebastian Werner (wpbasti) ************************************************************************ */ /** * The ImageLoader can preload and manage loaded image resources. It easily * handles multiple requests and supports callbacks for successful and failed * requests. * * After loading of an image the dimension of the image is stored as long * as the application is running. This is quite useful for in-memory layouting. * * Use {@link #load} to preload your own images. */ qx.Bootstrap.define("qx.io.ImageLoader", { statics : { /** @type {Map} Internal data structure to cache image sizes */ __data : {}, /** @type {Map} Default image size */ __defaultSize : { width : null, height : null }, /** @type {RegExp} Known image types */ __knownImageTypesRegExp : /\.(png|gif|jpg|jpeg|bmp)\b/i, /** @type {RegExp} Image types of a data URL */ __dataUrlRegExp : /^data:image\/(png|gif|jpg|jpeg|bmp)\b/i, /** * Whether the given image has previously been loaded using the * {@link #load} method. * * @param source {String} Image source to query * @return {Boolean} <code>true</code> when the image is loaded */ isLoaded : function(source) { var entry = this.__data[source]; return !!(entry && entry.loaded); }, /** * Whether the given image has previously been requested using the * {@link #load} method but failed. * * @param source {String} Image source to query * @return {Boolean} <code>true</code> when the image loading failed */ isFailed : function(source) { var entry = this.__data[source]; return !!(entry && entry.failed); }, /** * Whether the given image is currently loading. * * @param source {String} Image source to query * @return {Boolean} <code>true</code> when the image is loading in the moment. */ isLoading : function(source) { var entry = this.__data[source]; return !!(entry && entry.loading); }, /** * Returns the format of a previously loaded image * * @param source {String} Image source to query * @return {String ? null} The format of the image or <code>null</code> */ getFormat : function(source) { var entry = this.__data[source]; if (! entry || ! entry.format) { var result = this.__dataUrlRegExp.exec(source); if (result != null) { // If width and height aren't defined, provide some defaults var width = (entry && qx.lang.Type.isNumber(entry.width) ? entry.width : this.__defaultSize.width); var height = (entry && qx.lang.Type.isNumber(entry.height) ? entry.height : this.__defaultSize.height); entry = { loaded : true, format : result[1], width : width, height : height }; } } return entry ? entry.format : null; }, /** * Returns the size of a previously loaded image * * @param source {String} Image source to query * @return {Map} The dimension of the image (<code>width</code> and * <code>height</code> as key). If the image is not yet loaded, the * dimensions are given as <code>null</code> for width and height. */ getSize : function(source) { var entry = this.__data[source]; return entry ? { width: entry.width, height: entry.height } : this.__defaultSize; }, /** * Returns the image width * * @param source {String} Image source to query * @return {Integer} The width or <code>null</code> when the image is not loaded */ getWidth : function(source) { var entry = this.__data[source]; return entry ? entry.width : null; }, /** * Returns the image height * * @param source {String} Image source to query * @return {Integer} The height or <code>null</code> when the image is not loaded */ getHeight : function(source) { var entry = this.__data[source]; return entry ? entry.height : null; }, /** * Loads the given image. Supports a callback which is * executed when the image is loaded. * * This method works asynchronous. * * @param source {String} Image source to load * @param callback {Function?} Callback function to execute * The first parameter of the callback is the given source url, the * second parameter is the data entry which contains additional * information about the image. * @param context {Object?} Context in which the given callback should be executed */ load : function(source, callback, context) { // Shorthand var entry = this.__data[source]; if (!entry) { entry = this.__data[source] = {}; } // Normalize context if (callback && !context) { context = window; } // Already known image source if (entry.loaded || entry.loading || entry.failed) { if (callback) { if (entry.loading) { entry.callbacks.push(callback, context); } else { callback.call(context, source, entry); } } } else { // Updating entry entry.loading = true; entry.callbacks = []; if (callback) { entry.callbacks.push(callback, context); } // Create image element var el = document.createElement('img'); // Create common callback routine var boundCallback = qx.lang.Function.listener(this.__onload, this, el, source); // Assign callback to element el.onload = boundCallback; el.onerror = boundCallback; // Start loading of image el.src = source; // save the element for aborting entry.element = el; } }, /** * Abort the loading for the given url. * * @param source {String} URL of the image to abort its loading. */ abort : function (source) { var entry = this.__data[source]; if (entry && !entry.loaded) { entry.aborted = true; var callbacks = entry.callbacks; var element = entry.element; // Cleanup listeners element.onload = element.onerror = null; // prevent further loading element.src = ""; // Cleanup entry delete entry.callbacks; delete entry.element; delete entry.loading; for (var i=0, l=callbacks.length; i<l; i+=2) { callbacks[i].call(callbacks[i+1], source, entry); } } this.__data[source] = null; }, /** * Calls a method based on qx.globalErrorHandling */ __onload: function () { var callback = qx.core.Environment.select("qx.globalErrorHandling", { "true": qx.event.GlobalError.observeMethod(this.__onLoadHandler), "false": this.__onLoadHandler }); callback.apply(this, arguments); }, /** * Internal event listener for all load/error events. * * @signature function(event, element, source) * * @param event {Event} Native event object * @param element {Element} DOM element which represents the image * @param source {String} The image source loaded */ __onLoadHandler: function (event, element, source) { // Shorthand var entry = this.__data[source]; // [BUG #9149]: When loading a SVG IE11 won't have // the width/height of the element set, unless // it is inserted into the DOM. if(qx.bom.client.Engine.getName() == "mshtml" && parseFloat(qx.bom.client.Engine.getVersion()) === 11) { document.body.appendChild(element); } var isImageAvailable = function (imgElem) { return (imgElem && imgElem.height !== 0); }; // [BUG #7497]: IE11 doesn't properly emit an error event // when loading fails so augment success check if (event.type === "load" && isImageAvailable(element)) { // Store dimensions entry.loaded = true; entry.width = element.width; entry.height = element.height; // try to determine the image format var result = this.__knownImageTypesRegExp.exec(source); if (result != null) { entry.format = result[1]; } } else { entry.failed = true; } if(qx.bom.client.Engine.getName() == "mshtml" && parseFloat(qx.bom.client.Engine.getVersion()) === 11) { document.body.removeChild(element); } // Cleanup listeners element.onload = element.onerror = null; // Cache callbacks var callbacks = entry.callbacks; // Cleanup entry delete entry.loading; delete entry.callbacks; delete entry.element; // Execute callbacks for (var i = 0, l = callbacks.length; i < l; i += 2) { callbacks[i].call(callbacks[i + 1], source, entry); } }, /** * Dispose stored images. */ dispose : function() { this.__data = {}; } } });