/**
* Copyright (C) 2005-2016 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* This module provides a simple mechanism for creating image elements on a page. Additionally, it mixes
* in the [_PublishOrLinkMixin]{@link module:alfresco/core/_PublishOrLinkMixin} class to provide support
* for clicking to navigate or to publish a topic.
*
* @module alfresco/html/Image
* @extends external:dijit/_WidgetBase
* @mixes external:dojo/_TemplatedMixin
* @mixes module:alfresco/core/_PublishOrLinkMixin
* @author Martin Doyle
* @since 1.0.41
*/
define(["alfresco/core/_PublishOrLinkMixin",
"alfresco/enums/urlTypes",
"alfresco/util/urlUtils",
"dijit/_TemplatedMixin",
"dijit/_WidgetBase",
"dojo/_base/declare",
"dojo/_base/lang",
"dojo/Deferred",
"dojo/dom-class",
"dojo/dom-style",
"dojo/text!./templates/Image.html"],
function(_PublishOrLinkMixin, urlTypes, urlUtils, _TemplatedMixin, _WidgetBase, declare, lang, Deferred, domClass, domStyle, template) {
return declare([_WidgetBase, _TemplatedMixin, _PublishOrLinkMixin], {
/**
* @instance
* @typedef {object} Dimensions
* @property {number} width The width property
* @property {number} height The height property
*/
/**
* An array of the CSS files to use with this widget.
*
* @instance
* @type {object[]}
* @default [{cssFile:"./css/Image.css"}]
*/
cssRequirements: [{
cssFile: "./css/Image.css"
}],
/**
* The HTML template to use for the widget.
*
* @instance
* @override
* @type {string}
*/
templateString: template,
/**
* <p>Whether to enable advanced image natural-size retrieval. If set to false then
* image natural-size retrieval is only available for modern browsers and for
* "foreground" images only (i.e. ones specified by src, not background-image
* property).</p>
*
* <p>By setting this to true, the natural-size of foreground images
* in IE8 will be enabled, and also the calculation of background images'
* natural sizes, meaning the widget will be auto-sized according to the size
* of the background-image, however this will introduce an asynchronous
* process that will cause the widget to mark itself as "ready" prior to the
* resizing being completed. This will not be discernable to an end-user, but
* could affect any programmatic inspection of the image.</p>
*
* @instance
* @type {boolean}
* @default
*/
advancedSizeRetrieval: false,
/**
* Alt text for the image. This will be encoded and checked against i18n property bundles.
*
* @instance
* @type {string}
* @default
*/
altText: null,
/**
* <p>An optional aspect ratio that will be used if only one of [width]{@link module:alfresco/html/Image#width}
* or [height]{@link module:alfresco/html/Image#height} has been specified. This is specified as a floating
* point number as a ratio to 1.</p>
*
* <p>Example from Wikipedia: "Two common videographic aspect ratios are 4:3 (1.33:1), the universal video
* format of the 20th century, and 16:9 (1.77:1), universal for high-definition television and European digital
* television". So, the value in these two examples would be 1.33 and 1.77 respectively.</p>
*
* @instance
* @type {number?}
* @default
*/
aspectRatio: null,
/**
* Any optional classes to be added to the wrapped image element. If a
* [src]{@link module:alfresco/html/Image#src} property is provided then
* this property will not be used.
*
* @instance
* @type {string|string[]}
* @default
*/
classes: null,
/**
* Short-hand property for setting both width and height simultaneously. This
* will override any individual width/height property setting.
*
* @instance
* @type {object}
* @property {number} [w] Width
* @property {number} [h] Height
* @default
*/
dimensions: null,
/**
* If we can get no other sizes, then rather than having a zero-by-zero image,
* make it square using this value (treated as pixels).
*
* @instance
* @type {number}
* @default
*/
fallbackImageDimensions: 50,
/**
* An optional CSS height to apply to the image node in pixels. If height
* is specified without width then the other will be calculated using the
* aspectRatio property, or the calculated naturalAspectRatio if not
* specified.
*
* @instance
* @type {number}
* @default
*/
height: 0,
/**
* Any optional style rules to be applied to the wrapped image element.
*
* @type {String}
* @default
*/
imgStyle: null,
/**
* When set to true, the root node (and container of the image node) will be
* set to be a block level element.
*
* @instance
* @type {boolean}
* @default
*/
isBlockElem: false,
/**
* <p>This will be set to the natural aspect-ratio of the displayed image. This
* is specified as a floating point number as a ratio to 1.</p>
*
* <p>Example from Wikipedia: "Two common videographic aspect ratios are 4:3 (1.33:1),
* the universal video format of the 20th century, and 16:9 (1.77:1), universal for
* high-definition television and European digital television". So, the value in
* these two examples would be 1.33 and 1.77 respectively.</p>
*
* @instance
* @type {number}
* @readonly
* @default
*/
naturalAspectRatio: 0,
/**
* This will be set to the natural height of the displayed image.
*
* @instance
* @type {number}
* @readonly
* @default
*/
naturalHeight: 0,
/**
* This will be set to the natural width of the displayed image.
*
* @instance
* @type {number}
* @readonly
* @default
*/
naturalWidth: 0,
/**
* The path to the default blank gif, using the module pathing system
*
* @instance
* @type {string}
* @default
*/
pathToBlankGif: "alfresco/css/images/blank.gif",
/**
* The URL of the image to use (this is used in conjunction with the
* [srcType]{@link module:alfresco/html/Image#srcType} property). If a src
* is provided, then the [classes]{@link module:alfresco/html/Image#classes}
* property will not be used.
*
* @instance
* @type {string}
* @default
*/
src: null,
/**
* The type of URL to use (see [urlTypes]{@link module:alfresco/enums/urlTypes}
* for possible values).
*
* @instance
* @type {string}
* @default {@link module:alfresco/enums/urlTypes#PAGE_RELATIVE}
*/
srcType: urlTypes.PAGE_RELATIVE,
/**
* We want to set a maximum time for waiting for the widget to be attached to
* the DOM before trying to setup the widget.
*
* @instance
* @type {number}
* @default
*/
timeForDomAttach: 5000,
/**
* An optional CSS width to apply to the image node in pixels. If width is
* specified without height then the other will be calculated using the
* aspectRatio property, or the calculated naturalAspectRatio if not specified.
*
* @instance
* @type {number}
* @default
*/
width: 0,
/**
* In order to ensure we don't try forever to setup this widget when it's not
* being attached to the DOM, note when we first retry the setup.
*
* @instance
* @type {number?}
*/
_firstAttemptedSetup: null,
/**
* This is run after the config has been mixed into this instance.
*
* @instance
* @override
*/
postMixInProperties: function alfresco_html_Image__postMixInProperties() {
this.inherited(arguments);
// If this is being made into a link, try and make sure we have some "title" text
if (this.targetUrl && !this.label) {
this.label = this.altText;
}
// It's possible to provide a dimensions object, so use this if available
if (this.dimensions) {
this.width = this.dimensions.w || null;
this.height = this.dimensions.h || null;
}
// Update the src according to the srcType
this.src = urlUtils.convertUrl(this.src, this.srcType);
},
/**
* This is run after the widget has been created, but before any sub-widgets
* have finished being created.
*
* @instance
* @override
*/
postCreate: function alfresco_html_Image__postCreate() {
this.inherited(arguments);
// Set the alt attribute value (the JS will make it "safe" automatically)
if (this.altText) {
this.imageNode.setAttribute("alt", this.message(this.altText));
}
// Add any image classes (but only if no src provided)
if (this.classes && !this.src) {
domClass.add(this.imageNode, this.classes);
}
// Add "block state"
if (this.isBlockElem) {
domClass.add(this.domNode, "alfresco-html-Image--block");
}
// Setup the image node (sizing, etc)
this.setupImageNode();
},
/**
* Prepare the image node
*
* @instance
*/
setupImageNode: function alfresco_html_Image__setupImageNode() {
// The image-node setup will only work if attached to the DOM, so try again later if necessary
var nextParent = this.domNode,
isAttached;
while((nextParent = nextParent.parentNode) && !isAttached) {
isAttached = (nextParent === document.body);
}
if(!isAttached) {
// We can't let this run forever, so break out if necessary
if(!this._firstAttemptedSetup) {
this._firstAttemptedSetup = Date.now();
}
if(Date.now() - this._firstAttemptedSetup < this.timeForDomAttach) {
setTimeout(lang.hitch(this, this.setupImageNode), 200);
return;
}
}
// No configured src, so set to blank.gif if a background image is present
if (!this.src) {
if (this._getBackgroundImageUrl()) {
this.imageNode.src = urlUtils.convertUrl(this.pathToBlankGif, urlTypes.REQUIRE_PATH);
} else {
this.imageNode.src = this.src;
}
}
// Resize the image node
if (this.advancedSizeRetrieval) {
domClass.add(this.domNode, "alfresco-html-Image--advanced-sizing");
this._updateNaturalDimensions(true).then(lang.hitch(this, this.resize));
} else {
this._updateNaturalDimensions();
this.resize();
}
},
/**
* <p>Get the CSS dimensions of the image node (will not differentiate between dimensions
* specified in style attribute and those in a stylesheet). Unavailable and non-numeric
* dimensions will be returned as 0.</p>
*
* <p><strong>NOTE:</strong> If no [src]{@link module:alfresco/html/Image#src} property
* has been set, then this will be treated as if no image is present, and so will return
* empty (zero) dimensions.</p>
*
* @instance
* @returns {module:alfresco/html/Image#Dimensions} A dimensions object
*/
getCssDimensions: function alfresco_html_Image__getCssDimensions() {
var imageStyle = this.imageNode.currentStyle || getComputedStyle(this.imageNode), // IE8 || Proper Browser
w = imageStyle.width,
h = imageStyle.height;
return {
w: (w && parseInt(w, 10)) || 0,
h: (h && parseInt(h, 10)) || 0
};
},
/**
* Get the natural size of the currently displayed image.
*
* @instance
* @returns {external:dojo/promise/Promise} A promise that will resolve to a [dimensions]{@link module:alfresco/html/Image#Dimensions} object
*/
getNaturalImageSize: function alfresco_html_Image__getNaturalImageSize() {
return this._updateNaturalDimensions().then(lang.hitch(this, function() {
return {
w: this.naturalWidth,
h: this.naturalHeight
};
}));
},
/**
* Resize the image node based on (in priority order) the configured width/height
* properties; the current CSS dimensions; or the image's natural width/height.
*
* @instance
* @param {boolean} [forceNatural] Force usage of the image's natural dimensions
*/
resize: function alfresco_html_Image__resize(forceNatural) {
/*jshint maxcomplexity:false*/
// Use configured width/height first
if ((this.width || this.height) && !forceNatural) {
// Fill in any missing values (defaults to square if no aspect ratios
// because could not determine natural dimensions)
var aspectRatio = this.aspectRatio || this.naturalAspectRatio || 1;
if (!this.height) {
this.height = this.width / aspectRatio;
} else if (!this.width) {
this.width = this.height * aspectRatio;
}
// Just style according to whichever dimension(s) we have
domStyle.set(this.imageNode, {
width: this.width + "px",
height: this.height + "px"
});
} else {
// Are there any CSS dimensions we should leave in-place
var cssDimensions = this.getCssDimensions(),
hasCssDimensions = cssDimensions.w || cssDimensions.h;
// Do the resize only if no CSS dimensions or it's a normal self-sizing image, or if forceNatural is true
if ((!hasCssDimensions && !this.src) || forceNatural) {
// Use the natural dimensions if we have them (must have both, as an
// image with zero width or zero height will not display at all)
var w = this.naturalWidth,
h = this.naturalHeight;
if (!w || !h) {
w = h = this.fallbackImageDimensions;
}
// Update the dimensions
domStyle.set(this.imageNode, {
width: w + "px",
height: h + "px"
});
}
}
},
/**
* This function assumes that the naturalWidth/naturalHeight properties have already been set,
* and calculates the resultant natural aspect-ratio, as a floating point ratio to 1.
*
* @instance
*/
_calculateNaturalAspectRatio: function alfresco_html_Image___calculateNaturalAspectRatio() {
try {
this.naturalAspectRatio = this.naturalWidth / this.naturalHeight;
} catch (e) {
this.naturalAspectRatio = 0;
this.alfLog("error", "Error calculating aspect ratio (naturalWidth=" + this.naturalWidth + ", naturalHeight=" + this.naturalHeight + ")");
}
},
/**
* Get the URL of the background image, which is assumed to be a single image,
* not surrounded by quotes (which would break in IE).
*
* @instance
* @returns {string} The URL of the background image, or null if not available
*/
_getBackgroundImageUrl: function alfresco_html_Image___getBackgroundImageUrl() {
// Setup variables
var url = null,
urlRegex = /url\((.+)\)/,
imageStyle = this.imageNode.currentStyle || getComputedStyle(this.imageNode), // IE8 || Proper Browser
backgroundImage = imageStyle.backgroundImage,
regexMatch;
// If we have a background image property, extract the URL
if (backgroundImage) {
regexMatch = urlRegex.exec(backgroundImage);
if (regexMatch) {
url = regexMatch[1];
}
}
// Pass back the URL
return url;
},
/**
* Get the natural size of the currently displayed image.
*
* @instance
* @returns {external:dojo/promise/Promise} A promise that will resolve once dimensions are retrieved
*/
_updateNaturalDimensions: function alfresco_html_Image___updateNaturalDimensions() {
// Setup deferred object
var useAsync = this.advancedSizeRetrieval,
dfd = useAsync ? new Deferred() : null;
// Will this be easy?
if (this.naturalWidth) {
// We already have the dimensions
useAsync && dfd.resolve();
} else if (this.src && this.imageNode.naturalWidth) {
// In proper browsers, we have access to naturalWidth/naturalHeight properties
this.naturalWidth = this.imageNode.naturalWidth;
this.naturalHeight = this.imageNode.naturalHeight;
useAsync && dfd.resolve();
} else if (useAsync) {
// Get the image src (and if we can't find one then there is no image)
var imageSrc = this.src || this._getBackgroundImageUrl();
if (imageSrc) {
// Create a new image and read the dimensions on-load
var newImg = new Image();
newImg.onload = lang.hitch(this, function() {
this.naturalWidth = newImg.width;
this.naturalHeight = newImg.height;
dfd.resolve();
});
newImg.onabort = newImg.onerror = lang.hitch(this, function() {
this.alfLog("info", "Unable to load image with src \"" + imageSrc + "\"");
dfd.resolve();
});
newImg.src = imageSrc; // NOTE: This must be done AFTER setting onload
// This prevents problems with cached images
if (newImg.complete || newImg.readyState === 4) {
newImg.onload();
}
} else {
// No image, no dimensions
this.naturalWidth = 0;
this.naturalHeight = 0;
dfd.resolve();
}
}
// Pass back the promise if this is an async function
if (useAsync) {
return dfd.promise.then(lang.hitch(this, function() {
this._calculateNaturalAspectRatio();
}));
} else {
this._calculateNaturalAspectRatio();
}
}
});
});