LANSA Tour Of Heroes Pt 2

The previous post, walked through getting the Layout and the beginning of the Application Shell in place.  This time we will focus on navigation and presentation. 

Routing

If you come from the Angular, Ember or even React world you are probably familiar with a router. While each implementation is different, they all offer the same concept, mapping a particular view to a URL and displaying that view in some form of outlet. LANSA does not have a "router" per say out of the box. It is however pretty easy to create something similar.

The major difference in how we can handle routing in LANSA verses another SPA framework is that we don't have the nice, clean URLs of those frameworks. So instead of something like '\customer\edit\4' when we are editing the customer with id 4 we end up with something like 'MYAPP.html?view=CSTEDT&id=4'. 

While looking to improve on the basics of routing in LANSA, I had a conversation with GregSippel on the LANSA forums who talked about keeping navigation separate from the presentation of the view. This made all kinds of lights come on for me and is the pattern discussed here, which may be unlike some previously posted patterns.

Navigation

Much like other SPA frameworks, navigation is triggered by changing the URL of the browser. Be it from a button click, link click, forward and back buttons on the browser or the user manually changing the address bar. Each one of those events triggers can change the URL. With LANSA we need to do the same thing, change the URL when the user performs a navigable action. With Angular you would use a router-link or inject the router into your component. We need something similar that will enable any view or component to trigger the change. 

Application Controller

I tend to create an "ApplicationController" or "ApplicationManager" to house bits of logic that can be used across the application. This object is generally defined in the Application scope, so once it gets created, the reference is shared across components, much like a Singleton.

To get started, create a new Resuable Part, but instead of a Panel use the Object type.

New object

Name and Identify your object accordingly.

There are a couple things this object has to do. 

The first of which is to modify the URL to trigger navigation. Modifying the URL is done by adding to the web history.

#SYS_WEB.History.Add( ('view=&1').Substitute('MYVIEWID'))

If you where to use this, it would work, the URL would change and the query string would include the view id. However there is a significant flaw here. If you had any other parameters on the query string, those parameters have just been replaced with the view parameter. By "adding" to the history, you are "replacing" the entire query string. This can be really problematic when developing because LANSA adds some parameters to the query string to trigger things like debugging. You can also include flags that enable tracing, or to disable caching.

First lets define a Keyed Collection (PRIM_KCOL) of PRIM_ALPH keyed by STD_STRNGs. This will allow us to store multiple parameters to use. 

define_com class(#PRIM_KCOL<#PRIM_ALPH #STD_STRNG>) name(#mParameters)

Then we can copy known parameters from the URL and put them in our keyed collection. You could copy all the parameters, however then you have the problem of removing a parameter, such as removing an object id when navigating from a details view to a list view.

Then we add our view parameter to the collection. 

#mParameters<VIEW> := #pViewId

Build the query string based on our parameters and add the query string to the history. 

#SYS_WEB.History.Add( #COM_OWNER.BuildQueryString.LowerCase )

Finally we need to clear out our parameters, so that any parameters in the collection do not interfere with the next navigation change. 

#mParameters.RemoveAll

Another pattern may be to remove only a single parameter at a time. This would mean you don't remove all the parameters at once. Any parameters in the collection would be overridden by parameters in the URL, however it would force the calling object to explicitly remove parameters before navigating to the next view. 

The complete Application Controller

Presentation

Whenever the URL is changed, LANSA fires the SYS_WEB.URLChanged event. I like to handle this event in one of two places. 

  1. Directly in the Web Page
  2. In a Reusable Part that is acting like a "Router Outlet"

Here we are going to handle it inside the web page because that is where we have our ViewContainerPanel defined.

Open the web page we created in the previous post and add a reference for the Application Controller (in *APPLICATION scope) and a handler for the URL Changed event.

A quick note on Application scope

I tend to define Application scoped components without a name. They have an implied name derived from their class name. So you would refer to the object through its class name like #ApplicationController.NavigateTo(MYVIEW)

Using a name will scope that instance to that name. So if on one component I defined it as 
define_com class(#ApplicationController) name(#Application)

and in other object I defined it as
define_com class(#ApplicationController) name(#App) 

I would have two different instances of the ApplicationController!

Handling URLChanged

The URLChanged event gets called when the page is loaded and when the URL changes. This is important because when the page loads, you may not have a view identifier, is when you load your initial view.

Now we need actually show the view and in order to do that, we need to do a few things.

  1. Transition from the previous view (if we have one) to the new one
  2. Tell the previous view (if we have one) that it is about to hidden and tell the new one that its about to be shown
  3. Adjust the view to fit into the Layout 

View Panel

To make this work, we are going to create a base class that our views will inherit from. This base class will allow us to get our views, which are really just PRIM_PANLs separate from other panels. It also gives a chance to add some hooks into the view so that each view can handle activating (when a view is about to be shown) and deactivating (when a view is about to be hidden).

Create a new panel, I named mine ViewPanel and add Activate and Deactivate methods. 

These methods will be overridden in child components and will give the component a chance to handle any logic required when a view is about to be shown and hidden.

Now that we have our ViewPanel, we know we are going to need three view for the Tour of Heroes application. A dashboard, a Hero list and a Hero details.

Create those panels and name and identify them accordingly. I named them DashboardViewPanel, HeroesViewPanel and HeroDetailViewPanel.

Open each new View Panel, and change the ROLE to (*EXTENDS #ViewPanel). You may want to add a border to each View Panel and a Label so you can see it later.

You should have something like this

Web Page

Now we need a place to reference those views. There are a couple different ways to do this, the easiest being a direct reference. You could dynamically create the views but that brings in some other issues. For now, lets just define those views in our web page. 

Notice that the parent has been defined as ViewContainerPanel. Technically you don't need to include it here. You could specify the parent when adjusting the layout. 

We will also add a component to keep track what our current view is

define_com class(#PRIM_ALPH) name(#mCurrentViewId)

and a keyed collection so we can easily reference the views

define_com class(#PRIM_KCOL<#ViewPanel #STD_TEXTS>) name(#mViews) style(Collection)

Now we can add our views to our collection when the web page is created

This will allow us to reference our views using only the view identifier.

#myView <= #mViews<MYVIEWID>

Showing a View

Finally we can implement our ShowView method.

The first thing we need to do is check that we have the requested view in our collection. If we don't we just show an alert. This could be that we misspelled an identifier, did not reference the view correctly or did not add it to the collection.

Then we set the previous view id and the current view id and activate the current view.

At this point, if we have a previous view and the previous view is not the same as the current view, we can transition to the current view. Otherwise, we'll just make the current view visible.

Lastly we need to adjust the layout. 

This is done by telling the ViewContainerLayoutItem to manage the view and adjusting its height to ContentHeightFitToWidth.

Hooking Up the Navigation Bar

Now we have everything in place we can hook up the navigation bar and respond to navigation changes.

Remember the Navigation Bar we added to the web page? We can now hook into our Clicked event.

Now that we have our panels defined as well we can go back to our Navigation Bar and add the view identifiers to the Component Tag of each label.

define_com class(#PRIM_LABL) name(#DashboardLabel) caption('Dashboard') displayposition(1) ellipses(Word) height(25) left(5) parent(#COM_OWNER) tabposition(1) tabstop(False) top(16) verticalalignment(Center) width(69) themedrawstyle('NavBarButton') marginleft(5) marginright(5) marginbottom(5) margintop(5) componenttag('TOHDSHP01')define_com class(#PRIM_LABL) name(#HeroesLabel) caption('Heroes') displayposition(2) ellipses(Word) height(25) left(84) parent(#COM_OWNER) tabposition(2) tabstop(False) top(16) verticalalignment(Center) width(49) themedrawstyle('NavBarButton') marginbottom(5) marginleft(5) marginright(5) margintop(5) componenttag('TOHHROP01')

The complete Web Page source

Testing it out

Now if we did everything right you should be able to compile everything and execute the web page.

When the page loads you should see the dashboard. Check the URL, notice we don't have any view defined.

Dashboard view

Click the Heroes link and we see the Heroes view and the URL has the proper view id

Heroes view

Click the Dashboard Link and we see the Dashboard view and the URL has the proper view id.

Dashboard view 2

Summary

We walked through handling navigation and presentation. While we don't have a built-in routing mechanism like Angular, its not too hard to handle it ourselves.

Source Files

Application Controller

View Panel

Dashboard View Panel

Heroes View Panel

Navigation Bar

Web Page

 In Part 3 we will take a look at implementing the HeroesView and see how we query data out of the database.

Comments

You can follow this conversation by subscribing to the comment feed for this post.

The comments to this entry are closed.