/**
* 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/>.
*/
/**
* <p>Renders a thumbnail rendition of a node. This widget was originally written to support the
* Alfresco Share Document Library but has been expanded to be the base module for all thumbnail
* widgets. It attempts to make it possible to render thumbnails renditions or action them
* even when all that is available is the nodeRef of the node.</p>
* <p>As well as providing basic actions for navigating a Document Library (e.g. clicking a folder
* requests to display the content of that folder and clicking on a document links to the details
* page that renders the document) it is also possible to configure custom actions along with the
* ability to request a preview of the node be displayed. <b>PLEASE NOTE:</b> If previews are to
* be shown then the [NodePreviewService]{@link module:alfresco/services/NodePreviewService} should
* be included on the page.</p>
* <p>A thumbnail can also be configured to perform selection/de-selection action when clicked through
* the configuration of the [selectOnClick]{@link module:alfresco/renderers/Thumbnail#selectOnClick}
* and [onlySelectOnClick]{@link module:alfresco/renderers/Thumbnail#onlySelectOnClick} attributes.</p>
* <p>It is possible to configure thumbnails so that images are
* [cropped to fit]{@link module:alfresco/renderers/Thumbnail#cropToFit} or
* [stretched to fit]{@link module:alfresco/renderers/Thumbnail#stretchToFit} so that no white space
* is shown within the thumbnail. When images are not cropped or stretched the position of the image
* can be controlled by configuring the [horizontal]{@link module:alfresco/renderers/Thumbnail#horizontalAlignment}
* and [vertical]{@link module:alfresco/renderers/Thumbnail#verticallAlignment} to control where the
* whitespace around the image appears.</p>
*
* @example <caption>Example configuration for use in Document Library:</caption>
* {
* name: "alfresco/renderers/Thumbnail"
* }
*
* @example <caption>Example setting a fixed width for the imgpreview rendition:</caption>
* {
* name: "alfresco/renderers/Thumbnail",
* config: {
* renditionName: "imgpreview",
* width: "200px"
* }
* }
*
* @example <caption>Example requesting a preview when only nodeRef available:</caption>
* {
* name: "alfresco/renderers/Thumbnail",
* config: {
* assumeRendition: true,
* showDocumentPreview: true
* }
* }
*
* @example <caption>Example thumbnail that only performs selection/deselection actions when clicked:</caption>
* {
* name: "alfresco/renderers/Thumbnail",
* config: {
* selectOnClick: true,
* onlySelectOnClick: true
* }
* }
*
* @example <caption>Example configuring full dimensions with a cropped image:</caption>
* {
* name: "alfresco/renderers/Thumbnail",
* config: {
* dimensions: {
* w: "150px",
* h: "150px",
* margins: "5px"
* },
* cropToFit: true
* }
* }
*
* @module alfresco/renderers/Thumbnail
* @extends module:aikau/core/BaseWidget
* @mixes external:dijit/_OnDijitClickMixin
* @mixes module:alfresco/renderers/_JsNodeMixin
* @mixes module:alfresco/node/DraggableNodeMixin
* @mixes module:alfresco/renderers/_PublishPayloadMixin
* @mixes module:alfresco/node/NodeDropTargetMixin
* @mixes module:alfresco/node/ItemSelectionMixin
* @author Dave Draper
*/
define(["dojo/_base/declare",
"aikau/core/BaseWidget",
"alfresco/renderers/_JsNodeMixin",
"alfresco/node/DraggableNodeMixin",
"alfresco/node/NodeDropTargetMixin",
"alfresco/renderers/_PublishPayloadMixin",
"dijit/_OnDijitClickMixin",
"alfresco/lists/ItemSelectionMixin",
"alfresco/navigation/LinkClickMixin",
"alfresco/renderers/_ItemLinkMixin",
"service/constants/Default",
"alfresco/core/topics",
"dojo/_base/array",
"dojo/_base/lang",
"dojo/_base/event",
"dojo/dom-class",
"dojo/dom-style",
"alfresco/core/NodeUtils",
"dojo/window",
"dojo/Deferred",
"dojo/when"],
function(declare, BaseWidget, _JsNodeMixin, DraggableNodeMixin, NodeDropTargetMixin,
_PublishPayloadMixin, _OnDijitClickMixin, ItemSelectionMixin, LinkClickMixin, _ItemLinkMixin,
AlfConstants, topics, array, lang, event, domClass, domStyle, NodeUtils, win, Deferred, when) {
return declare([BaseWidget, _OnDijitClickMixin, _JsNodeMixin, DraggableNodeMixin, NodeDropTargetMixin,
_ItemLinkMixin, _PublishPayloadMixin, ItemSelectionMixin, LinkClickMixin], {
/**
* An array of the i18n files to use with this widget.
*
* @instance
* @type {object[]}
* @default [{i18nFile: "./i18n/Thumbnail.properties"}]
*/
i18nRequirements: [{i18nFile: "./i18n/Thumbnail.properties"}],
/**
* An array of the CSS files to use with this widget.
*
* @instance
* @type {object[]}
* @default [{cssFile:"./css/Thumbnail.css"}]
*/
cssRequirements: [{cssFile:"./css/Thumbnail.css"}],
/**
* Some APIs provide very little information other than the nodeRef, however if we really
* believe that the thumbnails are only going to be of something that has a rendition then
* we can just "go for it" (all bets are really off though as to what we get back though
* so this should only set to true when you're confident that a valid thumbnail rendition
* will be available.
*
* @instance
* @type {boolean}
* @default
*/
assumeRendition: false,
/**
* If this is configured to be true then the image will be cropped within the thumbnail.
* It does mean that aspect ratio of the image will be maintained but that not all of
* the image will be visible.
*
* @instance
* @type {boolean}
* @default
* @since 1.0.40
*/
cropToFit: false,
/**
* Additional CSS classes to apply to the main DOM node defined in the template
*
* @instance
* @type {string}
* @default
*/
customClasses: "",
/**
* The Dimensions object that defines the dimension attributes of the thumbnail. This will override
* the default CSS styling.
*
* @typedef {Dimensions}
* @property {string} [w] The width of the thumbnail
* @property {string} [h] The height of the thumbnail
* @property {string} [margin] The padding around the image
*/
/**
* This should be set to an object containing the starting dimensions of the thumbnail as well as optional
* information on padding and whether the aspect ratio of the image should be maintained.
*
* @instance
* @type {Dimensions}
* @default
* @since 1.0.40
*/
dimensions: null,
/**
* Overrides the [mixed in default]{@link module:alfresco/documentlibrary/_AlfDndDocumentUploadMixin#dndUploadHighLightImage}
* to use the smaller image.
*
* @instance
* @type {string}
* @default
* @since 1.0.41
*/
dndUploadHighlightImage: "elipse-cross.png",
/**
* Overrides the [mixed in default]{@link module:alfresco/documentlibrary/_AlfDndDocumentUploadMixin#dndUploadText}
* to hide the upload message.
*
* @instance
* @type {string}
* @default
* @since 1.0.41
*/
dndUploadHighlightText: "",
/**
* The name of the folder image to use. Valid options are: "folder-32.png", "folder-48.png", "folder-64.png"
* and "folder-256.png".
*
* @instance
* @type {string}
* @default
*/
folderImage: "folder-64.png",
/**
* This is a mapping of aspects to folder images. It was added to support custom folder images for
* Smart Folders but can be reconfigured as necessary. If no configuration is provided then a default
* set of mappings will be assigned.
*
* @instance
* @type {object}
* @default
* @since 1.0.66
*/
folderImageAspectMappings: null,
/**
* This is a suffix to append to folder images matched according to the
* [folderImageAspectMappings]{@link module:alfresco/renderers/Thumbnails#folderImageAspectMappings}.
* Folder image sizes are typically "32", "48", "64" and "256". For backwards compatibility reasons, this
* will only be used when a folder is matched to an entry in the
* [folderImageAspectMappings]{@link module:alfresco/renderers/Thumbnails#folderImageAspectMappings}.
*
* @instance
* @type {string}
* @default
* @since 1.0.66
*/
folderImageSize: "64",
/**
* Indicates whether or not the thumbnail image should be given a shadow effect.
*
* @instance
* @type {boolean}
* @default
* @since 1.0.40
*/
hasShadow: false,
/**
* Indicates how the thumbnail image should be aligned horizontally, the options are "LEFT",
* "MIDDLE" and "RIGHT".
*
* @instance
* @type {string}
* @default
* @since 1.0.40
*/
horizontalAlignment: "MIDDLE",
/**
* The property to use for the image id.
*
* @instance
* @type {string}
* @default
*/
imageIdProperty: "jsNode.nodeRef.nodeRef",
/**
* This will be set to the height of the image node.
*
* @instance
* @type {number}
* @default
* @since 1.0.40
*/
imageNodeHeight: null,
/**
* This will be set to the width of the image node.
*
* @instance
* @type {number}
* @default
* @since 1.0.40
*/
imageNodeWidth: null,
/**
* The property to use for the image title.
*
* @instance
* @type {string}
* @default
*/
imageTitleProperty: "displayName",
/**
* Overrides the [mixed in default]{@link module:alfresco/lists/ItemSelectionMixin#itemKey} to
* set a value suitable for use with standard Alfresco Repository APIs (the default remains the
* same for backwards compatibility).
*
* @instance
* @type {string}
* @default
*/
itemKey: "node.nodeRef",
/**
* This property is used to determine whether or not a new version of the thumbnail needs
* to be generated or if the cached version can be used.
*
* @instance
* @type {string}
* @default
*/
lastThumbnailModificationProperty: "jsNode.properties.cm:lastThumbnailModification",
/**
* This will be set to the natural height of the displayed image.
*
* @instance
* @type {number}
* @default
* @readonly
* @since 1.0.40
*/
naturalImageHeight: null,
/**
* This will be set to the natural width of the displayed image.
*
* @instance
* @type {number}
* @default
* @readonly
* @since 1.0.40
*/
naturalImageWidth: null,
/**
* If this is configured to be true then this will ensure that click actions only perform a
* selection action. However, this also requires that the
* [selectOnClick]{@link module:alfresco/renderers/Thumbnail#selectOnClick} attribute also
* be configured to be true.
*
* @instance
* @type {boolean}
* @default
* @since 1.0.40
*/
onlySelectOnClick: false,
/**
* The type of rendition to use for the thumbnail.
*
* @instance
* @type {string}
* @default
*/
renditionName: "doclib",
/**
* Overrides the [mixed in default]{@link module:alfresco/lists/ItemSelectionMixin#selectOnClick} to
* disable selection on click by default.
*
* @instance
* @type {boolean}
* @default
* @since 1.0.40
*/
selectOnClick: false,
/**
* Indicates whether or not selection publication and subscriptions are made using the global scope.
* This is only used when either [selectOnClick]{@link module:alfresco/renderers/Thumbnail#selectOnClick}
* or [updateOnSelection]{@link module:alfresco/renderers/Thumbnail#updateOnSelection} are configured
* to be true.
*
* @instance
* @type {boolean}
* @default
* @since 1.0.40
*/
selectionPublishGlobal: false,
/**
* Indicates whether or not selection publication and subscriptions are made using the parent scope.
* This is only used when either [selectOnClick]{@link module:alfresco/renderers/Thumbnail#selectOnClick}
* or [updateOnSelection]{@link module:alfresco/renderers/Thumbnail#updateOnSelection} are configured
* to be true.
*
* @instance
* @type {boolean}
* @default
* @since 1.0.40
*/
selectionPublishToParent: false,
/**
* Indicates whether or not a preview of the node represented by the thumbnail should be
* displayed when it is clicked. If this is set to true and there is not enough information
* to determine whether or not the node can be previewed then a request will be published
* to retrieve that information.
*
* @instance
* @type {boolean}
* @default
*/
showDocumentPreview: false,
/**
* This indicates whether or not the aspect ratio of the thumbnail will be retained. This means that
* for images will be stretched to ensure that there is no white-space visible. However unlike
* [cropToFit]{@link module:alfresco/renderers/Thumbnail#cropToFit} the entire image will be visible
* although possible distorted. If no [dimensions]{@link module:alfresco/renderers/Thumbnail#dimensions}
* are provided then the aspect ratio will always be maintained and the image will not be stretched.
*
* @instance
* @type {boolean}
* @default
* @since 1.0.40
*/
stretchToFit: false,
/**
* This allows a tokenized template to be defined where the tokens will be populated from
* values in the "currentItem" attribute using the
* [processCurrentItemTokens function]{@link module:alfresco/core/ObjectProcessingMixin#processCurrentItemTokens}
* from the [ObjectProcessingMixin]{@link module:alfresco/core/ObjectProcessingMixin} module. Note that the
* thumbnail URL template is expected to be appended to the PROXY_URI for accessing an Alfresco Repository.
*
* @instance
* @type {string}
* @default
*/
thumbnailUrlTemplate: null,
/**
* Overrides the [mixed in default]{@link module:alfresco/lists/ItemSelectionMixin#updateOnSelection} to
* not set up the item selection listeners. If this is configured to be true then the thumbnail will be
* highlighted when the item it represents is selected.
*
* @instance
* @type {boolean}
* @default
* @since 1.0.40
*/
updateOnSelection: false,
/**
* It is recommended that [NodePreviewService]{@link module:alfresco/services/NodePreviewService} is used
* for displaying previews. For backwards compatibility reasons this is not the default configuration but
* it is expected to become the default in the next major release.
*
* @instance
* @type {boolean}
* @default
* @since 1.0.59
*/
usePreviewService: false,
/**
* Indicates how the thumbnail image should be aligned vertically, the options are "TOP",
* "MIDDLE" and "BOTTOM".
*
* @instance
* @type {string}
* @default
* @since 1.0.40
*/
verticalAlignment: "TOP",
/**
* The width to render the thumbnail. Units of measurement need to be provided, e.g.
* "100px" for 100 pixels. The default is null, and if left as this the thumbnail will
* be rendered at the original image size. This is shorthand configuration when not
* providing full [dimensions]{@link module:alfresco/renderers/Thumbnail#dimensions}.
*
* @instance
* @type {string}
* @default
*/
width: null,
/**
* Overrides [the inherited function]{@link module:aikau/core/BaseWidget#createWidgetDom}
* to construct the DOM for the widget using native browser capabilities.
*
* @instance
* @since 1.0.101
*/
createWidgetDom: function alfresco_renderers_Thumbnail__createWidgetDom() {
this.thumbnailNode = this.domNode = document.createElement("span");
this.domNode.classList.add("alfresco-renderers-Thumbnail");
domClass.add(this.domNode, this.customClasses);
this.frameNode = document.createElement("span");
this.frameNode.classList.add("alfresco-renderers-Thumbnail__frame");
this._attach(this.frameNode, "ondijitclick", lang.hitch(this, this.onLinkClick));
this.imgNode = document.createElement("img");
this.imgNode.classList.add("alfresco-renderers-Thumbnail__image");
this.imgNode.setAttribute("id", this.imgId);
this.imgNode.setAttribute("src", this.thumbnailUrl);
this.imgNode.setAttribute("alt", this.imgAltText);
this.imgNode.setAttribute("title", this.imgTitle);
this._attach(this.imgNode, "onload", lang.hitch(this, this.getNaturalImageSize));
this.frameNode.appendChild(this.imgNode);
this.domNode.appendChild(this.frameNode);
},
/**
* Overrides the [inherited function]{@link module:alfresco/lists/ItemSelectionMixin#getSelectionPublishGlobal}
* to return [selectionPublishGlobal]{@link module:alfresco/renderers/Thumbnail#selectionPublishGlobal} to
* avoid conflicts with scope configuration for other events (such as linking and showing previews).
*
* @instance
* @overridable
* @return {boolean} A boolean indicating whether or not to publish and subscribe to selection topics globally.
*/
getSelectionPublishGlobal: function alfresco_lists_Thumbnail__getSelectionPublishGlobal() {
return this.selectionPublishGlobal;
},
/**
* Overrides the [inherited function]{@link module:alfresco/lists/ItemSelectionMixin#getSelectionPublishToParent}
* to return [selectionPublishToParent]{@link module:alfresco/renderers/Thumbnail#selectionPublishToParent} to
* avoid conflicts with scope configuration for other events (such as linking and showing previews).
*
* @instance
* @overridable
* @return {boolean} A boolean indicating whether or not to publish and subscribe to selection topics to the parent scope.
*/
getSelectionPublishToParent: function alfresco_lists_Thumbnail__getSelectionPublishToParent() {
return this.selectionPublishToParent;
},
/**
* Set up the attributes to be used when rendering the template.
*
* @instance
*/
postMixInProperties: function alfresco_renderers_Thumbnail__postMixInProperties() {
// jshint maxcomplexity:false
this.imgId = "";
this.thumbnailUrl = "";
this.imgAltText = "";
this.imgTitle = "";
// See AKU-941 - support for smart folders
if (!this.folderImageAspectMappings)
{
this.folderImageAspectMappings = {
"smf:smartFolder": "alfresco/renderers/css/images/filetypes/smart-folder"
};
}
if (this.currentItem && this.thumbnailUrlTemplate)
{
// If we have an explicitly decared thumbnail URL template then use that initially, this
// is used by the avatar thumbnail for example...
this.thumbnailUrl = AlfConstants.PROXY_URI + this.processCurrentItemTokens(this.thumbnailUrlTemplate);
}
else if (this.currentItem && this.currentItem.jsNode)
{
var jsNode = this.currentItem.jsNode;
this.thumbnailUrl = this.generateThumbnailUrl();
var imageTitle = lang.getObject(this.imageTitleProperty, false, this.currentItem);
if (!imageTitle)
{
this.currentItem.displayName = jsNode.properties["cm:name"];
}
}
else if (this.currentItem && (lang.getObject(this.itemKey, false, this.currentItem) || lang.getObject("nodeRef", false, this.currentItem)))
{
var nodeRefData = lang.getObject(this.itemKey, false, this.currentItem);
if (!nodeRefData)
{
nodeRefData = lang.getObject("nodeRef", false, this.currentItem);
}
this.imageIdProperty = this.itemKey || "nodeRef";
// Fallback to just having a nodeRef available... this has been added to handle rendering of
// thumbnails in search results where full node information may not be available...
var nodeRef = NodeUtils.processNodeRef(nodeRefData);
if (this.currentItem.type === "folder")
{
this.thumbnailUrl = require.toUrl("alfresco/renderers/css/images/" + this.folderImage);
}
else if (this.currentItem.type === "document" && nodeRef.uri)
{
this.thumbnailUrl = this.generateRenditionSpecificThumbnailUrl(nodeRef.uri);
if (!this.thumbnailUrl)
{
this.thumbnailUrl = AlfConstants.PROXY_URI + "api/node/" + nodeRef.uri + "/content/thumbnails/" + this.renditionName + "/?c=queue&ph=true&lastModified=" + this.currentItem.modifiedOn;
}
}
else if (nodeRef && this.assumeRendition)
{
this.thumbnailUrl = AlfConstants.PROXY_URI + "api/node/" + nodeRef.uri + "/content/thumbnails/" + this.renditionName + "/?c=queue&ph=true";
}
else
{
this.thumbnailUrl = this.generateFallbackThumbnailUrl();
}
}
else
{
this.thumbnailUrl = this.generateFallbackThumbnailUrl();
}
// Ensure that image title attributes, etc are set
this.setImageTitle();
},
/**
* Sets the title to display over the thumbnail
*
* @instance
*/
setImageTitle: function alfresco_renderers_Thumbnail__setImageTitle() {
var title = lang.getObject(this.imageTitleProperty, false, this.currentItem);
if (title)
{
this.imgTitle = this.encodeHTML(title);
this.imgAltText = title ? title.substring(title.lastIndexOf(".")) : "";
}
var id = lang.getObject(this.imageIdProperty, false, this.currentItem);
if (id)
{
this.imgId = id;
}
},
/**
* If a thumbnail URL cannot be determined then fallback to a standard image.
*
* @instance
* @returns {string} The URL for the thumbnail.
*/
generateFallbackThumbnailUrl: function alfresco_renderers_Thumbnail__generateFallbackThumbnailUrl() {
return require.toUrl("alfresco/renderers") + "/css/images/filetypes/generic-file-48.png";
},
/**
* Returns a URL to the image to use when rendering a folder
*
* @instance
*/
getFolderImage: function alfresco_renderers_Thumbnail__getDefaultFolderImage() {
var url, jsNode = this.currentItem.jsNode;
if(jsNode && jsNode.aspects)
{
array.some(jsNode.aspects, function(aspect) {
var mappedImage = this.folderImageAspectMappings[aspect];
if(mappedImage)
{
var image = mappedImage + "-" + this.folderImageSize + ".png";
url = require.toUrl(image);
}
return !!mappedImage;
}, this);
}
if (!url)
{
url = require.toUrl("alfresco/renderers/css/images/" + this.folderImage);
}
return url;
},
/**
* Generates the URL to use as the source of the thumbnail.
*
* @instance
* @param renditionName
* @returns {string}
*/
generateThumbnailUrl: function alfresco_renderers_Thumbnail__generateThumbnailUrl() {
var url = null;
if (!this.renditionName)
{
this.renditionName = "doclib";
}
if (this.currentItem && this.currentItem.jsNode)
{
var jsNode = this.currentItem.jsNode;
if (jsNode.isContainer || (jsNode.isLink && jsNode.linkedNode.isContainer))
{
url = this.getFolderImage();
}
else
{
var nodeRef = jsNode.isLink ? jsNode.linkedNode.nodeRef : jsNode.nodeRef;
url = this.generateRenditionSpecificThumbnailUrl(nodeRef.uri);
}
}
if (!url)
{
url = AlfConstants.PROXY_URI + "api/node/" + nodeRef.uri + "/content/thumbnails/" + this.renditionName + "?c=queue&ph=true";
}
return url;
},
/**
* Attempts to retrieve a thumbnail URL for a specific rendition. It ensures that the rendition has been
* generated by inspecting that there is a timestamp for the [renditionName]{@link module:alfresco/renderers/Thumbnail#renditionName}
* in the [modification property]{@link module:alfresco/renderers/Thumbnail#lastThumbnailModificationProperty}
*
* @instance
* @param {string} nodeRefUri The URI compatible nodeRef value
* @returns {string} The URL of the thumbnail if available
*/
generateRenditionSpecificThumbnailUrl: function alfresco_renderers_Thumbnail__generateRenditionSpecificThumbnailUrl(nodeRefUri) {
var url = null;
var thumbnailModData = lang.getObject(this.lastThumbnailModificationProperty, false, this.currentItem);
if (thumbnailModData)
{
for (var i = 0; i < thumbnailModData.length; i++)
{
if (thumbnailModData[i].indexOf(this.renditionName) !== -1)
{
url = AlfConstants.PROXY_URI + "api/node/" + nodeRefUri + "/content/thumbnails/" + this.renditionName + "?c=queue&ph=true&lastModified=" + thumbnailModData[i];
break;
}
}
}
return url;
},
/**
*
* @instance
*/
postCreate: function alfresco_renderers_Thumbnail__postCreate() {
this.inherited(arguments);
this.createItemSelectionSubscriptions();
if (this.width)
{
domStyle.set(this.imgNode, "width", this.width);
}
if (this.hasUploadPermissions() === true)
{
this.addUploadDragAndDrop(this.frameNode);
this.addNodeDropTarget(this.frameNode);
this._currentNode = this.currentItem.node;
}
// If no full dimensions have been provided but a simple width has then
// just use that to derive the full thumbnail dimensions...
if (!this.dimensions && this.width)
{
this.dimensions = {
w: this.width
};
}
this.resize(this.dimensions);
// Apply some additional styling...
if (this.hasShadow && !lang.getObject("node.isContainer", false, this.currentItem))
{
domClass.add(this.domNode, "alfresco-renderers-Thumbnail--shadow");
}
if (this.horizontalAlignment === "LEFT")
{
domClass.add(this.domNode, "alfresco-renderers-Thumbnail--left");
}
if (this.horizontalAlignment === "RIGHT")
{
domClass.add(this.domNode, "alfresco-renderers-Thumbnail--right");
}
if (this.verticalAlignment === "MIDDLE")
{
domClass.add(this.domNode, "alfresco-renderers-Thumbnail--middle");
}
if (this.verticalAlignment === "BOTTOM")
{
domClass.add(this.domNode, "alfresco-renderers-Thumbnail--bottom");
}
},
/**
* This sizes the thumbnail based on the dimensions that have been provided. It is
* entirely possible to size the thumbnail with just a width (dimensions.w) however
* a height (dimensions.h) and margin (dimensions.margin) can also be provided.
*
* @instance
* @param {Dimensions} dimensions
* @since 1.0.40
*/
resize: function alfresco_renderers_Thumbnail__resize(dimensions) {
if (this.imgNode && dimensions && dimensions.w)
{
// Get the height and width for the outer thumbnail frame...
var thumbnailWidth = parseInt(dimensions.w, 10);
var thumbnailHeight = thumbnailWidth;
if (dimensions.h)
{
thumbnailHeight = parseInt(dimensions.h, 10);
}
// Get the margin and border (in order to calculate the appropriate
// inner image size)...
var margin = 0;
if (dimensions.margin)
{
margin = parseInt(dimensions.margin, 10);
}
// Calcuate the image dimensions...
var borderThickness = 0;
try
{
// See AKU-1164 - need to defensively code around this call...
borderThickness = parseInt(domStyle.get(this.imgNode, "borderWidth"), 10);
}
catch (e)
{
// No action required, leave as default...
}
this.imageNodeHeight = thumbnailHeight - ((margin * 2) + borderThickness);
this.imageNodeWidth = thumbnailWidth - ((margin * 2) + borderThickness);
// Update the thumbnail nodes...
domStyle.set(this.thumbnailNode, {
"width": thumbnailWidth + "px",
"height": thumbnailHeight + "px"
});
domStyle.set(this.frameNode, {
"width": thumbnailWidth + "px",
"height": thumbnailHeight + "px",
"lineHeight": thumbnailHeight + "px"
});
domStyle.set(this.imgNode, "margin", margin + "px");
if (this.cropToFit)
{
// If cropping to fit we require the natural image width and height to be available
// (which are set on image load)...
this.naturalImageWidth && this.naturalImageHeight && this.cropImage();
}
else if (this.stretchToFit)
{
// If stretching we use the width as the minimum height, this will stretch landscape
// images vertically...
domStyle.set(this.imgNode, {
"width": this.imageNodeWidth + "px",
"minHeight": this.imageNodeWidth + "px",
"maxHeight": "none",
"maxWidth": "none"
});
}
else
{
// Otherwise just allow the image to maintain its natural aspect ratio with white
// space showing...
domStyle.set(this.imgNode, {
"maxWidth": this.imageNodeWidth + "px",
"maxHeight": this.imageNodeHeight + "px"
});
}
}
},
/**
* This function is used to configure the thumbnail DOM model so that the image is cropped and
* centered within the thumbnail. It relies on
* [getNaturalImageSize]{@link module:alfresco/renderers/Thumbnail#getNaturalImageSize}
* having been called to establish the orientation of the image. Therefore it is important that
* this is only called when the natural image dimensions have been established.
*
* @instance
* @since 1.0.40
*/
cropImage: function alfresco_renderers_Thumbnail__cropImage() {
domStyle.set(this.frameNode, {
position: "relative",
overflow: "hidden",
textAlign: "left"
});
// When cropping the image we need to know whether we want to crop off the top and bottom (if the image
// is in portrait) or off the sides (if the image is in landscape)...
if (this.naturalImageHeight > this.naturalImageWidth)
{
// For portrait images...
// The offset is the half of the amount that that the image is taller than it is wide...
var vOffset = ((this.imageNodeWidth / (this.naturalImageWidth / this.naturalImageHeight)) - this.imageNodeWidth) / 2;
domStyle.set(this.imgNode, {
"width": this.imageNodeWidth + "px",
"position": "absolute",
"margin": "auto",
"maxHeight": "none",
"maxWidth": "none",
"top": "-" + vOffset + "px"
});
}
else
{
// For landscape images...
// The offset is half the amount that the image is wider than it is tall...
var hOffset = ((this.imageNodeHeight / (this.naturalImageHeight / this.naturalImageWidth)) - this.imageNodeHeight) / 2;
domStyle.set(this.imgNode, {
"height": this.imageNodeHeight + "px",
"position": "absolute",
"margin": "auto",
"maxHeight": "none",
"maxWidth": "none",
"left": "-" + hOffset + "px"
});
}
this.croppedToFit = true;
},
/**
* This function is called when the thumbnail image has been loaded and is used to store the
* natural height and width of the image. This allows us to work out whether or not the image
* is in portrait or landscape and allows [cropImage]{@link module:alfresco/renderers/Thumbnail#cropImage}
* to determine the position and size of the image.
*
* @instance
* @since 1.0.40
*/
getNaturalImageSize: function alfresco_renderers_Thumbnail__getNaturalImageSize() {
this.naturalImageHeight = this.imgNode.naturalHeight;
this.naturalImageWidth = this.imgNode.naturalWidth;
if (this.cropToFit === true && !this.croppedToFit)
{
this.alfLog("info", "Cropping on image load");
this.cropImage();
}
},
/**
* Handles the property being clicked. This stops the click event from propogating
* further through the DOM (to prevent any wrapping anchor elements from triggering
* browser navigation) and then publishes the configured topic and payload.
*
* @instance
* @param {object} evt The details of the click event
*/
onLinkClick: function alfresco_renderers_Thumbnail__onLinkClick(evt) {
event.stop(evt);
// Delegate to the ItemSelectionMixin - this will check if select on click is supported...
this.onSelectionClick();
// Check whether or not the thumbnail should ONLY support selection on click (because in
// some circumstances, e.g. film strip view carousel we might want to select AND perform
// an additinal action)...
if (!this.onlySelectOnClick)
{
// TODO: Need to use a nodeRef property attribute that can be configured
var nodeRef = lang.getObject("nodeRef", false, this.currentItem);
if (!nodeRef)
{
nodeRef = lang.getObject("node.nodeRef", false, this.currentItem);
}
// Check to see if the thumbnail is configured to display previews when clicked,
// this particular type of action could require an XHR request of the full node
// data so it needs to be handled in a specific way...
if (this.showDocumentPreview)
{
this.nodePromise = this.currentItem;
var type = lang.getObject("node.type", false, this.currentItem),
mimetype = lang.getObject("node.mimetype", false, this.currentItem);
if (this.usePreviewService)
{
if (mimetype)
{
// If we have a type and MIME type we can safely delegate to the NodePreviewService
// without needing to load the full metadata (we want to avoid requesting the full
// node metadata more than once)...
this.alfServicePublish(topics.SHOW_NODE_PREVIEW, this.currentItem);
}
else
{
// ...if we don't have a MIME type we can still safely delegate, but we should just
// pass the nodeRef and allow the NodePreviewService to get the full metadata
this.alfServicePublish(topics.SHOW_NODE_PREVIEW, {
nodeRef: lang.getObject(this.itemKey || "node.nodeRef", false, this.currentItem)
});
}
}
else
{
if (!type || !mimetype)
{
this.nodePromise = new Deferred();
this.onLoadNode(nodeRef);
}
when(this.nodePromise, lang.hitch(this, this.onNodePromiseResolved, nodeRef));
}
}
else
{
this.onNonPreviewAction(evt);
}
}
},
/**
* Handles non-preview related actions. Non-preview actions are encapsulated in their own function
* as requests for a preview might need to fallback to use them when an XHR request to obtain the
* full node data reveals that a preview cannot be supported.
*
* @instance
* @param {object} evt The click event
*/
onNonPreviewAction: function alfresco_renderers_Thumbnail__onNonPreviewAction(evt) {
if (!this.publishTopic)
{
// If no topic has been provided then set up a default one (presumed to be for use
// in a document library)...
this.publishPayload = {};
this.publishTopic = this.generateFileFolderLink(this.publishPayload);
}
else if (this.publishPayload)
{
// If a payload has been provided then use it...
this.publishPayload = this.getGeneratedPayload(true, null);
}
// ...then do it.
if (!this.publishTopic || lang.trim(this.publishTopic) === "")
{
this.alfLog("warn", "No publishTopic provided for PropertyLink", this);
}
else
{
var publishGlobal = this.publishGlobal || false;
var publishToParent = this.publishToParent || false;
this.processMiddleOrCtrlClick(evt, this.publishTopic, this.publishPayload);
this.alfPublish(this.publishTopic, this.publishPayload, publishGlobal, publishToParent);
}
},
/**
* This handles the resolution of the complete node data. This then inspects the node data to see
* whether or not it is possible to display a preview.
*
* @instance
* @param {string} nodeRef The nodeRef of the node to preview
* @param {object} nodeData The resolved node data.
* @deprecated Since 1.0.59 - Use [usePreviewService]{@link module:alfresco/renderers/Thumbnail#usePreviewService} instead
*/
onNodePromiseResolved: function alfresco_renderers_Thumbnail__onNodePromiseResolved(nodeRef, nodeData) {
// First of all, update the currentItem with the full node data that has been resolved...
this.currentItem = nodeData;
// Now check to see whether or not the preview can be shown...
var mimetype = lang.getObject("node.mimetype", false, this.currentItem);
if (mimetype)
{
this.onShowPreview(nodeRef, mimetype);
}
else
{
// Fallback to standard actions
this.onNonPreviewAction();
}
},
/**
* Makes a reqeust to load all the data for the node. This is required for preview actions when data
* is not available in the currentItem object.
*
* @instance
* @param {string} nodeRef The nodeRef to reqeuest the details for
* @deprecated Since 1.0.59 - Use [usePreviewService]{@link module:alfresco/renderers/Thumbnail#usePreviewService} instead
*/
onLoadNode: function alfresco_renderers_Thumbnail__onLoadNode(nodeRef) {
if (nodeRef)
{
// Generate a UUID for the response to the publication to ensure that only this widget
// handles to the XHR data...
var responseTopic = this.generateUuid();
var subscriptionHandle = this.alfSubscribe(responseTopic + "_SUCCESS", lang.hitch(this, this.onNodeLoaded), true);
this.alfPublish("ALF_RETRIEVE_SINGLE_DOCUMENT_REQUEST", {
subscriptionHandle: subscriptionHandle,
alfResponseTopic: responseTopic,
nodeRef: nodeRef,
rawData: true
}, true);
}
else
{
this.alfLog("warn", "No nodeRef supplied to use to retrieve all data.", this);
}
},
/**
* Handles the loading of the complete node data.
*
* @instance
* @param {object} payload
* @deprecated Since 1.0.59 - Use [usePreviewService]{@link module:alfresco/renderers/Thumbnail#usePreviewService} instead
*/
onNodeLoaded: function alfresco_renderers_Thumbnail__onNodeLoaded(payload) {
this.alfUnsubscribe(payload.requestConfig.subscriptionHandle);
if (lang.exists("response.item", payload))
{
if (this.nodePromise && typeof this.nodePromise.resolve === "function")
{
this.nodePromise.resolve(payload.response.item);
}
}
else
{
this.alfLog("warn", "Node data was provided but the 'response.item' attribute was not found", payload, this);
}
},
/**
* Handles requests to show a preview of the node represented by the Thumbnail. By default this will only
* show a lightbox image for image mimetypes and display a dialog containing a preview for all other mime types
*
* @instance
* @param {string} nodeRef The nodeRef of the node to preview
* @param {string} mimetype The mimetype of the node
* @deprecated Since 1.0.59 - Use [usePreviewService]{@link module:alfresco/renderers/Thumbnail#usePreviewService} instead
*/
onShowPreview: function alfresco_renderers_Thumbnail__onShowPreview(nodeRef, mimetype) {
// Since we're going to be publishing to services we need to publish globally...
this.publishGlobal = true;
if (mimetype && mimetype.indexOf("image/") === 0)
{
// get last modified for image preview if present in the metadata
var lastModified = lang.getObject(this.lastThumbnailModificationProperty, false, this.currentItem) || 1;
this.publishTopic = "ALF_DISPLAY_LIGHTBOX";
if (nodeRef)
{
this.publishPayload = {
src: AlfConstants.PROXY_URI + "api/node/" + nodeRef.replace(":/", "") +
"/content/thumbnails/imgpreview?c=force&lastModified=" + encodeURIComponent(lastModified),
title: this.imgTitle
};
}
else
{
this.alfLog("warn", "Could not find a nodeRef to process", this.currentItem, this);
}
}
else
{
// Because the content of the previewer will load asynchronously it's important that
// we set some dimensions for the dialog body, otherwise it will appear off-center
var vs = win.getBox();
this.publishTopic = "ALF_CREATE_DIALOG_REQUEST";
this.publishPayload = {
contentWidth: (vs.w*0.7) + "px",
contentHeight: (vs.h-250) + "px",
handleOverflow: false,
dialogTitle: this.imgTitle,
additionalCssClasses: "no-padding",
widgetsContent: [
{
name: "alfresco/documentlibrary/AlfDocument",
config: {
widgets: [
{
name: "alfresco/preview/AlfDocumentPreview",
config: {
heightMode: "DIALOG"
}
}
]
}
}
],
widgetsButtons: [
{
name: "alfresco/buttons/AlfButton",
config: {
label: this.message("thumbnail.preview.dialog.close"),
publishTopic: "NO_OP",
additionalCssClasses: "call-to-action"
}
}
],
publishOnShow: [
{
publishTopic: "ALF_RETRIEVE_SINGLE_DOCUMENT_REQUEST",
publishPayload: {
rawData: true,
nodeRef: nodeRef
}
}
]
};
}
this.alfPublish(this.publishTopic, this.publishPayload, this.publishGlobal, this.publishToParent);
}
});
});