openui5 icon indicating copy to clipboard operation
openui5 copied to clipboard

How to interrupt / override the ongoing routing?

Open pubmikeb opened this issue 2 years ago • 18 comments

OpenUI5 version: 1.98.0

Under some circumstance, I would like to forward a user from the view «A» to the view «C» instead of the view «B». I've tried so far:

  1. attachRoutePatternMatched
  2. attachBeforeRouteMatched
  3. router.stop()
  4. changing a hash via the HashChanger
  5. sap.m.library.URLHelper.redirect(…)

attachRoutePatternMatched and attachBeforeRouteMatched work but I observe the undesired view «B» for a second and then I'm redirected to the view «C». router.stop() stops the navigation but then if I re-initialize it again within the same method, then I'm redirected the undesired view «B». Changing a hash via the HashChanger doesn't help either. The only workaround that works for me is to use a hard redirect sap.m.library.URLHelper.redirect(…), that works correctly but I assume it leads to extra performance costs.

Is there any reliable and «friendly» approach to override /interrupt the ongoing routing, so user will not see the initially planned view but another one? I'm talking purely from the UX perspective, not the security-one.

Related topics:

pubmikeb avatar Dec 08 '21 06:12 pubmikeb

Hi @pubmikeb,

I've created an internal incident 2180412040. The status of the issue will be updated here in GitHub.

Regards, Oliver

stoyanovski avatar Dec 09 '21 20:12 stoyanovski

Hi @pubmikeb , thanks for sharing your interesting scenario. router.stop() and router.initialize(true) should do the job for you. The very really interesting part is where you put this coding in your application. All the routing events are placed after the hash changed already. Where do you wanna decide that you navigate to «C» instead of «B»? As far as I understand you wanna perform this coding in the responsibility of «B», so «B» has to be loaded before. So, «B» would be part of the routing history. Could you share some more details about your desired point where you wanna decide about the interrupting?

flovogt avatar Dec 21 '21 07:12 flovogt

@flovogt, thanks for the further info.

router.stop() and router.initialize(true) should do the job for you.

Actually, it doesn't do the job, otherwise I would not open this ticket. router.stop() really stops the navigation but once I call router.initialize(true) within the same method, then I'm redirected the undesired view «B».

where you put this coding in your application.

I'm using sap.tnt.SideNavigation, in the onInit() of the corresponding view's controller, I attach an event onClickSideNavigation:

this.getView().byId("sideNavigation").attachItemSelect(this.onClickSideNavigation, this);

Where do you wanna decide that you navigate to «C» instead of «B»?

In onClickSideNavigation() I call a method, which decides wherever allow navigation to «B» or to «C»:

async onClickSideNavigation(event) {
	const viewName = event.getParameters().item.mProperties.key;
	await this.navigationHandler(viewName);
}

All the routing events are placed after the hash changed already.

Perhaps, I should perform a permission check prior the hash will be already changed. The question, to which event should I attach my check?

As far as I understand you wanna perform this coding in the responsibility of «B», so «B» has to be loaded before. So, «B» would be part of the routing history.

Actually, I want to perform the check if user should be able to see the view «B» or must be forced to the view «C» inside of the shell view, which contains sap.tnt.SideNavigation. So, if user should not see «B», then «B» should not be loaded and therefore should not appear in the navigation history as well. The user should get «C» instead.

Could you share some more details about your desired point where you wanna decide about the interrupting?

Yes, sure. I just want to figure out how to adjust the navigation between the views in UI5 according to the user permissions:

  1. I'm on the shell view (with sap.tnt.SideNavigation) with the «A» loaded
  2. Now, I want to open the view «B» inside of the shell view
  3. I click on the view «B» item in the navigation sidebar
  4. UI5 sends the request if I'm eligible to see the view «B»
    • if I have a permission, then navigate me to the view «B» and the view «B» should appear in the navigation history
    • otherwise, show me the view «C» or some other warning

In case of I'm not eligible to see «B», I still should be able to click on «D» in the navigation sidebar and repeat the step number 4 for the desired view «D», therefore router.stop() is not enough and I need to use router.initialize(true) as well. That's all, as you may see, the scenario is trivial and standard.

And of course, I'm talking purely from the UX perspective, not the security-one.

pubmikeb avatar Dec 21 '21 11:12 pubmikeb

@flovogt, I think I found out the way to workaround the case, sap.tnt.SideNavigation has a property itemSelect, which allows specifying an even, that will be triggered at the time on a click on the SideNavigation item, there I can perform required logic before the navigation/routing will be started.

Anyway, it would be great to have more robust and flexible API for manipulating the routing, so I could not only stop the navigation butt actually change it in the real time.

pubmikeb avatar Dec 22 '21 23:12 pubmikeb

Hi @pubmikeb , sorry for my delay. I planned to provide a small showcase doing exactly what you described here. The check which route should be navigated has to happen before the actual navigation happens.

onItemSelect: function() {
var sRoute = "C";
if(..) {
   this.getRouter().navTo("B");
 }
}

We discussed your scenario and agreed on that interrupting the routing process is currently not easy for applications. We'll discuss this item further. Stay tuned!

flovogt avatar Dec 23 '21 10:12 flovogt

Some thoughts from my side: The question is where the coding of the check should be placed. In the sample above "A" performs the check. When "B" should contain the check but "B" should not be added to the routing history and also no visible navigation should happen when the check fails, then there is some more work to do. Think about someone has access to the content of "B" and sends the link to someone else with missing permissions. "B" has to contain the check nevertheless to inform the user that she has no access. I think it can not be handled generally because sometimes you wanna redirect the user. In other cases you wanna show a popup or react in different ways. What's your opinion on that?

flovogt avatar Dec 23 '21 10:12 flovogt

What's your opinion on that?

I also thought about this scenario and assumed that there should be 2 checks:

  • before the navigation, if a user tries to open a view from the side bar menu, here this approach can work out.
  • a user tries to open a view, which he has no permissions to, directly, via URL in address bar, then a permission check should be implemented somewhere in onBeforeRendering.

pubmikeb avatar Dec 23 '21 15:12 pubmikeb

Hi,

I have a similar requirement, but we are using SAPUI5 and BTP portal framework. I want to intercept inter-app navigation as opposed to the intra-app navigation discussed here.

The code below seems to do the trick, you probably can implement sth similar on the HashChanger. From an encapsulation point of view, this appraoch is bad practice, but I don't see an alternative.

I plan to create a small framework that allows a registered app to decide on what conditions navigation can happen (allow silently, allow after user approval, abort navigation and save state, ...).

oXAppNavService = sap.ushell.Container.getService("CrossApplicationNavigation"); oXAppNavService.toExternalOriginal = oXAppNavService.toExternal; oXAppNavService.toExternal = function (oArgs, oComponent){ if (confirm('Current app registered itself as a Navigation Guard and it told me it is in a dirty state. Navigate anyway?')) { oXAppNavService.toExternalOriginal(oArgs, oComponent); } }

Regards, Jeroen

jversignify avatar Jan 05 '22 09:01 jversignify

Hi @pubmikeb , @jversignify points to a valid point. Overriding the HashChanger artefact would enable you to hook into the navigation process. But as @jversignify already points out, it is bad practice because the HashChanger artefact should not be changed by applications. We have discussed this requirement and thought about following future concept: You can attach your check function in the attachBeforeRouteMatched handler:

this.getOwnerComponent().getRouter().attachBeforeRouteMatched(this.checkAccess.bind(this));
checkAccess: function(oEvent) {
    if(!this.hasAccess()) {
       oEvent.preventDefault();
    }
   }

When the event is marked with preventDefault we would omit loading the target and the history would not contain the hash for this route. IMHO the downside of this solution is that the hasAccess method has to return synchronously the result. So, you can not perform any async backend calls here because the response of the backend call would come later than the navigation process. Whats your opinion on that?

flovogt avatar Jan 14 '22 09:01 flovogt

BacklogItem is CPOUI5FRAMEWORK-338.

flovogt avatar Jan 14 '22 09:01 flovogt

Let's keep the issue open to wait for further input form @pubmikeb

flovogt avatar Jan 14 '22 12:01 flovogt

Hi @flovogt,

thanks for the proposed solution. Yes, the sync-query limitation is not the ideal but if currently it's the only way to manipulate the navigation process, so it should be considered as well.

I've actually reviewed the place where I call hasAccess() and now call it on a click on navigation item, so it checks the permissions before the navigation starts, while attachBeforeRouteMatched occurs already during the navigation process.

pubmikeb avatar Jan 15 '22 10:01 pubmikeb

Hi @flovogt,

[...] thought about following future concept [...]

I'm a bit late to the party here but is this something that has already been introduced to UI5 or did it remain a concept for the future (i.e. is the "preventDefault" considered already)? That'd be very useful when trying to centrally handle such things from within some sort of BaseController or the Component itself.

wridgeu avatar Sep 06 '22 16:09 wridgeu

Hi @wridgeu , not too late. This feature is still in the concept phase. As described above the downside is the need of a synchronous decision to abort the navigation process. Currently, the team does a great job of eliminating synchronous tasks in the framework and offering asynchronous options, introducing a new synchronous concept fits not really in this work stream, so the team is still looking for a way to make this concept asynchronous directly from the start. The team will keep you up to date, but we also encourage the community to propose additional ways of achieving this requirement. So if you have an idea how this might work, let us know.

flovogt avatar Sep 07 '22 14:09 flovogt

Is there a current way that is recommended to get the desired functionality? Thanks for your advice!

BenjyTec avatar Oct 17 '23 11:10 BenjyTec

Is there a current way that is recommended to get the desired functionality? Thanks for your advice!

See https://github.com/SAP/openui5/issues/3411#issuecomment-1000191501

flovogt avatar Jan 11 '24 12:01 flovogt

Is there a current way that is recommended to get the desired functionality? Thanks for your advice!

See #3411 (comment)

So has this already been implemented? In the comment you linked I see that you were talking about how a future concept looks like. When I tried that approach in the past, I think preventDefault() had no effect.

BenjyTec avatar Jan 25 '24 11:01 BenjyTec

@BenjyTec stoping the ongoing navigation process is not implemented so far.

Therefore, we recommend to check the condition before the actual navigation. See https://github.com/SAP/openui5/issues/3411#issuecomment-1000191501


Thanks for the hint, I have corrected the link in the comment above.

flovogt avatar Jan 25 '24 12:01 flovogt