diff --git a/src/Framework/Framework/Resources/Scripts/dotvvm-base.ts b/src/Framework/Framework/Resources/Scripts/dotvvm-base.ts index bd02db317c..4d946588ea 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 } }); } @@ -125,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 1064edfdef..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 } 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' @@ -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) @@ -109,6 +111,7 @@ const dotvvmExports = { registerOne: viewModuleManager.registerViewModule, init: viewModuleManager.initViewModule, call: viewModuleManager.callViewModuleCommand, + callNamedCommand: viewModuleManager.callNamedCommand, registerMany: viewModuleManager.registerViewModules }, resourceLoader: { @@ -130,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 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( 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;