Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC] TS migration patterns #1379

Closed
wants to merge 22 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
0fd4a33
LPS-X Enables TypeScript in frontend-js-web module
jbalsas Aug 18, 2021
c603114
LPS-X Adds an initial global Liferay interface
jbalsas Aug 19, 2021
70b78e0
LPS-X Converts autosize and html_util to TS
jbalsas Aug 19, 2021
7a98e2d
LPS-X Converts some trivial modules to TS
jbalsas Aug 19, 2021
0d69c50
LPS-X Converts utility navigate to TS
jbalsas Aug 19, 2021
2c1f090
LPS-X Converts another batch of semi-trivial conversions to TS
jbalsas Aug 19, 2021
2c3f8f8
LPS-X Converts create_X_url utilities to TS
jbalsas Aug 20, 2021
4b22739
LPS-X Refactors logic to help TS infer the proper type for `portletID`
jbalsas Aug 23, 2021
be8ced0
LPS-X Replaces {[key: string]: string} with Record<string, string>
jbalsas Aug 24, 2021
fb25f07
LPS-X Ports fetch utility to TS
jbalsas Aug 24, 2021
6ea38eb
LPS-X Migrates align utility to TS
jbalsas Aug 24, 2021
eda40b9
LPS-X Removes CompatibilityEventProxy
jbalsas Aug 25, 2021
0a7714e
LPS-X Updates form utilities to TS
jbalsas Aug 25, 2021
5f872ac
LPS-X Converts Address utilities to TS
jbalsas Aug 25, 2021
17e37e7
LPS-X Provides a global env.d.ts file for ambient types
jbalsas Sep 7, 2021
23ce0ab
LPS-X Converts EventEmitter utilities to TS
jbalsas Oct 15, 2021
b4136f9
LPS-X Converts run_scripts_in_element utility to TS
jbalsas Oct 15, 2021
be97819
LPS-X Modularizes Liferay global interface for better readability
jbalsas Oct 15, 2021
29a4640
LPS-X Converts module frontend-js-spa-web to TS
jbalsas Oct 15, 2021
c2201e3
LPS-X Converts toggle_disabled utility to TS
jbalsas Oct 15, 2021
bd30aa4
LPS-X Converts focus_form_field, in_browser_view, get_element and get…
jbalsas Oct 18, 2021
092dd31
LPS-X WIP convert module frontend-js-spa-web to TS
jbalsas Oct 20, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ if (portletTitleBasedNavigation) {
PortalUtil.addPortletBreadcrumbEntry(request, LanguageUtil.get(request, "add-multiple-file-entries"), currentURL);
%>

<aui:script require="frontend-js-web/liferay/util/run_scripts_in_element.es as runScriptsInElement">
<aui:script require="frontend-js-web/liferay/util/run_scripts_in_element as runScriptsInElement">
AUI().use('aui-base', 'aui-loading-mask-deprecated', 'node-load', (A) => {
Liferay.on('tempFileRemoved', () => {
Liferay.Util.openToast({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ else {

</c:if>

<aui:script position="inline" require="frontend-js-web/liferay/delegate/delegate.es as delegateModule,frontend-js-web/liferay/util/run_scripts_in_element.es as runScriptsInElement">
<aui:script position="inline" require="frontend-js-web/liferay/delegate/delegate.es as delegateModule,frontend-js-web/liferay/util/run_scripts_in_element as runScriptsInElement">
var documentTypeMenuList = document.querySelector(
'#<portlet:namespace />documentTypeSelector .lfr-menu-list'
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ String redirect = ParamUtil.getString(request, "redirect");
</aui:script>
</aui:form>

<aui:script require="frontend-js-web/liferay/util/run_scripts_in_element.es as runScriptsInElement">
<aui:script require="frontend-js-web/liferay/util/run_scripts_in_element as runScriptsInElement">
var continueButton = document.getElementById(
'<portlet:namespace />continueButton'
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

<%@ include file="/init.jsp" %>

<aui:script require="frontend-js-web/liferay/util/run_scripts_in_element.es as runScriptsInElement">
<aui:script require="frontend-js-web/liferay/util/run_scripts_in_element as runScriptsInElement">
function handleIframeMessage(event) {
if (event.data) {
var virtualDocument = document.createElement('html');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,103 +32,110 @@ import {
const NavigationStrategy = {
IMMEDIATE: 'immediate',
SCHEDULE_LAST: 'scheduleLast',
};
} as const;

type Nullable<T> = T | null;

interface NavigationEvent {
href: string;
isScheduledNavigation: boolean;
}

interface AppConfig {
navigationExceptionSelectors?: string;
}

class App extends EventEmitter {
activeScreen: Nullable<Screen>;
activePath: Nullable<string>;
allowPreventNavigate: boolean;
basePath: string;
browserPathBeforeNavigate: string;
captureScrollPositionFromScrollEvent: boolean;
defaultTitle: string;
formSelector: string;
ignoreQueryStringFromRoutePath: boolean;
linkSelector: string;
loadingCssClass: string;
nativeScrollRestorationSupported: boolean;
navigationStrategy: typeof NavigationStrategy[keyof typeof NavigationStrategy];
isNavigationPending: boolean;
pendingNavigate: Nullable<Promise<void>>;
popstateScrollLeft: number;
popstateScrollTop: number;
redirectPath: Nullable<String>;
routes: Route[];
scheduledNavigationQueue: NavigationEvent[];
screens: Record<string, Screen>;
skipLoadPopstate: boolean;
surfaces: Record<string, Surface>;
updateScrollPosition: boolean;
appEventHandlers_: EventHandler;
formEventHandler_: any;
linkEventHandler_: any;

/**
* App class that handle routes and screens lifecycle.
*/
constructor(config) {
constructor({navigationExceptionSelectors}: AppConfig = {}) {
super();

/**
* Holds the active screen.
* @type {?Screen}
* @protected
*/
this.activeScreen = null;

/**
* Holds the active path containing the query parameters.
* @type {?string}
* @protected
*/
this.activePath = null;

/**
* Allows prevent navigate from dom prevented event.
* @type {boolean}
* @default true
* @protected
*/
this.allowPreventNavigate = true;

/**
* Holds link base path.
* @type {!string}
* @default ''
* @protected
*/
this.basePath = '';

/**
* Holds the value of the browser path before a navigation is performed.
* @type {!string}
* @default the current browser path.
* @protected
*/
this.browserPathBeforeNavigate = getCurrentBrowserPathWithoutHash();

/**
* Captures scroll position from scroll event.
* @type {!boolean}
* @default true
* @protected
*/
this.captureScrollPositionFromScrollEvent = true;

/**
* Holds the default page title.
* @type {string}
* @default null
* @protected
*/
this.defaultTitle = document.title;

/**
* Holds the form selector to define forms that are routed.
* @type {!string}
* @default form[enctype="multipart/form-data"]:not([data-senna-off])
* @protected
*/
this.formSelector = config?.navigationExceptionSelectors
? `form${config.navigationExceptionSelectors}`
this.formSelector = navigationExceptionSelectors
? `form${navigationExceptionSelectors}`
: 'form[enctype="multipart/form-data"]:not([data-senna-off])';

/**
* When enabled, the route matching ignores query string from the path.
* @type {boolean}
* @default false
* @protected
*/
this.ignoreQueryStringFromRoutePath = false;

/**
* Holds the link selector to define links that are routed.
* @type {!string}
* @default a:not([data-senna-off])
* @protected
*/
this.linkSelector = config?.navigationExceptionSelectors
? `a${config.navigationExceptionSelectors}`
this.linkSelector = navigationExceptionSelectors
? `a${navigationExceptionSelectors}`
: 'a:not([data-senna-off]):not([target="_blank"])';

/**
* Holds the loading css class.
* @type {!string}
* @default senna-loading
* @protected
*/
this.loadingCssClass = 'senna-loading';

Expand All @@ -142,8 +149,6 @@ class App extends EventEmitter {
* Ultimately this leads to an horrible user experience. The good news is,
* however, that there's a potential fix: history.scrollRestoration.
* https://developers.google.com/web/updates/2015/09/history-api-scroll-restoration
* @type {boolean}
* @protected
*/
this.nativeScrollRestorationSupported =
'scrollRestoration' in window.history;
Expand All @@ -153,103 +158,68 @@ class App extends EventEmitter {
* cannot be Cancelled to start another and will be queued in
* scheduledNavigationQueue. When NavigationStrategy.IMMEDIATE means that all
* navigation will be cancelled to start another.
* @type {!string}
* @default immediate
* @protected
*/
this.navigationStrategy = NavigationStrategy.IMMEDIATE;

/**
* When set to true there is a pendingNavigate that has not yet been
* resolved or rejected.
* @type {boolean}
* @default false
* @protected
*/
this.isNavigationPending = false;

/**
* Holds a deferred with the current navigation.
* @type {?Promise}
* @default null
* @protected
*/
this.pendingNavigate = null;

/**
* Holds the window horizontal scroll position when the navigation using
* back or forward happens to be restored after the surfaces are updated.
* @type {!Number}
* @default 0
* @protected
*/
this.popstateScrollLeft = 0;

/**
* Holds the window vertical scroll position when the navigation using
* back or forward happens to be restored after the surfaces are updated.
* @type {!Number}
* @default 0
* @protected
*/
this.popstateScrollTop = 0;

/**
* Holds the redirect path containing the query parameters.
* @type {?string}
* @protected
*/
this.redirectPath = null;

/**
* Holds the screen routes configuration.
* @type {?Array}
* @default []
* @protected
*/
this.routes = [];

/**
* Holds a queue that stores every DOM event that can initiate a navigation.
* @type {!Event}
* @default []
* @protected
*/
this.scheduledNavigationQueue = [];

/**
* Maps the screen instances by the url containing the parameters.
* @type {?Object}
* @default {}
* @protected
*/
this.screens = {};

/**
* When set to true the first erroneous popstate fired on page load will be
* ignored, only if <code>window.history.state</code> is also
* <code>null</code>.
* @type {boolean}
* @default false
* @protected
*/
this.skipLoadPopstate = false;

/**
* Maps that index the surfaces instances by the surface id.
* @type {?Object}
* @default {}
* @protected
*/
this.surfaces = {};

/**
* When set to true, moves the scroll position after popstate, or to the
* top of the viewport for new navigation. If false, the browser will
* take care of scroll restoration.
* @type {!boolean}
* @default true
* @protected
*/
this.updateScrollPosition = true;

Expand Down Expand Up @@ -280,7 +250,7 @@ class App extends EventEmitter {
this.maybeOverloadBeforeUnload_();
}

addDOMEventListener(element, eventName, callback) {
addDOMEventListener(element: EventTarget, eventName: string, callback: EventListenerOrEventListenerObject) {
element.addEventListener(eventName, callback);

return {
Expand Down Expand Up @@ -347,10 +317,8 @@ class App extends EventEmitter {

/**
* Returns if can navigate to path.
* @param {!string} url
* @return {boolean}
*/
canNavigate(url) {
canNavigate(url: string) {
try {
const uri = url.startsWith('/')
? new URL(url, window.location.origin)
Expand Down Expand Up @@ -385,17 +353,16 @@ class App extends EventEmitter {

/**
* Clear screens cache.
* @chainable
*/
clearScreensCache() {
Object.keys(this.screens).forEach((path) => {
if (path === this.activePath) {
this.activeScreen.clearCache();
this.activeScreen?.clearCache();
}
else if (
!(
this.isNavigationPending &&
this.pendingNavigate.path === path
this.pendingNavigate?.path === path
)
) {
this.removeScreen(path);
Expand All @@ -408,7 +375,7 @@ class App extends EventEmitter {
* @param {!string} path Path containing the querystring part.
* @return {Screen}
*/
createScreenInstance(path, route) {
createScreenInstance(path: string, route) {
if (!this.pendingNavigate && path === this.activePath) {
return this.activeScreen;
}
Expand Down Expand Up @@ -952,7 +919,7 @@ class App extends EventEmitter {
* @param {Event=} event Optional event object that triggered the navigation.
* @return {Promise} Returns a pending request cancellable promise.
*/
navigate(path, opt_replaceHistory, opt_event) {
navigate(path, opt_replaceHistory?, opt_event?) {
if (opt_event) {
Liferay.SPA.__capturedFormElement__ = opt_event.capturedFormElement;
Liferay.SPA.__capturedFormButtonElement__ =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ import App from './App';
const MAX_TIMEOUT = Math.pow(2, 31) - 1;
const PROPAGATED_PARAMS = ['bodyCssClass'];

export interface LiferayAppConfig {
cacheExpirationTime: number;
clearScreensCache: boolean;
debugEnabled: boolean;
navigationExceptionSelectors: string;
portletsBlacklist: string[];
requestTimeout: number;
userNotification: string;
validStatusCodes: number[];
}

/**
* LiferayApp
*
Expand All @@ -35,13 +46,7 @@ const PROPAGATED_PARAMS = ['bodyCssClass'];
* from the SPA lifecycle.</li>
* </ul>
*/

class LiferayApp extends App {

/**
* @inheritDoc
*/

constructor({
cacheExpirationTime,
clearScreensCache,
Expand All @@ -51,7 +56,7 @@ class LiferayApp extends App {
requestTimeout,
userNotification,
validStatusCodes,
}) {
}: AppConfig) {
super({navigationExceptionSelectors});

this._cacheExpirationTime = cacheExpirationTime;
Expand Down
Loading