Quantcast
Channel: raphael.vogel
Viewing all articles
Browse latest Browse all 11

Writing a betting pool application for the Soccer World Cup 2006 (Part IV)

$
0
0

In this weblog we will build the UI for our betting pool application. Since Web Dynpro has been released for quite a while, a lot of tutorials are available in SDN. Therefore I assume, that the reader has experience in Web Dynpro. Describing every single click would really blow up the whole blog. To give you a first impression, the following two screenshots show you how the application could look like. After the user has logged on, he sees a table with all matches, the results of the matches and the predictions he has done. In the header we have two links. The "Make predictions" link will show the match table (as shown in the first picture) and the "Rank List" link will show a list of all users and the points they received (as shown in the second picture).
image

image


But before we are going to build the UI, we need to switch on the self registration. Because we have marked the WD application as protected (see Part III of this blog series), the users first have to register themselves in the UME. This is done by clicking the link "Register Now...". To enable this feature start the Visual Administrator (Path: usr > sap > J2E > JC01 > j2ee > admin > go.bat). Go to "Services > UME Provider" and set the property "ume.logon.selfreg" to TRUE. After that you have to restart the J2EE Server !
image

Defining the UI

The UI consists of three views which are embedded into a view container called "MainGrid". In the HeaderView, we have a logo and a welcome message for the user. There are also two links in the header, one called "Make predictions" which shows the PredictionView and the other called "Rank List" which shows the RankView

Create the views and define the navigation between them

1) Delete the assignment of the "PredictionView" to the "BettingPoolApp" window. Therefore, right click on the "PredictionView" and select delete as shown in the picture.
image

2) Create a new View Set called "MainGrid" in your BettingPoolApp window. The View Set should be based on the Grid Layout
image

3) Select the "MainGrid" and switch to the properties tab below to change the value of the property "columns" to 1. Also set the property "default" to true.

4) In cell[1,1] of the View Set embed a new view called HeaderView.
image

5) In cell[2,1] embed a new view called RankView

6) In cell[2,1] embed the existing view PredictionView. Set the property "default" of the PredictionView to "true".

7) In the navigation modeler (right click on the BettingPoolApp window > Open Navigation Modeler), define two outbound plugs for the HeaderView, called "ToPredictionView" and "ToRankView" without any parameters. The outbound plugs, the inbound plugs and the navigation link can be found in the toolbar at the left side in the navigation modeler (see following picture)
image

8) Define also two inbound plugs called "FromHeader", one on the PredictionView and one on the RankView

9) Define the navigation links from the HeaderView to the PredictionView and RankView. The following picture shows how the navigation should look like
image

10) Switch to the Layout tab of the "HeaderView" and design it as you like. The important thing is, that the header should contain two "LinkToAction" UI Elements and a TextView UI element, which prints out the welcome message to the user.

11) After you have added the TexView UI element and the two LinkToAction UI elements to the HeaderView, go to the property "onAction" of the first "LinkToAction" UI element and create a new Action called "GotoPredictionView". You have to click the button with the caption "..." to create a new Action (see following picture)
image

12) In the wizards, select the plug "ToPredictionView" as the plug which should be fired, when the user clicks on that link. After selecting the right plug, click Finish
image

13) Do the corresponding steps for the second "LinkToAction" UI element (define a new Action "GotoRankView" which fires the outbound plug "ToRankView"

14) After defining the navigation, you should deploy and test the navigation of your WD application (if you have problems, see the next paragraph). To deploy the application, open the node "Applications" in the WD Explorer, right click on your application called "BettingPoolApp" and click "Deploy New Archive and Run"
image

If you have any problems with the navigation, please goto the implementation tab of the HeaderView and check if the two action handler methods do really fire the plugs (in my case they did not fire the plugs!). The event handler method "onActionGotoPredictionView" must have the following code:

wdThis.wdFirePlugToPredictionView();

The event hanlder method "onActionGotoRankView" must have the following code:

wdThis.wdFirePlugToRankView();


Define the Component Controller Context Structure

Now we define the context of the Component Controller. Only the Component Controller will make the calls to the BettingPool Web Service. I'll work with the model classes directly without using model nodes, because I want to do some preprocessing before displaying the data from the Business Layer. For example if there is no Prediction for a Match, the Business Layer returns -1 as the prediction value. In the UI however, I don't want to show -1. I want to show an empty input field instead. In the following steps we define the context structure of the Component Controller. Later on, we map the Component Controller context to the view controller context.

1) Define a value node called "MatchPredictionData" in your Component Controller Context

2) Define the following value attributes on the node "MatchPredictionData"

 

  • Name: team1 Type: string
  • Name: team2 Type: string
  • Name: matchDate Type: string
  • Name: goal1 Type: string
  • Name: goal2 Type: string
  • Name: prediction1 Type: string
  • Name: prediction2 Type: string
  • Name: locked Type: boolean
  • Name: playerRef Type: string
  • Name: predictionRef Type: string
  • Name: matchRef Type: string

3) Define a value node called "PlayerList" in your Component Controller Context

4) Define the following value attributes on the node "PlayerList"

 

  • Name: rank Type: integer
  • Name: points Type: integer
  • Name: uniqueID Type: string

The resulting Component Controller context structure should look like this:
image

Defining some utility functions

In the Component Controller, we need some utility functions. For example to sort our tables, we need classes which implement the Comparator interface. We also have additional methods which capsulate the call to the Web Service.

1) Switch to the Implementation tab of your Component Controller. Include the following code in the "others" section (after //@@begin others):

// Instance variables ComplexType_Player player; // Retrieves a player private void retrievePlayer(String uniqueID) throws Exception{ Request_BettingPoolWSViDocument_getPlayerByUniqueID_R playerModel = new Request_BettingPoolWSViDocument_getPlayerByUniqueID_R(); playerModel.setUniqueID(uniqueID); playerModel.execute(); player = playerModel.getResponse().getResult(); } // Creates a Player in the Business Layer private void createPlayer(String firstname, String lastname, String uid) throws Exception{ Request_BettingPoolWSViDocument_createPlayerR playerModel = new Request_BettingPoolWSViDocument_createPlayerR(); playerModel.setFirstname(firstname); playerModel.setLastname(lastname); playerModel.setUniqueID(uid); playerModel.execute(); this.player = playerModel.getResponse().getResult(); } // Sorts by the date of the Match private class MatchSorter implements Comparator{ public int compare(Object o1, Object o2){ ComplexType_MatchPredictionDTO m1 = (ComplexType_MatchPredictionDTO)o1; ComplexType_MatchPredictionDTO m2 = (ComplexType_MatchPredictionDTO)o2; if(m1.getMatchTime().equals(m2.getMatchTime())) return 0; else if(m1.getMatchTime().before(m2.getMatchTime())) return -1; else return +1; } } // Sorts by the Player points private class PlayerSorter implements Comparator{ public int compare(Object o1, Object o2){ ComplexType_Player p1 = (ComplexType_Player)o1; ComplexType_Player p2 = (ComplexType_Player)o2; if(p1.getPoints() == p2.getPoints()) return 0; else if(p1.getPoints() < p2.getPoints()) return +1; else return -1; } }
Implement the "wdDoInit" method of the Component Controller

If the application starts, the wdDoInit method of the Component Controller is called. Here we need to check if a Player Entity already exists. If the Player does not exist, a new Player entity is created. Otherwise the Player entity is read and stored as a reference variable in the Component Controller.

1) To be able to access the UME to get the user, we need to add a new "Used DC" to our project. In your WD project open the node "DC MetaData > DC Definition > Used DCs". Add the DC "com.sap.security.api.sda" to your Used DCs. The DC "com.sap.security.api.sda" can be found under "Local Development > SAP-JEE"
image

2) Include the following code in the "wdDoInit" method of the Component Controller:

try{ IUser sapUser = WDClientUser.getCurrentUser().getSAPUser(); this.retrievePlayer(sapUser.getUniqueName()); if(this.player == null || this.player.getUniqueID() == null){ // player does not exist -> create it this.createPlayer(sapUser.getFirstName(), sapUser.getLastName(), sapUser.getUniqueName()); } } catch(Exception e){ logger.errorT(e.toString()); }
Define the method "getPlayer" in the Component Controller

This method is used to get the reference to the player object which is stored in the Component Controller. The HeaderView for example calls this method to get the firstname and the lastname of the Player to display the welcome message.

1) Go to the Methods tab of the Comp. Controller and create a new method called "getPlayer". The return type must be "ComplexType_Player" (choose Java Native Type)

2) Go to the Implemetation tab of the Component Controller and insert the following code to the method "getPlayer":

return this.player;
Define the method "getAllMatchesAndPredictions" in the Component Controller

This method is called from the PredictionView to display a table of all matches, the results and the predictions the player has made.

1) Go to the Methods tab of the Comp. Controller and create a new method called "getAllMatchesAndPredictions". The method has no return type and no parameters

2) Go to the implementation of the method "getAllMatchesAndPredictions" and insert the following sourcecode:

wdContext.nodeMatchPredictionData().invalidate(); Request_BettingPoolWSViDocument_getAllMatchesAndPredictionsR matchPredModel = new Request_BettingPoolWSViDocument_getAllMatchesAndPredictionsR(); matchPredModel.setPlayerKey(this.player.getKey()); try{ matchPredModel.execute(); List matchTipList = matchPredModel.getResponse().getResult(); Collections.sort(matchTipList, new MatchSorter()); // fill the context with the values for(Iterator it = matchTipList.iterator(); it.hasNext(); ){ ComplexType_MatchPredictionDTO dto = (ComplexType_MatchPredictionDTO)it.next(); IPublicBettingPoolApp.IMatchPredictionDataElement element = wdContext.nodeMatchPredictionData().createMatchPredictionDataElement(); element.setTeam1(dto.getTeam1()); element.setTeam2(dto.getTeam2()); DateFormat df = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT); String dateString = df.format(dto.getMatchTime().getTime()); element.setMatchDate(dateString); element.setGoal1((dto.getGoal1() == -1)? "":String.valueOf(dto.getGoal1())); element.setGoal2((dto.getGoal2() == -1)? "":String.valueOf(dto.getGoal2())); element.setPrediction1((dto.getPrediction1() == -1)? "":String.valueOf(dto.getPrediction1())); element.setPrediction2((dto.getPrediction2() == -1)? "":String.valueOf(dto.getPrediction2())); element.setLocked(!dto.getLocked()); element.setMatchRef(dto.getMatchRef()); element.setPlayerRef(dto.getPlayerRef()); element.setPredictionRef(dto.getPredictionRef()); wdContext.nodeMatchPredictionData().addElement(element); } } catch(Exception e){ logger.errorT(e.toString()); }


Define the method "getRankList" in the Component Controller

This method is called from the RankView to display the rank list of all Players.

1) Go to the Methods tab of the Comp. Controller and create a new method called "getRankList". The method has no return type and no parameters

2) Go to the implementation tab and add the following code to the "getRankList" method.

wdContext.nodePlayerList().invalidate(); Request_BettingPoolWSViDocument_getRankListR rankModel = new Request_BettingPoolWSViDocument_getRankListR(); try{ rankModel.execute(); List playerList = rankModel.getResponse().getResult(); Collections.sort(playerList, new PlayerSorter()); int i=1; for(Iterator it=playerList.iterator(); it.hasNext(); ){ ComplexType_Player aPlayer = (ComplexType_Player)it.next(); IPublicBettingPoolApp.IPlayerListElement element = wdContext.nodePlayerList().createPlayerListElement(); element.setUniqueID(aPlayer.getUniqueID()); element.setRank(i++); element.setPoints((int)aPlayer.getPoints()); wdContext.nodePlayerList().addElement(element); } } catch(Exception e){ logger.errorT(e.toString()); }
Define the method "savePredictions" in the Component Controller

This method is called from the PredictionView, if the user clicks on the save button to save all predictions he made.

1) Go to the Methods tab of the Comp. Controller and create a new method called "savePredictions". The method has no return type and no parameters

2) Go to the implementation tab and add the following code to the "savePredictions" method.

// loop over match predictions table List returnList = new ArrayList(); IPublicBettingPoolApp.IMatchPredictionDataNode node = wdContext.nodeMatchPredictionData(); int size = node.size(); for(int i=0; i< size; i++){ IPublicBettingPoolApp.IMatchPredictionDataElement element = node.getMatchPredictionDataElementAt(i); ComplexType_MatchPredictionDTO dto = new ComplexType_MatchPredictionDTO(); if(element.getPrediction1() == null || element.getPrediction1().equals("")) dto.setPrediction1(-1); else dto.setPrediction1(Integer.parseInt(element.getPrediction1())); if(element.getPrediction2() == null || element.getPrediction2().equals("")) dto.setPrediction2(-1); else dto.setPrediction2(Integer.parseInt(element.getPrediction2())); dto.setMatchRef(element.getMatchRef()); dto.setPlayerRef(element.getPlayerRef()); dto.setPredictionRef(element.getPredictionRef()); dto.setLocked(!element.getLocked()); returnList.add(dto); } Request_BettingPoolWSViDocument_saveAllPredictionsR predSaveModel = new Request_BettingPoolWSViDocument_saveAllPredictionsR(); predSaveModel.setMatchPredictionDTO0(returnList); try{ predSaveModel.execute(); } catch(Exception e){ logger.errorT(e.toString()); }
Create the context mapping to the view controllers

The view Controllers must be able to access the data we have retrieved in the component controller. Therefore a context mapping from the Component Controller to the PredictionView and the RankView must be made.

1) Open the Data Modeler (right click on the node "BettingPoolApp")
image

2) Define a data link from the PredictionView to the Component Controller. In the toolbox on the left side of the Data Modeler you find the data link.
image

3) After connecting the PredictionView with the Component Controller a wizard opens, where you can map the complete node "MatchPredictionData". Just drag this node from the right (your Component Controller Context) to the left (your PredictionView Context), select all attributes, click "Ok" and then "Finish".
image

4) Define a data link from the RankView to the Component Controller. Map the complete node "PlayerList"
image

Maintain the HeaderView

In the header view we want to display a welcome message like "Welcome Peter Smith".

1) In the Properties tab of the HeaderView add the Component Controller as a required controller.
image

2) In Context of the HeaderView, add the value attribute welcomeMessage of type string
image

3) Switch to the Layout tab of the HeaderView. Bind the property "text" of the TextView UI Element to the context attribute "welcomeMessage" by clicking the button with the caption "..."
image

4) Add the following code in the wdDoInit method of the HeaderView. You should actually use the message manager to provide the message. Do not hardcode the welcome message like I did

IPublicBettingPoolApp compController = wdThis.wdGetBettingPoolAppController(); ComplexType_Player player = compController.getPlayer(); // This should be done with the message manager ! - not hardcoded ! String msg = "Welcome "+player.getFirstname()+" "+player.getLastname(); wdContext.currentContextElement().setWelcomeMessage(msg);
Maintain the PredictionView

On the PredictionView we now create a table, which shows all matches and the predictions the user has already made. We also insert a "Save" button, where the user can save his predictions

1) Navigate to the Layout tab of the PredictionView. Right click on the RootUIElementContainer and apply a table template
image

2) Select all attributes of node "MatchPredictionData" beside the following: locked, matchRef, playerRef and predictionRef, because we don't want to display those values to the enduser. Click Next

3) For the columns prediction1 and prediction2, select an "InputField" as the used UI element. Click Finish

4) You can now change the caption of the table columns if you like

5) In the Outline View, go to the prediction1 input field and bind the value "enabled" to the context attribute "locked". This ensures, that if a match has already began, the input field is not enabled.
image

6) Do the same with the prediction2 input field (bind it to the context attribute "locked")

7) Add a "Save" button by applying an ActionButton template. Insert a name for the button label and press Next

8) Call the method "savePredictions" of the Component Controller (see following picture) and click Finish.
image

9) Go to the implementation tab and implement the method "onPlugFromHeader". This method is called, if the navigation in the HeaderView is triggered. Insert the following code:

wdThis.wdGetBettingPoolAppController().getAllMatchesAndPredictions();

10) Go to the implementation tab and implement the method "wdDoInit". This method is called, if the view is displayed the first time. Insert the following code:

wdThis.wdGetBettingPoolAppController().getAllMatchesAndPredictions();
Maintain the RankView

In the RankView we show the rank list table for all players and the points they received

1) Navigate to the Layout tab of the RankView. Right click on the RootUIElementContainer and apply a table template

2) Select all attributes of the node "PlayerList", click Next and then Finish.

3) Go to the implementaion of the method "onPlugFromHeader" and insert the following code:

wdThis.wdGetBettingPoolAppController().getRankList();

4) Save all metadata and deploy the Web Dynpro Application to the J2EE server

What's left ?

Before you can test and see anything in your application, you must insert the match data using the CAF Test UI as described in Part II (make sure you set the default values for the goals to -1). After you have maintained some Matches, you should see the matches in the PredictionView.

Of course I have a lot of ideas, how the application could be extended. Some suggestions from my side:

  • Improve the Web Dynpro UI using additional features. One nice feature would be to include a filter on the PredictionView, which only displays the matches for a given date
  • Write a full blown admin UI, where the administrator can maintain Matches or upload Match master data from a text file
  • ...

So it's up to you - have fun !

Raphael


Viewing all articles
Browse latest Browse all 11

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>