Статьи

Webflow + Roo Again – более сложный пример …

Кто-нибудь хочет больше информации о Roo и Webflow?

Конечно, вы все делаете …

Прежде чем начать, я должен рассказать короткую историю. Я написал этот пример, потому что я не видел хороших примеров для более нового синтаксиса вызова службы webflow, управляемого конвенциями. Я также хотел посмотреть, как будут выглядеть пустые страницы JSPX веб-потока с новыми тегами.

Мне также нужен был хороший пример для главы 6 Roo in Action, которая охватывает WebFlow, GWT и Flex. Чтобы убедиться, что я все понял правильно, я собрал эту работу в процессе.

После того, как я закончил, я выполнил поиск в Google, чтобы узнать, делал ли кто-либо еще образцы, подобные моему, что является простой (не полной) корзиной для покупок. Оказывается, Вилли Уилер написал очень хороший пример корзины для покупок еще в 2008 году. Я призываю вас прочитать этот пример , который гораздо более всеобъемлющий, чем мой.

Тем не менее, это пример Roo + WebFlow, так что я думаю, что пример все еще вполне допустим.

Пример — корзина покупок

Да, да, да, тележки для покупок. У всех они есть, а у меня более хромые! Но так как меня больше интересует механика WebFlow, чем совершенство прецедента, давайте просто примем, что мой пример является очень элементарным.

Мы просто собираемся жестко закодировать три продукта и позволить пользователям добавлять количество каждого из них в вымышленную корзину. Мы также позволим пользователям удалять элементы из корзины. Более поздние фазы веб-потока остаются незакодированными; это только руководство по началу работы.

Установка WebFlow с помощью Roo

Это самая простая часть — просто откройте оболочку Roo в существующем проекте и введите

web flow

Это установит всю поддержку Web Flow и удалит образец потока в каталог WEB-INF / views / sampleflow. Вы можете проверить это, чтобы почувствовать механику, но вот основы:

  • Веб-потоки являются конечными автоматами на основе XML. Каждый пользователь взаимодействует с веб-потоком в миниатюрном сеансе, который называется контекстом потока.
  • Потоки состоят из состояний и переходов.
  • Потоки могут хранить информацию между запросами в различных областях, включая область действия, которая существует до тех пор, пока пользователь не выйдет из потока, нажав конечное состояние.
  • Веб-потоки могут выполнять бизнес-логику в нескольких местах
  • Любой Spring Bean в контексте приложения доступен по id
  • Если вы создаете Spring Bean, расширяющий класс WebFlow MultiAction, и называете свои методы определенным образом, вы можете ссылаться на них, не передавая полную подпись.
  • Чтобы инициировать переход, вы отправляете обратно в веб-поток, передавая ему переменную специальной формы (как показано в примерах ниже) с именем перехода.

Поток корзины покупок

Начнем с преамбулы определения потока:

<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">

  <persistence-context />

Мы используем тег постоянства-контекста, чтобы включить отслеживание постоянства JPA. Наш объект JPA, ShoppingCart, содержится в этом постоянном контексте и является нашим объектом формы в различных представлениях. Следует отметить, что если вы хотите использовать сущности в вашем контексте, вы должны сделать их сериализуемыми.

Теперь мы начинаем поток. Первый этап:

<var name="shoppingCart" class="com.chotchkies.model.ShoppingCart" />

<!-- A sample view state -->
<view-state id="show-cart">
  <transition on="add" to="show-products"/>
  <transition on="checkout" to="end-state"/>
  <transition on="removeItem" to="remove-item">
    <set name="flowScope.productId" value="requestParameters.productId" 
       type="java.lang.Long" />
  </transition>
  <transition on="empty">
    <evaluate expression="cartManager.clearCart" />
  </transition>
</view-state>

Первое, что делает поток — это предварительно создает нашу корневую сущность JPA, ShoppingCart. Это хранится в специальном держателе, называемом областью потока. Поскольку это объект JPA, он автоматически сбрасывается и сохраняется при каждом переходе.

We then render our first view-state, show-cart. WebFlow tries to resolve the view name by looking up the definition in the Tiles view.xml file, located in the flow directory. I’ve replaced a more complex file with wildcard support, which was recently added in the newest Tiles release:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration 2.1//EN" "http://tiles.apache.org/dtds/tiles-config_2_1.dtd">
<tiles-definitions>
    <definition extends="default" name="*">
        <put-attribute name="body" value="/WEB-INF/views/cart/{1}.jspx"/>
    </definition>
</tiles-definitions>

Note — this makes it possible to just drop new files in the flow definition directory, WEB-INF/views/cart, without modifying the views.xml file each time.

looks for a file named show-cart.jspx, within

We have several transitions, or exit paths, from this view-state:

  • add — transition to the show-products view-state
  • checkout — transition to the end-state
  • removeItem — transition to the remove-item state, but first pull the submitted productId and store it in the flowScope as productId
  • empty — a transition without a destination — this executes a method in the object named cartManager called clearCart — which removes the products from the user’s shopping cart.

How do we execute these transitions? Here is a snippet from the button bar at the bottom of the cart page:

<form:form>
  <input type="submit" id="add" name="_eventId_add" value="Add Additional Items..." />
  <input type="submit" id="checkout" name="_eventId_checkout" value="Checkout" />
  <input type="submit" id="empty" name="_eventId_empty" value="Empty Cart" />
</form:form>

You can see the special names — they reference the transition name after the prefix _eventId and an additional underscore. When Web Flow sees these tags, it attempts to perform the transition attached to that tag.

More About Expressions and Convention

When navigating to the show-products state, an on-entry event is triggered, which fires off a call to a Spring Bean, CartManager. Here is the fragment:

<on-entry>
  <evaluate expression="cartManager.getAllProductsNotInCart" />
</on-entry>

The CartManagerImpl class, which extends the WebFlow MultiAction base class, has this method signature for getAllProductsNotInCart:

public Event getAllProductsNotInCart(RequestContext context)

Because it uses this syntax, we don’t need to reference the parameters in the XML definition. Nice touch, eh? This pushes some of the details into the bean itself, but also can simplify the XML definition. Here is the full method:

@Override
public Event getAllProductsNotInCart(RequestContext context) {
  ShoppingCart cart = getCart(context);
  Set<Long> keySet = cart.getItems().keySet();
  List<Product> products = Product.findProductsNotIn(keySet);
  context.getViewScope().asMap().put("productList", products);
  return success();
}

private ShoppingCart getCart(RequestContext context) {
  ShoppingCart cart = (ShoppingCart) context.getFlowScope().get("shoppingCart");
  return cart;
}

This shows a few helpful conventions. First, the RequestContext is a class that provides access to all of the scopes, including flowScope and viewScope. In this case, because each time we show the products we want to re-evaluate whether they are in the cart, we place the information in the viewScope variable. This data is only held while rendering the view.

Our helper method, getCart, shows how to access the flow scope. Remember the shoppingCart variable defined in the var tag at the top of the flow? Yep, it’s accessed using the context.getFlowScope() method.

Wrap-up — the full flow example

Those are some of the key conventions used by Web Flow. I’m working on this sample, which I’ll be adding to a GIT repository soon. For now, here is the rest of the flow, and a complete sample page.

Cart Flow

Notice the end-state — it has a special attribute, commit=true. This makes sure that on exit of the end state, any in-flight JPA changes will be flushed and committed. This is the normal behavior on each step, but when leaving a flow in error, you may wish to have an error end state that does not commit.

<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">

  <persistence-context />

  <var name="shoppingCart" class="com.chotchkies.model.ShoppingCart" />

    <!-- A sample view state -->
    <view-state id="show-cart">
      <transition on="add" to="show-products"/>
      <transition on="checkout" to="end-state"/>
      <transition on="removeItem" to="remove-item">
        <set name="flowScope.productId" value="requestParameters.productId" 
           type="java.lang.Long" />
      </transition>
      <transition on="empty">
        <evaluate expression="cartManager.clearCart" />
      </transition>
    </view-state>

    <action-state id="remove-item">     
      <evaluate expression="cartManager.removeItemFromCart" />
      <transition on="success" to="show-cart" />
    </action-state>

    <view-state id="show-products">
      <on-entry>
        <evaluate expression="cartManager.getAllProductsNotInCart" />
      </on-entry>
      <transition on="select" to="confirm-product">               
        <evaluate expression="cartManager.configureCartItem" />
      </transition>
      <transition on="cancel" to="show-cart" />
    </view-state>

    <view-state id="confirm-product" model="flowScope.currentItem">           
      <transition on="confirm" to="show-cart"/>             
      <transition on="cancel" to="show-cart">
        <evaluate expression="cartManager.removeItemFromCart" />
      </transition>
    </view-state>

  <action-state id="add-product">
    <!-- KJR - todo -->
    <transition to="show-cart"/>
  </action-state>

  <view-state id="enter-address">
    <transition on="continue" to="confirm-shipping"/>
    <transition on="back" to="checkout-cart"/>
  </view-state>

  <view-state id="confirm-shipping">
    <transition on="continue" to="end-state"/>
    <transition on="back" to="enter-address"/>
  </view-state>

  <end-state id="end-state" view="end-state" commit="true"/>

</flow>

The show-products.jspx view

Note the use of the special variable, ${flowExecutionKey}, which represents the proper webflow flow for the server. If you’re writing your own links, and not posting the form, you need to include this. Also note that all scoped variables are ‘just there’ in the context by their names.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<div xmlns:jsp="http://java.sun.com/JSP/Page" 
  xmlns:page="urn:jsptagdir:/WEB-INF/tags/form" 
  xmlns:table="urn:jsptagdir:/WEB-INF/tags/form/fields" 
  xmlns:c="http://java.sun.com/jsp/jstl/core" 
  xmlns:fn="http://java.sun.com/jsp/jstl/functions" 
  xmlns:util="urn:jsptagdir:/WEB-INF/tags/util" 
  xmlns:form="http://www.springframework.org/tags/form" 
  version="2.0">
    <jsp:output omit-xml-declaration="yes"/>
    <h3>Shopping Cart Contents</h3>

    <ul>
      <c:forEach items="${productList}" var="product">
        <li>${product.name} - Price: ${product.price} -
         <c:url var="addUrl" value="">
          <c:param name="productId" value="${product.id}"/>
          <c:param name="_eventId" value="select"/>
          <c:param name="execution" value="${flowExecutionKey}" />
        </c:url>
          <a href="${addUrl}">Add...</a>
        </li>      
      </c:forEach>
    </ul> 

    <form:form>
      <input type="submit" id="cancel" name="_eventId_cancel" value="Cancel and return to cart..." />
    </form:form>

</div>