Displaying a model in the View, using the JSTL

This recipe shows how to populate the Spring MVC View with data and how to render this data within the View.

Getting ready

At this point, we don't have any real data to be displayed in our View. For this purpose, we have created three DTOs and two service layers that are injected from their Interface into the controller.

There are two dummy service implementations that are designed to produce a fake set of data. We will use the Java Server Tags Library (JSTL) and the JSP Expression Language (JSP EL) to render the server data in the right places in our JSP.

How to do it...

  1. After checking out the v2.x.x branch (in the previous recipe), a couple of new components are now showing-up in the cloudstreetmarket-core module: two interfaces, two implementations, one enum, and three DTOs. The code is as follows:
    public interface IMarketService {
      DailyMarketActivityDTO getLastDayMarketActivity(String string);
      List<MarketOverviewDTO> getLastDayMarketsOverview();
    }
        public interface ICommunityService {
        List<UserActivityDTO> getLastUserPublicActivity(int number);
    }

    As you can see they refer to the three created DTOs:

    public class DailyMarketActivityDTO {
      String marketShortName;
      String marketId;
      Map<String, BigDecimal> values;
      Date dateSnapshot;
      ... //and constructors, getters and setters
    }
    public class MarketOverviewDTO {
      private String marketShortName;
      private String marketId;
      private BigDecimal latestValue;
      private BigDecimal latestChange;
      ... //and constructors, getters and setters
    }
    public class UserActivityDTO {
      private String userName;
      private String urlProfilePicture;
      private Action userAction;
      private String valueShortId;
      private int amount;
      private BigDecimal price;
      private Date date;
      ... //and constructors, getters and setters
    }

    This last DTO refers to the Action enum:

    public enum Action {
      BUY("buys"), SELL("sells");
      private String presentTense;
        Action(String present){
      presentTense = present;
      }
        public String getPresentTense(){
        return presentTense;
      }
    }

    Also, the previously created DefaultController in cloudstreetmarket-webapp has been altered to look like:

    @Controller
    public class DefaultController {
      @Autowired
      private IMarketService marketService;
      @Autowired
      private ICommunityService communityService;
      @RequestMapping(value="/*", method={RequestMethod.GET,RequestMethod.HEAD})
      public String fallback(Model model) {
        model.addAttribute("dailyMarketActivity", marketService.getLastDayMarketActivity("GDAXI"));
        model.addAttribute("dailyMarketsActivity", marketService.getLastDayMarketsOverview());
        model.addAttribute("recentUserActivity", communityService.getLastUserPublicActivity(10));
        return "index";
      }
    }

    And there are the two dummy implementations:

    @Service
    public class DummyMarketServiceImpl implements IMarketService {
        private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
        public DailyMarketActivityDTO 
        getLastDayMarketActivity(String string){
        Map<String, BigDecimal> map = new HashMap<>();
        map.put("08:00", new BigDecimal(9523));
        map.put("08:30", new BigDecimal(9556));
        ...
        map.put("18:30", new BigDecimal(9758));
        LocalDateTime ldt = LocalDateTime.parse("2015-04-10 17:00", formatter);
        return new DailyMarketActivityDTO("DAX 30","GDAXI", map, Date.from(ldt.toInstant(ZoneOffset.UTC)));
      }
        @Override
      public List<MarketOverviewDTO> getLastDayMarketsOverview() {
          List<MarketOverviewDTO> result = Arrays.asList(
          new MarketOverviewDTO("Dow Jones-IA", "DJI", new BigDecimal(17810.06), new BigDecimal(0.0051)),
          ...
          new MarketOverviewDTO("CAC 40", "FCHI", new BigDecimal(4347.23), new BigDecimal(0.0267))
        );
        return result;
      }
    }
      @Service
    public class DummyCommunityServiceImpl implements ICommunityService {
      private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
        public List<UserActivityDTO> getLastUserPublicActivity(int number){
          List<UserActivityDTO> result = Arrays.asList(
          new UserActivityDTO("happyFace8", "img/young-lad.jpg", Action.BUY, "NXT.L", 6, new BigDecimal(3), LocalDateTime.parse("2015-04-10 11:12", formatter)),
          ...
           new UserActivityDTO("userB", null, Action.BUY, "AAL.L", 7, new BigDecimal(7), LocalDateTime.parse("2015-04-10 13:29", formatter))
          );
        return result;
      }
    }

    The index.jsp has been altered with the addition of the following section below the graph container:

    <div class='morrisTitle'>
      <fmt:formatDate value="${dailyMarketActivity.dateSnapshot}" pattern="yyyy-MM-dd"/>
    </div>
    <select class="form-control centeredElementBox">
      <option value="${dailyMarketActivity.marketId}"> ${dailyMarketActivity.marketShortName}
      </option>
    </select> 

    The market overview table, especially the body, has been added:

    <c:forEach var="market" items="${dailyMarketsActivity}">
      <tr>
        <td>${market.marketShortName}</td>
        <td style='text-align: right'>
          <fmt:formatNumber type="number" maxFractionDigits="3" value="${market.latestValue}"/>
      </td>
       <c:choose>
          <c:when test="${market.latestChange >= 0}">
          <c:set var="textStyle" scope="page" value="text-success"/>
          </c:when>
          <c:otherwise>
            <c:set var="textStyle" scope="page" value="text-error"/>
          </c:otherwise>
        </c:choose>        
          <td class='${textStyle}' style='text-align: right'>
            <b><fmt:formatNumber type="percent" maxFractionDigits="2" value="${market.latestChange}"/>
        </b>
      </td>
      </tr>
    </c:forEach>

    The container for the community activity has been added:

    <c:forEach var="activity" items="${recentUserActivity}">
       <c:choose>
         <c:when test="${activity.userAction == 'BUY'}">
           <c:set var="icoUpDown" scope="page" value="ico-up-arrow actionBuy"/>
         </c:when>
         <c:otherwise>
           <c:set var="icoUpDown" scope="page" value="ico-down-	arrow actionSell"/>
         </c:otherwise>
        </c:choose>
        <c:set var="defaultProfileImage" scope="page" value=""/>
        <c:if test="${activity.urlProfilePicture == null}">
        <c:set var="defaultProfileImage" scope="page" value="ico-user"/>
        </c:if>
      <li>
      <div class="itemTitle">
        <div class="listUserIco ${defaultProfileImage}">
          <c:if test="${activity.urlProfilePicture != 	null}">
        <img src='${activity.urlProfilePicture}'>
    </c:if>
    </div>
      <span class="ico-white ${icoUpDown} listActionIco"></span>
    <a href="#">${activity.userName}</a> 
    ${activity.userAction.presentTense} ${activity.amount} 
      <a href="#">${activity.valueShortId}</a> 
      at $${activity.price}
        <p class="itemDate">
          <fmt:formatDate value="${activity.date}" pattern="dd/MM/yyyy hh:mm aaa"/>
        </p>
        </div>
      </li>
    </c:forEach>

    At the bottom of the file, a hardcoded set of JavaScript data is now populated from the server:

    <script>
      var financial_data = [];
      <c:forEach var="dailySnapshot" items="${dailyMarketActivity.values}">
      financial_data.push({"period": '<c:out value="${dailySnapshot.key}"/>', "index": <c:out value='${dailySnapshot.value}'/>});
      </c:forEach>
    </script>
    <script>
      $(function () {
        Morris.Line({
          element: 'landingGraphContainer',
          hideHover: 'auto', data: financial_data,
          ymax: <c:out value="${dailyMarketActivity.maxValue}"/>,
          ymin: <c:out value="${dailyMarketActivity.minValue}"/>,
          pointSize: 3, hideHover:'always',
          xkey: 'period', xLabels: 'month',
          ykeys: ['index'], postUnits: '',
          parseTime: false, labels: ['Index'],
          resize: true, smooth: false,
          lineColors: ['#A52A2A']
        });
    });
    </script>

How it works...

These changes don't produce fundamental UI improvements but they shape the data supply for our View layer.

The approach to handle our data

We are going to review here the server side of the data-supply implementation.

Injection of services via interfaces

Forecasting application needs to feed the frontpage in dynamic data, the choice has been made to inject two service layers marketService and communityService into the controller. The problem was that we don't yet have a proper Data Access layer. (This will be covered in Chapter 4, Building a REST API for a Stateless Architecture!). We need the controller to be wired to render the front page though.

Wiring the controller needs to be loosely coupled to its service layers. With the idea of creating dummy Service implementations in this chapter, the wiring has been designed using interfaces. We then rely on Spring to inject the expected implementations in the service dependencies, typed with the relevant Interfaces.

@Autowired
private IMarketService marketService;
@Autowired
private ICommunityService communityService;

Note the types IMarketService and ICommunityService, which are not DummyCommunityServiceImpl nor DummyMarketServiceImpl. Otherwise, we would be tied to these types when switching to real implementations.

How does Spring choose the dummy implementations?

It chooses these implementations in the cloudstreetmarket-core Spring context file: csmcore-config.xml. We have defined the beans earlier:

<context:annotation-config/>
<context:component-scan base-package="edu.zipcloud.cloudstreetmarket.core" />  

Spring scans all the types matching the root package edu.zipcloud.cloudstreetmarket.core to find stereotypes and configuration annotations.

In the same way that DefaultController is marked with the @Controller annotation, our two dummy implementation classes are marked with @Service, which is a Spring Stereotype. Among the detected stereotypes and beans, the dummy implementations are the only ones available for the injection configuration:

@Autowired
private IMarketService marketService;
  
@Autowired
private ICommunityService communityService;

With only one respective match per field, Spring picks them up without any extra-configuration.

DTOs to be used in View layer

We have made use of DTOs for the variables fetched in our JSPs. Exposed DTOs can be particularly useful in web services when it comes to maintaining several versions simultaneously. More generally, DTOs are implemented when the target and destination objects differ significantly.

We will implement Entities later. It is better not to make use of these Entities in the rendering or version-specific logic, but instead defer them to a layer dedicated to this purpose.

Although, it must be specified that creating a DTO layer produces a fair amount of boilerplate code related to type conversion (impacting both sides, other layers, tests, and so on).

Dummy service implementations

The DummyMarketServiceImpl implementation with the getLastDayMarketActivity method builds an activity map (made of static daily times associated to values for the market, the index). It returns a new DailyMarketActivityDTO instance (built from this map), it is in the end a wrapper carrying the daily activity for one single market or Index such as DAX 30.

The getLastDayMarketsOverview method returns a list of MarketOverviewDTOs also constructed from hardcoded data. It emulates an overview of daily activities for a couple of markets (indices).

The DummyCommunityServiceImpl implementation with its getLastUserPublicActivity method returns a list of instantiated UserActivityDTO, which simulates the last six logged user activities.

Populating the Model in the controller

Presenting the possible method-handler arguments in the first recipe of this chapter, we have seen that it can be injected-as-argument a Model. This Model can be populated with data within the method and it will be transparently passed to the expected View.

That is what we have done in the fallback method-handler. We have passed the three results from the Service layers into three variables dailyMarketActivity, dailyMarketsActivity, and recentUserActivity so they can be available in the View.

Rendering variables with the JSP EL

The JSP Expression Language allows us to access application data stored in JavaBeans components. The notation ${…} used to access variables such as ${recentUserActivity} or ${dailyMarketActivity.marketShortName} is typically a JSP EL notation.

An important point to remember when we want to access the attributes of an object (like marketShortName for dailyMarketActivity) is that the object class must offer JavaBeans standard getters for the targeted attributes.

In other words, dailyMarketActivity.marketShortName refers in the MarketOverviewDTO class to an expected:

public String getMarketShortName() {
  return marketShortName;
}

Implicit objects

The JSP EL also offers implicit objects, usable as shortcuts in the JSP without any declaration or prepopulation in the model. Among these implicit objects, the different scopes pageScope, requestScope, sessionScope, and applicationScope reflect maps of attributes in the related scope.

For example, consider the following attributes:

request.setAttribute("currentMarket", "DAX 30");
request.getSession().setAttribute("userName", "UserA");
request.getServletContext().setAttribute("applicationState", "FINE");

These could respectively be accessed in the JSP with:

${requestScope["currentMarket"]}
${sessionScope["username"]}
${applicationScope["applicationState"]}

Other useful implicit objects are the map of request headers: header (that is, ${header["Accept-Encoding"]}), the map of request cookies: cookies (that is, ${cookie["SESSIONID"].value}), the map of request parameters: param (that is, ${param["paramName"]}) or the map of context initialization parameters (from web.xml) initParam (that is, ${initParam["ApplicationID"]}).

Finally, the JSP EL provides a couple of basic operators:

  • Arithmetic: +, - (binary), *, / and div, % and mod, - (unary).
  • Logical: and, &&, or, ||, not, !.
  • Relational: ==, eq, !=, ne, <, lt, >, gt, <=, ge, >=, le.

Comparisons can be made against other values, or against Boolean, String, integer, or floating point literals.

  • Empty: The empty operator is a prefix operation that can be used to determine whether a value is null or empty.
  • Conditional: A ? B : C.

Evaluate B or C, depending on the result of the evaluation of A.

This description of operators comes from the JavaEE 5 tutorial.

Rendering variables with the JSTL

The JSP Standard Tag Library (JSTL) is a collection of tools for JSP pages. It is not really a brand new feature of Java web but it is still used.

The tags the most used are probably Core and I18N when we need a display logic, or when we need to format data or to build a hierarchy in the View layer.

Area

Function

Tags

Description

Core

Variable support

c:set

c:remove

Set/unset a variable from a scope.

Flow control

c:choose

c:when

c:otherwise

Implements a conditional block IF/THEN/ELSE.

c:if

Implements a conditional IF block.

c:forEach

Iterates over collection types.

c:forTokens

Iterates over tokens, separated by provided delimiters.

URL management

c:import

c:param

Resolves a URL, imports its content into a page, a variable (var) or a variable reader (varReader). Can pass parameters to the underlying resource with param.

c:redirect

c:param

Redirects to a URL. Can pass parameters.

c:url

c:param

Creates a URL. Can assign parameters.

Miscellaneous

c:catch

Catches any throwable that happens in its block.

c:out

Fetches an expression or a variable.

I18N

Setting Locale

fmt:setLocale

fmt:requestEncoding

Stores a Locale in a specific scope.

Sets the Encoding type for the HTTP requests of the page.

Messaging

fmt:bundle

fmt:message

fmt:param

fmt:setBundle

Sets bundles for a specific tag or scope.

Retreives a message, output its content, pass optional parameters.

Number and Date Formatting

fmt:formatNumber

fmt:formatDate

fmt:parseDate

fmt:parseNumber

fmt:setTimeZone

fmt:timeZone

Outputs different contents in different formats. Parse dates and number.

Sets timezone for a specific tag or scope.

These presented tags are not the only capabilities of the JSTL, visit the Java EE tutorial for more details:

http://docs.oracle.com/javaee/5/tutorial/doc/bnakc.html

Taglib directives in JSPs

If we plan to make use of one or the other of the above tags, we first need to include the suited directive(s) in the JSP page:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>

There's more...

More about JSP EL

There is more features covered by the JSP EL. Feel free to read the Oracle tutorials such as http://docs.oracle.com/javaee/5/tutorial/doc/bnahq.html.

More about the JavaBeans standard

We have talked about the expected JavaBean standard when using the JSP EL. More information about JavaBeans can be found in the Oracle tutorial again:

http://docs.oracle.com/javaee/5/tutorial/doc/bnair.html

More about the JSTL

As announced, you can discover more modules of the JSTL on the Java EE tutorial:

http://docs.oracle.com/javaee/5/tutorial/doc/bnakc.html

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset