UI5 Router destroying cached views - sapui5

I have a Master Details Page App where we configured the Router for the same to navigate between pages.
App.view.xml
<SplitApp id="rootControl" detailNavigate="onDetailNavigation">
</SplitApp>
manifest.json
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"viewPath": "master",
"controlId": "rootControl",
"viewType": "XML",
"async":"true"
},
"routes": [
{
....
},
...
"targets": {}
...
App is simple Employee CRUD app, i have configured the router with 2 routes 1 for Create/Edit and another one for Dispaly
I need to destroy the view if i navigate from one view to another view, like on start of the page show the Master Page with all the employees and detail page show display view of the employee1.
i have Edit button on the Display view, on press i navigate details page from Display view to Edit View, on this point i need to destroy the Display view from the router, which is cached.
How to achive this? or do i need to take different approch to solve the cacheing? or I should not think of Memory
tried calling destroy onDetailNavigate of SplitApp
onDetailNavigation : function(oEvent){
console.log("Split app onDetailNavigation");
oEvent.getParameter('from').destroy();
}
Which gives Error next time go back to same view again
Error: The object with ID __xmlview4 was destroyed and cannot be used anymore.

According to the comments you destroy views to save the memory allocated by your two views. I do not think this brings any real benefit. There are three possible solutions:
Stick with the current solution.
Use a single view and switch between a display and a edit fragment. An example can be found here.
Use a single view with a form with input fields. Bind the editable attribute against a model (e.g. view model) property reflecting edit or display state of the whole form or per property.
<Input value="{applicationModel>/propertyName}" editable="{viewModel>/editable}"/>
I'm using a version of the third solution. My application model (extending JSONModel) holds the application data plus a state controlling the property. The controller just calls setEditable on the application model which computes the state. Using this approach I avoid to spread the logic accross to many parts of the application.
<Input value="{applicationModel>/propertyName}" editable="{applicationModel>/Attributes/propertyName/editable}"/>

Since my Question is about the destroying the view from Router which are cached, i have followed the #boghyon comment and made some changes in my code as below which removes the pages after navigation of detail page as below
var splitApp = this.getView().byId('rootControl');
splitApp.removeDetailPage(oEvent.getParameter('from'));
and to remove the cached view from router i have wrote some logic
var router = this.getOwnerComponent().getRouter();
for(var view in router._oViews._oViews){
if( router._oViews._oViews[view].sId === oEvent.getParameter('fromId'){
delete router._oViews._oViews[view];
}
}
Which destroy the view.
By doing this which loads the views multiple times and this is not proper ways for my requirement we have follow the #matbtt answer.
Thank you both for the valueable input.

Related

How to override the routing and redirect to another view in UI5?

Given the simplified sample app based on Shop Administration Tool. When navigating with the sidebar between the views, I just show different views, e.g. View #1, View #2, etc. These views are standalone views, each of them has its own XML-template and JS-controller.
Now, I want to add a permission check for each view and if a user has no permission, then he should be redirected to the main view instead of the desired view.
I've implemented a pre-navigation check:
router.attachRoutePatternMatched(async (event) => {
const targetView = event.getParameters().view.getProperty("viewName");
const isPermitttedToSeeView = await this.checkUserPermission(targetView);
if (!isPermitttedToSeeView) {
MessageToast.show("Sorry, you don't have permissions.");
router.navTo("mainView");
}
}, this);
This code works but the problem is that in case of no permissions user firstly sees a MessageToast message (OK), then is redirected to the forbidden view (bad), and then immediately redirected to the mainView view (OK).
I've tried to use attachBeforeRouteMatched instead, hoping that in this case the routing is not performed yet and I can redirect a user if needed and user will not see the forbidden view. But not, I still see the forbidden view for a second and then I'm redirected to the mainView.
How can I prevent a redirection to the forbidden view and send the user directly to the mainView view? In other words, how can I alter a routing navigation pipeline?
The possible workaround is to use a «hard» redirect instead of routing:
mLibrary.URLHelper.redirect("%desiredURL%", false);
Where mLibrary is defined in sap.ui.define via "sap/m/library".
This will drop any ongoing routing and will force (in terms of UX, not in terms of security) user to be redirected to the desired URL.
Perhaps, it's not the optimal solution from the performance point of view, since it might lead to unwanted rendering but that's the only way I found to prevent user from being routed to undesired view.

How to check if a page component is created by a navController or a modalController

I have a lazy loaded IonicPage Component.
Which can be called in two ways:
navController.push('pageName');
modalController.create('pageName');
Both work well.
Is there a way I can check from within the IonicPage Component to see if it has been created by the navController or the modalController?
Plan B. I can pass a navParam with both e.g. isModal=true to fix this, but I am curious if there is an easier way to check how a page is created.
Context: The page is a component with a list of say users. If the component is loaded as a page (see 1. above) then when we click on an user you can edit its details. But when the component is presented as a modal and we click on it, the details of the clicked user is passed back to the previous page for processing.

Why this.setModel() behaves differently based on context

I have followed the tutorials and now I am trying to extend that learning into a real app. In my app I use a JSON model. Unlike the tutorials, mine is a real-world app and I have to get user credentials to act as a filter when I load the data model. In the tutorials the model is loaded in component.js. In my app I have to prompt the user for id and password so I have a login fragment that appears modally over the first view in the app. This happens to be a master view, and critically it runs after component.js. After validating the user I collect JSON data from the server via Ajax and place it into the default model via this.setData(my_json).
When testing the routing from master to detail view I produced a stubborn bug in that this.getModel() called in the detail view produced an empty model. Huh - I just set the model in the master view and can see the data in the table control - what gives?
I considered a routing issue but confirmed that was not the problem - I can console log the parameters that pass through the router and anyway the detail view appears so routing is ok.
Recap: I use this.setModel() in the master page then this.getModel() in the detail page but the latter is an empty model.
Question: I want the model to be available across the app. The tutorials focus on setting model in component.js but I cannot. What is the correct syntax for setting the global model from the master view for example, or any other place that is not the component.js.
I think I need to use the following in the master (last line is significant):
var oModel = new JSONModel(); // declare a JSON model
oModel.setData(<json string>); // load a JSON string fetched from serve etc.
sap.ui.getCore().setModel(oModel); // important - set as the core model
I think the source of my confusion is that in the tutorials it seems that models are set in the component via
this.setModel(oModel); // a line in component.js
I therefore assume that this in component.js context is app-global whilst this in a view relates to the view along, which makes sense. Am I right?
In the tutorials this.setModel(...) inside the Component.js will set the model on the Component directly. Therefore, the model is visible in all views inside that Component.
When you see this.getView().setModel(...) inside a controller you know that the model is only set on that one view (and therefore it's also visible for it's children).
However, if you see something like this.setModel(...) inside a controller you should check what happens inside this.setModel(...). It is possible that the model is set on the view, or on the Component, or even somewhere else! Some of the tutorials make use of the so called "BaseController" concept. This is basically a parent controller of other controllers and therefore this approach allows to code some handy APIs that you can easily reuse in the child controllers that extend from this BaseController. For example, have a look at the BaseController of the Worklist App. There you can see that the setModel(...) API is setting the model on the view. That means whenever you call this.setModel(...) in your controllers which extend from that BaseController your model is set on the view!
Furthermore, because in a Master-Detail app there is no hierarchy between Master and Details page (parent/child relation) your models on the Master view are not visible on the Detail view.
In your case it seems to be best setting the model on the Component directly. You can do this by calling
this.getOwnerComponent().setModel(...);
inside any of your controllers. Or just do it directly on the Component.js like in the Wordlist tutorial. You can propagate the data to that model later, i.e. at anytime later from within your controllers.

Register Navigation Service to Frame Element and NOT the page - WinRt Prism 2.0

Can anyone help.
We are working on an app which has a consistent header and footer and therefore ideally we'll use one viewmodel for the "home page" but we want the header and footer to remain.
Before we switched to starting using Prism, this was easy enough to navigate as we could control that in the Pages event and set the page.contentFrame.Navigate method to go where we wanted.
Now we're using the MVVM structure (which is superb and wish I'd done it ages ago) the NavigationService class only navigates the entire page (the VisualStateAware page).
How can I set this up so that when calling the Navigate method on the interface in the viewmodel that only the main content frame is ever navigated? or is there a better approach to this?
Any help would be greatly appreciated.
thank you
The question title seems to, pre-empt the details of the question slightly as a solution. But to share a common view model and visual parts across all pages, within a frame, using the navigation service to navigate between pages here is an overview..
Create a shared ViewModel, say "HeaderViewModel" of type say IHeaderViewModel to be shared between the different pages' view models. Inject this into the constructor of each page's ViewModel.
Then expose this as a property of each page's ViewModel. This property could also be called HeaderViewModel too. You can then reference the properties of this common HeaderViewModel in the bindings in the View, using binding '.' notation.
If you are using Unity with Prism, you can create this shared instance HeaderViewModel in the OnInitialize override of the App.
Create a shared part for each Page/View as a UserControl, which can be positioned on each page in the same place. This enables you to bind to the same properties on your HeaderViewModel.

Returning object model to a view, that is handled by different controller

how can I pass an object model to a view, that is partial view on a master page?
regards
You might consider creating an another object that more closely represents the view you are trying to render.
Let's say i have an MyDomain.Order object, so I make a view page that looks something like ViewPage<MyDomain.Order>. Now, let's say that I have a menu that is driven off of a logged in user, as example. It wouldn't make sense to have menu as a property of MyDomain.Order. I would create another object, specifically for the view, call it something like OrderPageModel and have MyDomain.Order and List<MenuItem> as properties of this new object, my view being set up as ViewPage<OrderPageModel>.
The other thing to consider might be something like Html.RenderAction(). Same scenario, I have a view, and as you mention in your question, it has a master page, and as in my example, lets say it hosts a menu common to your site. You could create a partial view (UserMenu.ascx) and a controller (SiteController.cs) with an action (UserMenu) that calculates the items for the menu. Inside your master page, you can then call <% Html.RenderAction("UserMenu","SiteController") %>.
I would use the first example if it could be something made for a particular view: just make it a part of the model. I would use the second example if it was something more generic to the site, like a menu.
You could specify the location of the view:
return PartialView("~/Views/SomeOtherController/SomePartial.ascx", someModel);
Best bet here is RenderAction over RenderPartial. Your child controller can easily figure out if the user is logged in and render the right partial rather than making your master page worry about these details.