const base = require("../base");
require("./visual");
const ripe = base.ripe;
/**
* @class
* @classdesc Class that reactively updates the image of an img element whenever
* the state of its owner changes.
*
* @param {Ripe} owner The Ripe instance to be shown.
* @param {Object} element The img element that should be updated.
* @param {Object} options An object with options to configure the image, such as:
* - 'showInitials' - A Boolean indicating if the owner's personalization should be shown (defaults to 'true¨).
* - 'initialsBuilder' - A function that receives the initials and engraving as Strings and the img element that
* will be used and returns a map with the initials and a profile list.
* - 'frame' - The Ripe instance frame to display (defaults to '0').
* - 'size' - The image size in pixels (defaults to '1000').
* - 'width' - The image width in pixels (defaults to 'null', meaning it will fallback to 'size').
* - 'height' - The image height in pixels (defaults to 'null', meaning it will fallback to 'size').
* - 'crop' - A Boolean indicating if it is to crop the image composition.
*/
ripe.Image = function(owner, element, options) {
this.type = this.type || "Image";
ripe.Visual.call(this, owner, element, options);
};
ripe.Image.prototype = ripe.build(ripe.Visual.prototype);
ripe.Image.prototype.constructor = ripe.Image;
/**
* The Image initializer, which is called (by the owner)
* whenever the Image is going to become active.
*
* Sets the various values for the Image taking into
* owner's default values.
*/
ripe.Image.prototype.init = function() {
ripe.Visual.prototype.init.call(this);
this.frame = this.options.frame || null;
this.format = this.options.format || null;
this.size = this.options.size || null;
this.width = this.options.width || null;
this.height = this.options.height || null;
this.usePixelRatio = this.options.usePixelRatio || false;
this.pixelRatio =
this.options.pixelRatio ||
(this.usePixelRatio ? (typeof window !== "undefined" && window.devicePixelRatio) || 2 : 1);
this.mutations = this.options.mutations || false;
this.rotation = this.options.rotation || null;
this.crop = this.options.crop || null;
this.flip = this.options.flip || null;
this.mirror = this.options.mirror || null;
this.boundingBox = this.options.boundingBox || null;
this.algorithm = this.options.algorithm || null;
this.background = this.options.background || null;
this.engine = this.options.engine || null;
this.initialsX = this.options.initialsX || null;
this.initialsY = this.options.initialsY || null;
this.initialsWidth = this.options.initialsWidth || null;
this.initialsHeight = this.options.initialsHeight || null;
this.initialsViewport = this.options.initialsViewport || null;
this.initialsColor = this.options.initialsColor || null;
this.initialsOpacity = this.options.initialsOpacity || null;
this.initialsAlign = this.options.initialsAlign || null;
this.initialsVertical = this.options.initialsVertical || null;
this.initialsEmbossing = this.options.initialsEmbossing || null;
this.initialsRotation = this.options.initialsRotation || null;
this.initialsZindex = this.options.initialsZindex || null;
this.initialsAlgorithm = this.options.initialsAlgorithm || null;
this.initialsBlendColor = this.options.initialsBlendColor || null;
this.initialsPattern = this.options.initialsPattern || null;
this.initialsTexture = this.options.initialsTexture || null;
this.initialsExclusion = this.options.initialsExclusion || null;
this.initialsInclusion = this.options.initialsInclusion || null;
this.initialsImageRotation = this.options.initialsImageRotation || null;
this.initialsImageFlip = this.options.initialsImageFlip || null;
this.initialsImageMirror = this.options.initialsImageMirror || null;
this.debug = this.options.debug || null;
this.fontFamily = this.options.fontFamily || null;
this.fontWeight = this.options.fontWeight || null;
this.fontSize = this.options.fontSize || null;
this.fontSpacing = this.options.fontSpacing || null;
this.fontTrim = this.options.fontTrim || null;
this.fontMask = this.options.fontMask || null;
this.fontMode = this.options.fontMode || null;
this.lineHeight = this.options.lineHeight || null;
this.lineBreaking = this.options.lineBreaking || null;
this.shadow = this.options.shadow || null;
this.shadowColor = this.options.shadowColor || null;
this.shadowOffset = this.options.shadowOffset || null;
this.offsets = this.options.offsets || null;
this.curve = this.options.curve || null;
this.showInitials = this.options.showInitials || false;
this.initialsGroup = this.options.initialsGroup || null;
this.initialsContext = this.options.initialsContext || null;
this.getInitialsContext = this.options.getInitialsContext || null;
this.initialsProfiles = this.options.initialsProfiles || null;
this.initialsBuilder = this.options.initialsBuilder || this.owner.initialsBuilder;
this.doubleBuffering =
this.options.doubleBuffering === undefined ? true : this.options.doubleBuffering;
this.noAwaitLayout = this.options.noAwaitLayout || this.owner.noAwaitLayout || false;
this.composeLogic = this.options.composeLogic || this.owner.composeLogic || false;
this.imageUrlProvider =
this.options.imageUrlProvider === undefined
? (...args) => this.owner._getImageURL(...args)
: this.options.imageUrlProvider;
this.frameValidator =
this.options.frameValidator === undefined
? (...args) => this.owner.hasFrame(...args)
: this.options.frameValidator;
this.composeOptions = this.options.composeOptions || null;
this._observer = null;
this._url = null;
this._previousUrl = null;
this._registerHandlers();
};
/**
* The Image deinitializer, to be called (by the owner) when
* it should stop responding to updates so that any necessary
* cleanup operations can be executed.
*/
ripe.Image.prototype.deinit = async function() {
await this.cancel();
this._unregisterHandlers();
this._observer = null;
this.initialsBuilder = null;
ripe.Visual.prototype.deinit.call(this);
};
/**
* Updates the Image's current options with the ones provided.
*
* @param {Object} options Set of optional parameters to adjust the Image, such as:
* - 'format' - The format of the image, (eg: png, jpg, svg, etc.).
* - 'crop' - A Boolean indicating if the resulting image should be cropped.
* - 'initialsGroup' - The group in which the image initials belongs to.
* @param {Boolean} update If an update operation should be executed after
* the options updated operation has been performed.
*/
ripe.Image.prototype.updateOptions = async function(options, update = true) {
ripe.Visual.prototype.updateOptions.call(this, options);
this.frame = options.frame === undefined ? this.frame : options.frame;
this.format = options.format === undefined ? this.format : options.format;
this.size = options.size === undefined ? this.size : options.size;
this.width = options.width === undefined ? this.width : options.width;
this.height = options.height === undefined ? this.height : options.height;
this.pixelRatio = options.pixelRation === undefined ? this.pixelRatio : options.pixelRatio;
this.rotation = options.rotation === undefined ? this.rotation : options.rotation;
this.crop = options.crop === undefined ? this.crop : options.crop;
this.flip = options.flip === undefined ? this.flip : options.flip;
this.mirror = options.mirror === undefined ? this.mirror : options.mirror;
this.boundingBox = options.boundingBox === undefined ? this.boundingBox : options.boundingBox;
this.algorithm = options.algorithm === undefined ? this.algorithm : options.algorithm;
this.background = options.background === undefined ? this.background : options.background;
this.engine = options.engine === undefined ? this.engine : options.engine;
this.initialsX = options.initialsX === undefined ? this.initialsX : options.initialsX;
this.initialsY = options.initialsY === undefined ? this.initialsY : options.initialsY;
this.initialsWidth =
options.initialsWidth === undefined ? this.initialsWidth : options.initialsWidth;
this.initialsHeight =
options.initialsHeight === undefined ? this.initialsHeight : options.initialsHeight;
this.initialsViewport =
options.initialsViewport === undefined ? this.initialsViewport : options.initialsViewport;
this.initialsColor =
options.initialsColor === undefined ? this.initialsColor : options.initialsColor;
this.initialsOpacity =
options.initialsOpacity === undefined ? this.initialsOpacity : options.initialsOpacity;
this.initialsAlign =
options.initialsAlign === undefined ? this.initialsAlign : options.initialsAlign;
this.initialsVertical =
options.initialsVertical === undefined ? this.initialsVertical : options.initialsVertical;
this.initialsEmbossing =
options.initialsEmbossing === undefined
? this.initialsEmbossing
: options.initialsEmbossing;
this.initialsRotation =
options.initialsRotation === undefined ? this.initialsRotation : options.initialsRotation;
this.initialsZindex =
options.initialsZindex === undefined ? this.initialsZindex : options.initialsZindex;
this.initialsAlgorithm =
options.initialsAlgorithm === undefined
? this.initialsAlgorithm
: options.initialsAlgorithm;
this.initialsBlendColor =
options.initialsBlendColor === undefined
? this.initialsBlendColor
: options.initialsBlendColor;
this.initialsPattern =
options.initialsPattern === undefined ? this.initialsPattern : options.initialsPattern;
this.initialsTexture =
options.initialsTexture === undefined ? this.initialsTexture : options.initialsTexture;
this.initialsExclusion =
options.initialsExclusion === undefined
? this.initialsExclusion
: options.initialsExclusion;
this.initialsInclusion =
options.initialsInclusion === undefined
? this.initialsInclusion
: options.initialsInclusion;
this.initialsImageRotation =
options.initialsImageRotation === undefined
? this.initialsImageRotation
: options.initialsImageRotation;
this.initialsImageFlip =
options.initialsImageFlip === undefined
? this.initialsImageFlip
: options.initialsImageFlip;
this.initialsImageMirror =
options.initialsImageMirror === undefined
? this.initialsImageMirror
: options.initialsImageMirror;
this.debug = options.debug === undefined ? this.debug : options.debug;
this.fontFamily = options.fontFamily === undefined ? this.fontFamily : options.fontFamily;
this.fontWeight = options.fontWeight === undefined ? this.fontWeight : options.fontWeight;
this.fontSize = options.fontSize === undefined ? this.fontSize : options.fontSize;
this.fontSpacing = options.fontSpacing === undefined ? this.fontSpacing : options.fontSpacing;
this.fontTrim = options.fontTrim === undefined ? this.fontTrim : options.fontTrim;
this.fontMask = options.fontMask === undefined ? this.fontMask : options.fontMask;
this.fontMode = options.fontMode === undefined ? this.fontMode : options.fontMode;
this.lineHeight = options.lineHeight === undefined ? this.lineHeight : options.lineHeight;
this.lineBreaking =
options.lineBreaking === undefined ? this.lineBreaking : options.lineBreaking;
this.shadow = options.shadow === undefined ? this.shadow : options.shadow;
this.shadowColor = options.shadowColor === undefined ? this.shadowColor : options.shadowColor;
this.shadowOffset =
options.shadowOffset === undefined ? this.shadowOffset : options.shadowOffset;
this.offsets = options.offsets === undefined ? this.offsets : options.offsets;
this.curve = options.curve === undefined ? this.curve : options.curve;
this.initialsGroup =
options.initialsGroup === undefined ? this.initialsGroup : options.initialsGroup;
this.initialsContext =
options.initialsContext === undefined ? this.initialsContext : options.initialsContext;
this.getInitialsContext =
options.getInitialsContext === undefined
? this.getInitialsContext
: options.getInitialsContext;
this.initialsProfiles =
options.initialsProfiles === undefined ? this.initialsProfiles : options.initialsProfiles;
this.doubleBuffering =
options.doubleBuffering === undefined ? this.doubleBuffering : options.doubleBuffering;
this.imageUrlProvider =
options.imageUrlProvider === undefined ? this.imageUrlProvider : options.imageUrlProvider;
this.frameValidator =
options.frameValidator === undefined ? this.frameValidator : options.frameValidator;
this.composeOptions =
options.options === undefined ? this.composeOptions : options.composeOptions;
if (update) await this.update();
};
/**
* This function is called (by the owner) whenever its state changes
* so that the Image can update itself for the new state.
*
* @param {Object} state An object containing the new state of the owner.
* @param {Object} options Set of optional parameters to adjust the Image.
* @returns {Boolean} If an effective operation has been performed by the
* update operation.
*/
ripe.Image.prototype.update = async function(state, options = {}) {
// in case the element is no longer available (possible due to async
// nature of execution) returns the control flow immediately
if (!this.element) return;
const _update = async () => {
// in case the element is no longer available (possible due to async
// nature of execution) returns the control flow immediately
if (!this.element) return;
// gathers the complete set of data values from the element if existent
// defaulting to the instance one in case their are not defined
const frame = this.element.dataset.frame || this.frame;
const format = this.element.dataset.format || this.format;
const size = this.element.dataset.size || this.size;
const width = this.element.dataset.width || this.width;
const height = this.element.dataset.height || this.height;
const pixelRatio = this.element.dataset.pixelRatio || this.pixelRatio;
const rotation = this.element.dataset.rotation || this.rotation;
const crop = this.element.dataset.crop || this.crop;
const initialsGroup = this.element.dataset.initialsGroup || this.initialsGroup;
const initialsContext = this.element.dataset.initialsContext || this.initialsContext;
const getInitialsContext =
this.element.dataset.getInitialsContext || this.getInitialsContext;
const initialsProfiles = this.element.dataset.initialsProfiles || this.initialsProfiles;
const flip = this.element.dataset.flip || this.flip;
const mirror = this.element.dataset.mirror || this.mirror;
const boundingBox = this.element.dataset.boundingBox || this.boundingBox;
const algorithm = this.element.dataset.algorithm || this.algorithm;
const background = this.element.dataset.background || this.background;
const engine = this.element.dataset.engine || this.engine;
const initialsX = this.element.dataset.initialsX || this.initialsX;
const initialsY = this.element.dataset.initialsY || this.initialsY;
const initialsWidth = this.element.dataset.initialsWidth || this.initialsWidth;
const initialsHeight = this.element.dataset.initialsHeight || this.initialsHeight;
const initialsViewport = this.element.dataset.initialsViewport || this.initialsViewport;
const initialsColor = this.element.dataset.initialsColor || this.initialsColor;
const initialsOpacity = this.element.dataset.initialsOpacity || this.initialsOpacity;
const initialsAlign = this.element.dataset.initialsAlign || this.initialsAlign;
const initialsVertical = this.element.dataset.initialsVertical || this.initialsVertical;
const initialsEmbossing = this.element.dataset.initialsEmbossing || this.initialsEmbossing;
const initialsRotation = this.element.dataset.initialsRotation || this.initialsRotation;
const initialsZindex = this.element.dataset.initialsZindex || this.initialsZindex;
const initialsAlgorithm = this.element.dataset.initialsAlgorithm || this.initialsAlgorithm;
const initialsBlendColor =
this.element.dataset.initialsBlendColor || this.initialsBlendColor;
const initialsPattern = this.element.dataset.initialsPattern || this.initialsPattern;
const initialsTexture = this.element.dataset.initialsTexture || this.initialsTexture;
const initialsExclusion = this.element.dataset.initialsExclusion || this.initialsExclusion;
const initialsInclusion = this.element.dataset.initialsInclusion || this.initialsInclusion;
const initialsImageRotation =
this.element.dataset.initialsImageRotation || this.initialsImageRotation;
const initialsImageFlip = this.element.dataset.initialsImageFlip || this.initialsImageFlip;
const initialsImageMirror =
this.element.dataset.initialsImageMirror || this.initialsImageMirror;
const debug = this.element.dataset.debug || this.debug;
const fontFamily = this.element.dataset.fontFamily || this.fontFamily;
const fontWeight = this.element.dataset.fontWeight || this.fontWeight;
const fontSize = this.element.dataset.fontSize || this.fontSize;
const fontSpacing = this.element.dataset.fontSpacing || this.fontSpacing;
const fontTrim = this.element.dataset.fontTrim || this.fontTrim;
const fontMask = this.element.dataset.fontMask || this.fontMask;
const fontMode = this.element.dataset.fontMode || this.fontMode;
const lineHeight = this.element.dataset.lineHeight || this.lineHeight;
const lineBreaking = this.element.dataset.lineBreaking || this.lineBreaking;
const shadow = this.element.dataset.shadow || this.shadow;
const shadowColor = this.element.dataset.shadowColor || this.shadowColor;
const shadowOffset = this.element.dataset.shadowOffset || this.shadowOffset;
const offsets = this.element.dataset.offsets || this.offsets;
const curve = this.element.dataset.curve || this.curve;
const doubleBuffering = this.element.dataset.doubleBuffering || this.doubleBuffering;
const composeLogic = this.element.dataset.composeLogic || this.composeLogic || undefined;
const composeOptions = this.element.dataset.composeOptions || this.composeOptions;
// in case the state is defined tries to gather the appropriate
// sate options for both initials and engraving taking into
// consideration that groups may exist
if (state !== undefined) {
const base = initialsGroup ? state.initialsExtra[initialsGroup] || {} : state;
this.initials = base.initials || "";
this.engraving = base.engraving || null;
}
// in case the context changes with the group, gets the
// context and calls the initials builder
const context = getInitialsContext ? getInitialsContext(initialsGroup) : initialsContext;
const ctx = {
brand: this.owner.brand,
model: this.owner.model,
version: this.owner.version,
parts: this.owner.parts,
locale: this.owner.locale,
country: this.owner.country,
frame: this.frame
};
let initialsSpec = {};
// in case the compose (initials builder) logic has been requested then
// a simple initials spec is set with only the initials value present
// the remainder of the logic will be executed on the server-side
if (composeLogic) {
initialsSpec = { initials: this.initials };
}
// otherwise "prepares" the execution of the business logic allowing
// proper cancellation of the execution request if needed
else {
// encapsulates the initials builder around a promise that for
// promised (async) based function allows early cancellation using
// the associated cancel event, for non async function simply
// ignores the cancel event and executes the method normally,
// this strategy allows huge performance gains for quick image
// specification changes (eg: rapid initials typing)
const _initialsBuilderPromise = (...args) => {
return new Promise(resolve => {
const cancelBind = this.bind("cancel", () => {
this.unbind("cancel", cancelBind);
resolve();
});
const promise = this.initialsBuilder(...args);
if (promise && promise.then) {
promise.then(result => {
this.unbind("cancel", cancelBind);
resolve(result);
});
} else {
const result = promise;
this.unbind("cancel", cancelBind);
resolve(result);
}
});
};
initialsSpec = this.showInitials
? await _initialsBuilderPromise(
this.initials,
this.engraving,
initialsGroup,
initialsViewport,
context,
ctx
)
: {};
// in case the initials builder promise was cancelled
// early then return the control flow immediately
if (!initialsSpec) return;
// if there are message events in initials builder ctx, dispatches
// them to the proper message handler (to display message to end user)
if (initialsSpec.ctx && initialsSpec.ctx.messages && initialsSpec.ctx.messages.length) {
for (const [topic, content] of initialsSpec.ctx.messages) {
this.owner.trigger("message", topic, content);
}
}
}
// verifies if the model currently loaded in the Ripe Instance can
// render the frame to be display and if that's not the case "ignores"
// the current request for update
if (frame && !this.frameValidator(frame)) {
this.trigger("not_loaded");
return false;
}
// builds the URL of the image using the frame hacking approach
// this should provide us with the new values
const url = this.imageUrlProvider({
frame: frame,
name: frame,
format: format,
size: size ? parseInt(size * pixelRatio) : size,
width: width ? parseInt(width * pixelRatio) : width,
height: height ? parseInt(height * pixelRatio) : height,
rotation: rotation,
crop: crop,
initials: initialsSpec.initials,
profile: initialsProfiles
? [...initialsProfiles, ...(initialsSpec.profile || [])]
: initialsSpec.profile,
flip: flip,
mirror: mirror,
boundingBox: boundingBox,
algorithm: algorithm,
background: background,
engine: engine,
initialsX: initialsX,
initialsY: initialsY,
initialsWidth: initialsWidth,
initialsHeight: initialsHeight,
initialsViewport: initialsViewport,
initialsColor: initialsColor,
initialsOpacity: initialsOpacity,
initialsAlign: initialsAlign,
initialsVertical: initialsVertical,
initialsEmbossing: initialsEmbossing,
initialsRotation: initialsRotation,
initialsZindex: initialsZindex,
initialsAlgorithm: initialsAlgorithm,
initialsBlendColor: initialsBlendColor,
initialsPattern: initialsPattern,
initialsTexture: initialsTexture,
initialsExclusion: initialsExclusion,
initialsInclusion: initialsInclusion,
initialsImageRotation: initialsImageRotation,
initialsImageFlip: initialsImageFlip,
initialsImageMirror: initialsImageMirror,
debug: debug,
fontFamily: fontFamily,
fontWeight: fontWeight,
fontSize: fontSize,
fontSpacing: fontSpacing,
fontTrim: fontTrim,
fontMask: fontMask,
fontMode: fontMode,
lineHeight: lineHeight,
lineBreaking: lineBreaking,
shadow: shadow,
shadowColor: shadowColor,
shadowOffset: shadowOffset,
offsets: offsets,
logic: composeLogic,
curve: curve,
options: composeOptions
});
// verifies if the target image URL for the update is already
// set and if that's the case returns (end of loop)
if (url === this._url) {
this.trigger("not_loaded");
return false;
}
// saves the previous URL value and then updates the new URL
// according to the newly requested one
this._previousUrl = this._url;
this._url = url;
// in case the double buffering mode is active an off-screen
// image element is created to "cache" the image that is going
// to be displayed for the current update, this way the typical
// flickering visual artifact can be avoided
if (doubleBuffering && this._url) await this._doubleBuffer(this._url);
// in case the element is no longer available (possible due to async
// nature of execution) returns the control flow immediately
if (!this.element) return;
// updates the image DOM element with the values of the image
// including requested size and URL
if (width) this.element.width = width;
if (height) this.element.height = height;
this.element.src = this._url || "";
// saves the space for the result of the loaded callback that
// should be a boolean indicating if there's was a visual impact
// resulting from the loading operation
let result = true;
try {
// create a promise waiting for the current image for either load
// or receive an error, for both situation there should be a proper
// waiting process in motion
result = await new Promise((resolve, reject) => {
this._loadedCallback = resolve;
this._errorCallback = reject;
});
} finally {
// unsets both of the callbacks as they are no longer required by
// the promise's underlying logic
this._loadedCallback = null;
this._errorCallback = null;
}
// in case there's no value returned by the loaded callback then
// the result is considered valid (proper update)
if (result === undefined) result = true;
// returns a value indicating that if the loading operation
// as been triggered with success (effective operation)
return result;
};
// cancels any pending operation in the configurator and waits
// for the update promise to be finished (in case an update is
// currently running)
await this.cancel();
if (this._updatePromise && (!options.noAwaitLayout || !this.noAwaitLayout)) {
await this._updatePromise;
}
this._updatePromise = _update();
try {
const result = await this._updatePromise;
return result;
} finally {
this._updatePromise = null;
}
};
/**
* This function is called (by the owner) whenever the current operation
* in the child should be canceled this way an Image is not updated.
*
* @param {Object} options Set of optional parameters to adjust the Image.
* @returns {Boolean} If an effective operation has been performed or if
* instead no cancel logic was executed.
*/
ripe.Image.prototype.cancel = async function(options = {}) {
// notifies cancel event to listeners so that
// if possible they can resolve promises early
await this.trigger("cancel");
// in case the image is not under a loading then
// returns the control flow immediately as it's no longer
// possible to cancel it
if (!this._loadedCallback) return false;
// restores the internal URL state of the image back to
// the previous one (and updates the element accordingly)
if (this._previousUrl) this._url = this._previousUrl;
this._previousUrl = null;
this.element.src = this._url || "";
// calls the loaded callback with the indication that there
// was a cancel operation for its load
this._loadedCallback({ canceled: true });
// unsets both the loaded and the error callbacks so that
// they don't get called anymore in the future
this._loadedCallback = null;
this._errorCallback = null;
return true;
};
/**
* Resizes the Image's DOM element to `size` pixels, both the
* width and the height of the image will reflect this value.
*
* @param {String} size The number of pixels to resize to.
*/
ripe.Image.prototype.resize = async function(size, width, height) {
this.size = size;
this.width = width;
this.height = height;
await this.update();
};
/**
* Updates the frame that the Image is referring to.
*
* @param {String} frame The Ripe instance frame to display.
* @param {Object} options An object with options to configure
* the setting of the frame.
*/
ripe.Image.prototype.setFrame = async function(frame, options) {
this.frame = frame;
await this.update();
};
/**
* Updates the Image's `showInitials` flag that indicates
* if the initials should be display in the image.
*
* @param {String} showInitials If the image should display initials.
*/
ripe.Image.prototype.setShowInitials = async function(showInitials) {
this.showInitials = showInitials;
await this.update();
};
/**
* Updates the Image's `setComposeLogic` flag that indicates
* if the initials builder logic should only run on the server side.
*
* @param {String} composeLogic If the compose (initials builder) logic
* should be executed or not, in case this value is defined the local
* initials builder logic should not be executed.
*/
ripe.Image.prototype.setComposeLogic = async function(composeLogic) {
this.composeLogic = composeLogic;
await this.update();
};
/**
* Updates the Image's `noAwaitLayout` flag that indicates if the
* current update should not wait the completion of the previous one.
*
* @param {String} noAwaitLayout If the layout operations should wait
* for the complete execution of the previous ones.
*/
ripe.Image.prototype.setNoAwaitLayout = async function(noAwaitLayout) {
this.noAwaitLayout = noAwaitLayout;
await this.update();
};
/**
* Updates the Image's `initialsBuilder` function.
*
* @param {Function} builder The new `initialsBuilder` function
* to be used by the Image.
* @param {Object} options An object with options to configure
* the setting of the `initialsBuilder`.
*/
ripe.Image.prototype.setInitialsBuilder = async function(builder, options) {
this.initialsBuilder = builder;
await this.update();
};
/**
* @ignore
*/
ripe.Image.prototype._doubleBuffer = async function(url) {
if (typeof window === "undefined" || !window.document) return;
const image = document.createElement("img");
try {
await new Promise((resolve, reject) => {
image.addEventListener("load", resolve);
image.addEventListener("error", reject);
image.src = url;
});
} finally {
image.remove();
}
};
/**
* @ignore
*/
ripe.Image.prototype._registerHandlers = function() {
if (!this.element) return;
// creates and add both the load and the error listeners
// for the underlying image element to propagate those events
// into the current observable context (event normalization)
this.loadListener = () => this.trigger("loaded");
this.errorListener = () => this.trigger("error");
this.element.addEventListener("load", this.loadListener);
this.element.addEventListener("error", this.errorListener);
// registers for both the loaded and error handlers to cast
// the handlers to the "simpler" callback attributes
this.loadedHandler = this.bind("loaded", () => {
if (this._loadedCallback) this._loadedCallback();
});
this.errorHandler = this.bind("error", () => {
if (this._errorCallback) this._errorCallback();
});
// verifies if mutation should be "observed" for this visual
// and in such case registers for the observation of any DOM
// mutation (eg: attributes) for the image element, triggering
// a new update operation in case that happens
if (this.mutations) {
const Observer =
(typeof MutationObserver !== "undefined" && MutationObserver) ||
(typeof WebKitMutationObserver !== "undefined" && WebKitMutationObserver) || // eslint-disable-line no-undef
null;
this._observer = Observer
? new Observer(mutations => {
this.update();
})
: null;
if (this._observer) {
this._observer.observe(this.element, {
attributes: true,
subtree: false
});
}
}
};
/**
* @ignore
*/
ripe.Image.prototype._unregisterHandlers = function() {
if (this.loadListener && this.element) {
this.element.removeEventListener("load", this.loadListener);
}
if (this.errorListener && this.element) {
this.element.removeEventListener("error", this.errorListener);
}
if (this.loadedHandler) this.unbind("loaded", this.loadedHandler);
if (this.errorHandler) this.unbind("error", this.errorHandler);
if (this._observer) this._observer.disconnect();
};