META-INF.resources.surface.Surface.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of com.liferay.frontend.js.spa.web
Show all versions of com.liferay.frontend.js.spa.web
Liferay Frontend JS SPA Web
The newest version!
/**
* SPDX-FileCopyrightText: (c) 2000 Liferay, Inc. https://liferay.com
* SPDX-License-Identifier: LGPL-2.1-or-later OR LicenseRef-Liferay-DXP-EULA-2.0.0-2023-06
*/
import {Disposable, buildFragment} from 'frontend-js-web';
class Surface extends Disposable {
/**
* Surface class representing the references to elements on the page that
* can potentially be updated by App
.
* @param {string} id
*/
constructor(id) {
super();
if (!id) {
throw new Error(
'Surface element id not specified. A surface element requires a valid id.'
);
}
/**
* Holds the active child element.
* @type {Element}
* @default null
* @protected
*/
this.activeChild = null;
/**
* Holds the default child element.
* @type {Element}
* @default null
* @protected
*/
this.defaultChild = null;
/**
* Holds the element with the specified surface id, if not found creates a
* new element with the specified id.
* @type {Element}
* @default null
* @protected
*/
this.element = null;
/**
* Holds the surface id.
* @type {String}
* @default null
* @protected
*/
this.id = id;
/**
* Holds the default transitionFn for the surfaces.
* @param {?Element=} from The visible surface element.
* @param {?Element=} to The surface element to be flipped.
* @default null
*/
this.transitionFn = null;
this.defaultChild = this.getChild(Surface.DEFAULT);
this.maybeWrapContentAsDefault_();
this.activeChild = this.defaultChild;
}
/**
* Adds screen content to a surface. If content hasn't been passed, see if
* an element exists in the DOM that matches the id. By convention, the
* element should already be nested in the right element and should have an
* id that is a concatentation of the surface id + '-' + the screen id.
* @param {!string} screenId The screen id the content belongs too.
* @param {?string|Element=} opt_content The string content or element to
* add be added as surface content.
* @return {Element}
*/
addContent(screenId, opt_content) {
const fragment =
typeof opt_content === 'string'
? buildFragment(opt_content)
: opt_content;
Liferay.DOMTaskRunner.runTasks(fragment);
let child = this.defaultChild;
if (fragment) {
child = this.getChild(screenId);
if (child) {
while (child.firstChild) {
child.removeChild(child.firstChild);
}
}
else {
child = this.createChild(screenId);
this.transition(child, null);
}
child.appendChild(fragment);
}
const element = this.getElement();
if (element && child) {
element.appendChild(child);
}
return child;
}
/**
* Creates child node for the surface.
* @param {!string} screenId The screen id.
* @return {Element}
*/
createChild(screenId) {
const child = document.createElement('div');
child.setAttribute('id', this.makeId_(screenId));
return child;
}
/**
* Gets child node of the surface.
* @param {!string} screenId The screen id.
* @return {?Element}
*/
getChild(screenId) {
return document.getElementById(this.makeId_(screenId));
}
/**
* Gets the surface element from element, and sets it to the el property of
* the current instance.
* this.element
will be used.
* @return {?Element} The current surface element.
*/
getElement() {
if (this.element) {
return this.element;
}
this.element = document.getElementById(this.id);
return this.element;
}
/**
* Gets the surface id.
* @return {String}
*/
getId() {
return this.id;
}
/**
* Gets the surface transition function.
* See Surface.defaultTransition
.
* @return {?Function=} The transition function.
*/
getTransitionFn() {
return this.transitionFn;
}
/**
* Makes the id for the element that holds content for a screen.
* @param {!string} screenId The screen id the content belongs too.
* @return {String}
* @private
*/
makeId_(screenId) {
return this.id + '-' + screenId;
}
/**
* If default child is missing, wraps surface content as default child. If
* surface have static content, make sure to place a
* surfaceId-default
element inside surface, only contents
* inside the default child will be replaced by navigation.
*/
maybeWrapContentAsDefault_() {
const element = this.getElement();
if (element && !this.defaultChild) {
const childNodesToWrap = [];
element.childNodes.forEach((childNode) => {
/*
* Some child nodes must be kept out of the Senna surface.
*
* We don't have any good mechanism to do it and we don't want
* to put anything in place given all this is more or less
* legacy.
*
* Thus, we simply skip some nodes that are known to us and
* leave them as direct children of , outside the default
* Senna surface.
*
* See LPS-151462 for more information.
*/
if (childNode.classList) {
if (
childNode.classList.contains('yui3-dd-proxy') ||
childNode.classList.contains('yui3-dd-shim')
) {
return;
}
}
childNodesToWrap.push(childNode);
});
const fragment = document.createDocumentFragment();
childNodesToWrap.forEach((childNode) => {
fragment.appendChild(childNode);
});
this.defaultChild = this.addContent(Surface.DEFAULT, fragment);
this.transition(null, this.defaultChild);
}
}
/**
* Sets the surface id.
* @param {!string} id
*/
setId(id) {
this.id = id;
}
/**
* Sets the surface transition function.
* See Surface.defaultTransition
.
* @param {?Function=} transitionFn The transition function.
*/
setTransitionFn(transitionFn) {
this.transitionFn = transitionFn;
}
/**
* Shows screen content from a surface.
* @param {String} screenId The screen id to show.
* @return {Promise} Pauses the navigation until it is resolved.
*/
show(screenId) {
const from = this.activeChild;
let to = this.getChild(screenId);
if (!to) {
to = this.defaultChild;
}
this.activeChild = to;
return this.transition(from, to).finally(() => {
if (from && from !== to) {
from.remove();
}
});
}
/**
* Removes screen content from a surface.
* @param {!string} screenId The screen id to remove.
*/
remove(screenId) {
const child = this.getChild(screenId);
if (child) {
child.remove();
}
}
/**
* @return {String}
*/
toString() {
return this.id;
}
/**
* Invokes the transition function specified on transition
attribute.
* @param {?Element=} from
* @param {?Element=} to
* @return {?Promise=} This can return a promise, which will pause the
* navigation until it is resolved.
*/
transition(from, to) {
const transitionFn = this.transitionFn || Surface.defaultTransition;
return Promise.resolve(transitionFn.call(this, from, to));
}
}
/**
* Holds the default surface name. Elements on the page must contain a child
* element containing the default content, this element must be as following:
*
* Example:
*
*
* Default surface content.
*
*
*
* The default content is relevant for the initial page content. When a
* screen doesn't provide content for the surface the default content is
* restored into the page.
*
* @type {!String}
* @default default
* @static
*/
Surface.DEFAULT = 'default';
/**
* Holds the default transition for all surfaces. Each surface could have its
* own transition.
*
* Example:
*
*
* surface.setTransitionFn(function(from, to) {
* if (from) {
* from.style.display = 'none';
* from.classList.remove('flipped');
* }
* if (to) {
* to.style.display = 'block';
* to.classList.add('flipped');
* }
* return null;
* });
*
*
* @param {?Element=} from The visible surface element.
* @param {?Element=} to The surface element to be flipped.
* @static
*/
Surface.defaultTransition = function (from, to) {
if (from) {
from.style.display = 'none';
from.classList.remove('flipped');
}
if (to) {
to.style.display = 'block';
to.classList.add('flipped');
}
};
export default Surface;