AJAX-enhanced drag and drop

The user's client-side drag and drop interactions can be posted to the server. Drag and drop has only one (default) AJAX behavior event provided by the droppable component, which is processed when a valid draggable component is dropped. That is the drop event. If we define a listener, it will be invoked by passing an event instance of the type org.primefaces.event.DragDrop as parameter. This parameter holds information about the dragged and dropped components. Through this information, the server-side state of the draggable/droppable items can be updated.

In this recipe, we will develop a workflow simulating the process of pizza ordering. The pizza ordering should occur by drag and drop. Users should be able to select any available Turkish pizza and drag and drop it onto the order list. The remove functionality, capable of drag and drop, should be included as well. For this purpose, we will implement a trash for the items removed from the pizza items in the order list.

How to do it…

The following screenshots demonstrate the entire workflow:

How to do it…

The first screenshot shows the dragging process from the list of available pizzas to the order list.

How to do it…

The second screenshot shows what happens when the dragged pizza image is dropped into the order list. A growl component is displayed with the currently selected pizza name.

How to do it…

The last screenshot demonstrates the removal process. One pizza has been dragged from the order list and dropped into the trash list.

We will make the five pizza image tags h:graphicImage draggable.

<p:growl id="growl" escape="false"/>

<h:panelGrid id="selectPizza" columns="1">
  <h:outputText value="Kiymali Pide" styleClass="text"/>
  <h:graphicImage id="pizza1" styleClass="pizzaimage"
    library="images" name="dragdrop/pizza1.png"
    title="Kiymali Pide"/>

  <h:outputText value="Kusbasi Pide" styleClass="text"/>
  <h:graphicImage id="pizza2" styleClass="pizzaimage"
    library="images" name="dragdrop/pizza2.png"
    title="Kusbasi Pide"/>

  <h:outputText value="Sucuklu Ve Yumurtali Pide" styleClass="text"/>
  <h:graphicImage id="pizza3" styleClass="pizzaimage"
    library="images" name="dragdrop/pizza3.png"
    title="Sucuklu Ve Yumurtali Pide"/>

  <h:outputText value="Peynirli Pide" styleClass="text"/>
  <h:graphicImage id="pizza4" styleClass="pizzaimage"
    library="images" name="dragdrop/pizza4.png"
    title="Peynirli Pide"/>

  <h:outputText value="Ispanakli Pide" styleClass="text"/>
  <h:graphicImage id="pizza5" styleClass="pizzaimage"
    library="images" name="dragdrop/pizza5.png"
    title="Ispanakli Pide"/>
</h:panelGrid>

<p:draggable for="pizza1" helper="clone" revert="true" cursor="move"/>
<p:draggable for="pizza2" helper="clone" revert="true" cursor="move"/>
<p:draggable for="pizza3" helper="clone" revert="true" cursor="move"/>
<p:draggable for="pizza4" helper="clone" revert="true" cursor="move"/>
<p:draggable for="pizza5" helper="clone" revert="true" cursor="move"/>

Two h:panelGroup tags will be made droppable. One h:panelGroup tag is intended to be used for the order list and one is for items removed from the order list. Droppable p:droppable tags will get AJAX behaviors p:ajax attached with corresponding listeners in each case. One listener should be invoked on pizza ordering and another on pizza removal.

<h:panelGroup id="order" layout="block" styleClass="ui-widget-content"
  style="width:350px; padding:1px;">
  <p class="ui-widget-header" style="margin:0;padding:5px;">
    Order
  </p>

  <h:panelGroup layout="block" style="padding:10px;"
    rendered="#{empty ajaxDragDrop.orderedPizza}">
    Please drag and drop any available pizza to order it
  </h:panelGroup>

  <p:dataList id="orderedPizza"
    value="#{ajaxDragDrop.orderedPizza}" var="op"
    rendered="#{not empty ajaxDragDrop.orderedPizza}">
    <h:panelGroup id="op" styleClass="text" layout="block">
      <f:attribute name="pizza" value="#{op}"/>
      <h:outputText value="#{op}"/>
    </h:panelGroup>

    <p:draggable for="op" revert="true" cursor="move" scope="trash"/>
  </p:dataList>

  <p:droppable id="drop1" for="order" accept=".pizzaimage"
    tolerance="touch" activeStyleClass="ui-state-default"
    hoverStyleClass="ui-state-hover">
    <p:ajax listener="#{ajaxDragDrop.onPizzaOrder}"
      update="order growl"/>
  </p:droppable>
</h:panelGroup>

<p:commandButton value="Send order" action="#{ajaxDragDrop.sendOrder}"
  update="growl" style="margin:10px 0 20px 0;"/>

<h:panelGroup id="trash" layout="block" styleClass="ui-widget-content"
  style="width:350px; padding:1px;">
  <p class="ui-widget-header" style="margin:0;padding:5px;">Trash</p>

  <h:panelGroup layout="block" style="padding:10px;"
    rendered="#{empty ajaxDragDrop.removedPizza}">
    Drag and drop a pizza from the ordered list to remove it
  </h:panelGroup>

  <p:dataList value="#{ajaxDragDrop.removedPizza}" var="rp"
    rendered="#{not empty ajaxDragDrop.removedPizza}">
    <h:panelGroup styleClass="text" layout="block">
      <h:outputText value="#{rp}"/>
    </h:panelGroup>
  </p:dataList>

  <p:droppable id="drop2" for="trash" scope="trash" tolerance="touch"
    activeStyleClass="ui-state-default"
    hoverStyleClass="ui-state-hover">
    <p:ajax listener="#{ajaxDragDrop.onPizzaRemove}"
      update="order trash growl"/>
  </p:droppable>
</h:panelGroup>

The corresponding CDI bean AjaxDragDrop adds an ordered pizza to the orderedPizza list, and moves the pizza to the removedPizza list when it gets removed. This happens in the listeners onPizzaOrder and onPizzaRemove, respectively.

@Named
@ViewScoped
public class AjaxDragDrop implements Serializable {

  private List<String> orderedPizza = new ArrayList<String>();
  private List<String> removedPizza = new ArrayList<String>();

  public List<String> getOrderedPizza() {
    return orderedPizza;
  }

  public List<String> getRemovedPizza() {
    return removedPizza;
  }

  public void onPizzaOrder(DragDropEvent event) {
    HtmlGraphicImage image = (HtmlGraphicImage) event.
      getComponent().findComponent(event.getDragId());
    String pizza = image != null ? image.getTitle() : "";

    orderedPizza.add(pizza);

    FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_INFO,
      "Selected pizza: " + pizza, null);
    FacesContext.getCurrentInstance().addMessage(null, msg);
  }

  public void onPizzaRemove(DragDropEvent event) {
    DataList dataList = (DataList) event.
      getComponent().findComponent("orderedPizza");

    FacesContext fc = FacesContext.getCurrentInstance();
    dataList.invokeOnComponent(fc, event.getDragId(),
      new ContextCallback() {
      public void invokeContextCallback(FacesContext fc,
        UIComponent comp) {
          HtmlPanelGroup pGroup = (HtmlPanelGroup)comp;
          String pizza = pGroup != null ?
            (String) pGroup.getAttributes().get("pizza") :
            "";

          orderedPizza.remove(pizza);
          removedPizza.add(pizza);

          FacesMessage msg = new FacesMessage(
            FacesMessage.SEVERITY_INFO,
            "Removed pizza: " + pizza, null);
          fc.addMessage(null, msg);
        }
    });
  }

  public String sendOrder() {
    StringBuilder sb = new StringBuilder("You have ordered:");
    for (String pizza : orderedPizza) {
      sb.append("<br/>");
      sb.append(pizza);
    }

    FacesMessage msg = new FacesMessage(
      FacesMessage.SEVERITY_INFO, sb.toString(), null);
    FacesContext.getCurrentInstance().addMessage(null, msg);

    return null;
  }
}

How it works…

To make h:graphicImage draggable, we use p:draggable with proper options: helper="clone", revert="true", and cursor="move". The draggable images have the title attributes set to the pizza names. This is important for getting the dropped pizza's name in the onPizzaOrder listener by means of the findComponent() call. The draggable h:panelGroup tag in the order list has, in contrast to h:graphicImage, f:attribute with the pizza name as the value. This allows us to get the dropped pizza's name from the component's attribute map in the onPizzaRemove listener by means of the invokeOnComponent() call. Client IDs of draggable/droppable components can be accessed by getDragId() or getDropId() on a DragDropEvent instance.

Note

Refer to the JSF 2 API documentation (http://javaserverfaces.java.net/nonav/docs/2.2/javadocs/javax/faces/component/UIComponent.html) to read more about findComponent() and invokeOnComponent().

Last but not least, we use different ways to accept draggable. In the case of images, we set accept to .pizzaimage. The accept attribute defines a jQuery selector for the accepted draggable components. In the case of items in the order list, we set scope to trash. The scope attribute is an alternative way to match the droppable and accepted draggable components. What is easier to use in each particular case depends on the code.

There's more…

We used two style classes with p:droppable:

  • activeStyleClass set to ui-state-default
  • hoverStyleClass set to ui-state-hover

They are used for better visual effects when dragging/dropping. If activeStyleClass is specified, the class will be added to the droppable component while an acceptable draggable component is being dragged. If hoverStyleClass is specified, the class will be added to the droppable component while an acceptable draggable component is being dragged over it.

PrimeFaces Cookbook Showcase application

This recipe is available in the demo web application on GitHub (https://github.com/ova2/primefaces-cookbook/tree/second-edition). Clone the project if you have not done it yet, explore the project structure, and build and deploy the WAR file on every Servlet 3.x compatible application server, such as JBoss WildFly or Apache TomEE.

The showcase for the recipe is available at http://localhost:8080/pf-cookbook/views/chapter8/ajaxDragDrop.jsf.

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

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