From 0562331edc22514e2afa29aeffc768a544d130ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?= Date: Sat, 12 Mar 2022 11:55:24 +0100 Subject: [PATCH 1/3] Information about current route is sent to the client --- .../Framework/Resources/Scripts/dotvvm-base.ts | 15 ++++++++++++--- .../Framework/Resources/Scripts/dotvvm-root.ts | 4 +++- .../Serialization/DefaultViewModelSerializer.cs | 6 ++++++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/Framework/Framework/Resources/Scripts/dotvvm-base.ts b/src/Framework/Framework/Resources/Scripts/dotvvm-base.ts index bd02db317c..2e695befd6 100644 --- a/src/Framework/Framework/Resources/Scripts/dotvvm-base.ts +++ b/src/Framework/Framework/Resources/Scripts/dotvvm-base.ts @@ -16,6 +16,10 @@ type DotvvmCoreState = { _viewModelCacheId?: string _virtualDirectory: string _initialUrl: string, + _routeName: string, + _routeParameters: { + [name: string]: any + }, _stateManager: StateManager } @@ -60,7 +64,8 @@ export function clearViewModelCache() { delete getCoreState()._viewModelCache; } export function getCulture(): string { return getCoreState()._culture; } - +export function getRouteName(): string { return getCoreState()._routeName; } +export function getRouteParameters(): { [name: string]: any } { return getCoreState()._routeParameters; } export function getStateManager(): StateManager { return getCoreState()._stateManager } let initialViewModelWrapper: any; @@ -85,7 +90,9 @@ export function initCore(culture: string): void { _culture: culture, _initialUrl: thisViewModel.url, _virtualDirectory: thisViewModel.virtualDirectory!, - _stateManager: manager + _stateManager: manager, + _routeName: thisViewModel.routeName, + _routeParameters: thisViewModel.routeParameters } // store cached viewmodel @@ -106,7 +113,9 @@ export function initCore(culture: string): void { _culture: currentCoreState!._culture, _initialUrl: a.serverResponseObject.url, _virtualDirectory: a.serverResponseObject.virtualDirectory!, - _stateManager: currentCoreState!._stateManager + _stateManager: currentCoreState!._stateManager, + _routeName: a.serverResponseObject.routeName, + _routeParameters: a.serverResponseObject.routeParameters } }); } diff --git a/src/Framework/Framework/Resources/Scripts/dotvvm-root.ts b/src/Framework/Framework/Resources/Scripts/dotvvm-root.ts index 1064edfdef..d58c262bb4 100644 --- a/src/Framework/Framework/Resources/Scripts/dotvvm-root.ts +++ b/src/Framework/Framework/Resources/Scripts/dotvvm-root.ts @@ -1,4 +1,4 @@ -import { initCore, getViewModel, getViewModelObservable, initBindings, getCulture, getState, getStateManager } from "./dotvvm-base" +import { initCore, getViewModel, getViewModelObservable, initBindings, getCulture, getState, getStateManager, getRouteName, getRouteParameters } from "./dotvvm-base" import * as events from './events' import * as spa from "./spa/spa" import * as validation from './validation/validation' @@ -86,6 +86,8 @@ const dotvvmExports = { get viewModel() { return getViewModel() } } }, + get routeName() { return getRouteName() }, + get routeParameters() { return getRouteParameters() }, get state() { return getState() }, patchState(a: any) { getStateManager().patchState(a) diff --git a/src/Framework/Framework/ViewModel/Serialization/DefaultViewModelSerializer.cs b/src/Framework/Framework/ViewModel/Serialization/DefaultViewModelSerializer.cs index ce78606f16..5e74056ea1 100644 --- a/src/Framework/Framework/ViewModel/Serialization/DefaultViewModelSerializer.cs +++ b/src/Framework/Framework/ViewModel/Serialization/DefaultViewModelSerializer.cs @@ -149,6 +149,12 @@ public void BuildViewModel(IDotvvmRequestContext context, object? commandResult) result["renderedResources"] = JArray.FromObject(context.ResourceManager.GetNamedResourcesInOrder().Select(r => r.Name)); } + if (context.Route != null) + { + result["routeName"] = context.Route.RouteName; + result["routeParameters"] = new JObject(context.Parameters.Select(p => new JProperty(p.Key, p.Value)).ToArray()); + } + // TODO: do not send on postbacks if (validationRules?.Count > 0) result["validationRules"] = validationRules; From a95282dd40cbc3e36661ffd057546c6ef78b5f50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?= Date: Sat, 12 Mar 2022 11:58:49 +0100 Subject: [PATCH 2/3] Call NamedCommand JS function added --- .../Resources/Scripts/dotvvm-root.ts | 1 + .../Scripts/viewModules/viewModuleManager.ts | 34 ++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/Framework/Framework/Resources/Scripts/dotvvm-root.ts b/src/Framework/Framework/Resources/Scripts/dotvvm-root.ts index 1064edfdef..952762ea1b 100644 --- a/src/Framework/Framework/Resources/Scripts/dotvvm-root.ts +++ b/src/Framework/Framework/Resources/Scripts/dotvvm-root.ts @@ -109,6 +109,7 @@ const dotvvmExports = { registerOne: viewModuleManager.registerViewModule, init: viewModuleManager.initViewModule, call: viewModuleManager.callViewModuleCommand, + callNamedCommand: viewModuleManager.callNamedCommand, registerMany: viewModuleManager.registerViewModules }, resourceLoader: { diff --git a/src/Framework/Framework/Resources/Scripts/viewModules/viewModuleManager.ts b/src/Framework/Framework/Resources/Scripts/viewModules/viewModuleManager.ts index 4c2915a88a..d3ad0d75c4 100644 --- a/src/Framework/Framework/Resources/Scripts/viewModules/viewModuleManager.ts +++ b/src/Framework/Framework/Resources/Scripts/viewModules/viewModuleManager.ts @@ -105,6 +105,38 @@ export function callViewModuleCommand(viewIdOrElement: string | HTMLElement, com } } +export function callNamedCommand(viewIdOrElement: string | HTMLElement, commandName: string, args: any[], allowAsync: boolean = true) { + if (compileConstants.debug && commandName == null) { throw new Error("commandName has to have a value"); } + if (compileConstants.debug && !(args instanceof Array)) { throw new Error("args must be an array"); } + + const foundModules: ModuleContext[] = []; + + for (let context of getModules(viewIdOrElement)) { + if (commandName in context.namedCommands && typeof context.namedCommands[commandName] === "function") { + foundModules.push(context); + } + } + + if (compileConstants.debug && !foundModules.length) { + throw new Error(`Named command ${commandName} could not be found in view ${viewIdOrElement}.`); + } + + if (compileConstants.debug && foundModules.length > 1) { + throw new Error(`Conflict: There were multiple named commands ${commandName} in view ${viewIdOrElement}.`); + } + + try { + var result = foundModules[0].namedCommands[commandName](...args.map(v => serialize(v))); + if (!allowAsync && result instanceof Promise) { + throw compileConstants.debug ? `Named command returned Promise even though it was called through _js.Invoke("${commandName}", ...). Use the _js.InvokeAsync method to call commands which (may) return a promise.` : "Command returned Promise"; + } + return result + } + catch (e: unknown) { + throw new Error(`While executing command ${commandName}(${args.map(v => JSON.stringify(serialize(v)))}), an error occurred. ${e}`); + } +} + const globalComponent: { [key: string]: DotvvmJsComponentFactory } = {} export function findComponent( @@ -244,7 +276,7 @@ function mapCommandResult(result: any) { } export class ModuleContext { - private readonly namedCommands: { [name: string]: (...args: any[]) => Promise } = {}; + readonly namedCommands: { [name: string]: (...args: any[]) => Promise } = {}; public module: any; constructor( From 34387fd56efbac06d3bbcc4b0b128026cd22b7d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?= Date: Tue, 29 Mar 2022 21:30:33 +0200 Subject: [PATCH 3/3] Added an extensibility point to provide custom fetch implementation --- src/Framework/Framework/Resources/Scripts/dotvvm-base.ts | 8 ++++++++ src/Framework/Framework/Resources/Scripts/dotvvm-root.ts | 4 +++- .../Framework/Resources/Scripts/global-declarations.ts | 2 +- .../Framework/Resources/Scripts/postback/http.ts | 6 +++--- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Framework/Framework/Resources/Scripts/dotvvm-base.ts b/src/Framework/Framework/Resources/Scripts/dotvvm-base.ts index 2e695befd6..4d946588ea 100644 --- a/src/Framework/Framework/Resources/Scripts/dotvvm-base.ts +++ b/src/Framework/Framework/Resources/Scripts/dotvvm-base.ts @@ -134,3 +134,11 @@ function persistViewModel() { getViewModelStorageElement().value = JSON.stringify(persistedViewModel); } + +let _customFetch: (url: string, init: RequestInit) => Promise = fetch; +export function customFetch(url: string, init: RequestInit): Promise { + return _customFetch(url, init); +} +export function setCustomFetch(fetchImplementation: (url: string, init: RequestInit) => Promise) { + _customFetch = fetchImplementation; +} \ No newline at end of file diff --git a/src/Framework/Framework/Resources/Scripts/dotvvm-root.ts b/src/Framework/Framework/Resources/Scripts/dotvvm-root.ts index 1861900a51..0ea2380771 100644 --- a/src/Framework/Framework/Resources/Scripts/dotvvm-root.ts +++ b/src/Framework/Framework/Resources/Scripts/dotvvm-root.ts @@ -1,4 +1,4 @@ -import { initCore, getViewModel, getViewModelObservable, initBindings, getCulture, getState, getStateManager, getRouteName, getRouteParameters } from "./dotvvm-base" +import { initCore, getViewModel, getViewModelObservable, initBindings, getCulture, getState, getStateManager, getRouteName, getRouteParameters, setCustomFetch, customFetch } from "./dotvvm-base" import * as events from './events' import * as spa from "./spa/spa" import * as validation from './validation/validation' @@ -133,6 +133,8 @@ const dotvvmExports = { } as any, StateManager, DotvvmEvent, + get customFetch(): (url: string, init: RequestInit) => Promise { return customFetch }, + set customFetch(implementation: (url: string, init: RequestInit) => Promise) { setCustomFetch(implementation); } } if (compileConstants.isSpa) { diff --git a/src/Framework/Framework/Resources/Scripts/global-declarations.ts b/src/Framework/Framework/Resources/Scripts/global-declarations.ts index 43d72b440d..6b579536b7 100644 --- a/src/Framework/Framework/Resources/Scripts/global-declarations.ts +++ b/src/Framework/Framework/Resources/Scripts/global-declarations.ts @@ -321,4 +321,4 @@ type DotvvmViewModule = { $dispose?: (context: any) => void [commandName: DotvvmViewModuleCommandName]: (...args: any[]) => Promise | any -} +} \ No newline at end of file diff --git a/src/Framework/Framework/Resources/Scripts/postback/http.ts b/src/Framework/Framework/Resources/Scripts/postback/http.ts index 065df5d308..0712182471 100644 --- a/src/Framework/Framework/Resources/Scripts/postback/http.ts +++ b/src/Framework/Framework/Resources/Scripts/postback/http.ts @@ -1,4 +1,4 @@ -import { getVirtualDirectory, getViewModel, getState, getStateManager } from '../dotvvm-base'; +import { getVirtualDirectory, getViewModel, getState, getStateManager, customFetch } from '../dotvvm-base'; import { DotvvmPostbackError } from '../shared-classes'; import { logInfoVerbose, logWarning } from '../utils/logging'; import { keys } from '../utils/objects'; @@ -32,7 +32,7 @@ export async function postJSON(url: string, postData: any, signal: AbortSigna export async function fetchJson(url: string, init: RequestInit): Promise> { let response: Response; try { - response = await fetch(url, init); + response = await customFetch(url, init); } catch (err) { throw new DotvvmPostbackError({ type: "network", err }); @@ -55,7 +55,7 @@ export async function fetchCsrfToken(signal: AbortSignal | undefined): Promise