User Tools

Site Tools


acq:developers

This page is obsolete. OfBiz is not part of current developments.

Acquisitions/Serials - notes for developers

OFBiz/opentaps is huge, but lots of organizations use it and there is a lot of material online. A particularly useful document is Zen and the Art of Debugging OFBiz, especially the section on log files. The Hello World example gives a good sense of how OFBiz works. David Jones' introductory video is also very helpful.

We have made some changes to the layout of OFBiz, nothing complex in a technical sense, but it is well worth touching base with us before embarking on a developer's journey. On the other hand, downloading it directly to see how it works is still a valid option.

In general, the main objects of most changes do not require a restart:

There is a cache that often needs to be cleared in Web Tools when making modifications, but this is still a much faster and more convenient option than waiting for a tomcat restart.

Up close: modifying the Purchase Order screen

OFBiz applications are made up of many small pieces. Although some developers use OFBiz as a general purpose framework, its value is clearly far more as a ready source of financial functions and data models (see Django or Ruby on Rails if you are looking for a framework to build something from scratch).

The example that might be the most helpful is both intricate (in terms of layout) and trivial (in terms of actual code), and that is using OpenSRF to bring records from the Evergreen catalogue into the Purchase Order screen:

PO Screen

As you may have guessed, the "Evergreen Bib Lookup" option has been a customized addition to this screen. In order to see how this was done, we go to the key to any interaction, which is the URL, in this case:

https://localhost:8443/ordermgr/control/setOrderCurrencyAgreementShipDates

We will ignore the hostname and port, and focus on the rest of the url:

/ordermgr/control/setOrderCurrencyAgreementShipDates

The first part of the address is the mount-point, and this is specified in the ofbiz-component.xml associated with the application. Since the application in this case is related to ordering, the ofbiz-component.xml file we want is in located in the subdirectory applications/order.

ofbiz-component.xml

And, sure enough, in this file, we find the entry point for ordermgr:

<webapp name="order"
      title="Order"
      server="default-server"
      location="webapp/ordermgr"
      base-permission="OFBTOOLS,ORDERMGR"
      mount-point="/ordermgr"/>

This tells us that the building blocks of ordermgr are in the subdirectory webapp/ordermgr.

ordermgr dir listing

The Role of the Controller

The file that represents the switchboard for URL requests is controller.xml, which is always located in the WEB-INF subdirectory. The control part of the URL identifies the servlet involved and confirms that this interaction is under a controller, and we look for the section that describes what happens when setOrderCurrencyAgreementShipDates is requested.

    <request-map uri="setOrderCurrencyAgreementShipDates">
          <description>
                Handles setting the currency, agreement and shipment dates of an order.
          </description>
          <security https="true" auth="true"/>
          <event type="java" path="org.ofbiz.order.shoppingcart.ShoppingCartEvents"
                invoke="setOrderCurrencyAgreementShipDates"/> 
          <response name="success" type="request" value="orderentry"/>
          <response name="error" type="request" value="orderagreements"/>
    </request-map> 

This tells us a bit about what this request is responsible for, and what action should take place. In this case, a java action is invoked (setOrderCurrencyAgreementShipDates), and the action can either return a value indicating success or error. Generally speaking, this is how you would normally weave a java interaction into an application, and looking at the source of the ShoppingCartEvents class reveals the java contortions that can be mixed in. However, let's assume that the action is successful and skip to orderentry. This also maps to a request, and is specified in controller.xml.

    <request-map uri="orderentry">
          <security https="true" auth="true"/>
          <event type="java" path="org.ofbiz.order.shoppingcart.ShoppingCartEvents" 
                invoke="routeOrderEntry"/>
          <response name="init" type="view" value="checkinits"/>
          <response name="agreements" type="view" value="orderagreements"/>
          <response name="cart" type="view" value="showcart"/>
          <response name="error" type="view" value="checkinits"/>
    </request-map>

Again, more java plumbing is invoked, but this time there are 4 possible outcomes. It is possible to glean the jumping off point from the code, but the runtime/logs/console.log also gives a running update on what's happening.

    [        UtilXml.java:246:DEBUG] XML Read 0.061s: jndi:/0.0.0.0/ordermgr/WEB-INF/controller.xml
    [ConfigXMLReader.java:561:INFO ] ConfigMap Created: (4) records in 0.0030s
    [ConfigXMLReader.java:719:INFO ] HandlerMap Created: (4) view handlers and (6) request/event handlers in 0.0s
    [ConfigXMLReader.java:294:INFO ] RequestMap Created: (295) records in 0.016s
    [ConfigXMLReader.java:388:INFO ] ViewMap Created: (129) records in 0.0010s
    [        UtilXml.java:246:DEBUG] XML Read 0.046s: file:opentaps-1.0-dist/applications/order/widget/ordermgr/OrderViewScreens.xml
    [  ScreenFactory.java:121:INFO ] Got 19 screens in 0.084s from: file:opentaps-1.0-dist/applications/order/widget/ordermgr/OrderViewScreens.xml
    [        UtilXml.java:246:DEBUG] XML Read 0.03s: file:opentaps-1.0-dist/applications/order/widget/ordermgr/CommonScreens.xml
    [  ScreenFactory.java:121:INFO ] Got 4 screens in 0.036s from: file:opentaps-1.0-dist/applications/order/widget/ordermgr/CommonScreens.xml

This is where things start getting interesting, and we can start to see how the screen, a snippet of HTML for display, is coming together. Each response in controller.xml has a type, and in this case, the type is view. Peeking inside OrderViewScreens.xml, for example, shows how screens can be defined.

    <screen name="minicart">
          <section>
                <actions>
                      <set field="hidetoplinks" value="Y"/>
                      <set field="hidebottomlinks" value="Y"/>
                </actions>
                <widgets>
                      <platform-specific>
                            <html>
                                  <html-template 
                                        location="component://order/webapp/ordermgr/entry/cart/minicart.ftl"/>
                            </html>
                      </platform-specific>
                </widgets>
          </section>
    </screen>

In this case, minicart.ftl is an Freemaker template file, and the component designation indicates a directory relative to the applications directory, so that the source file can be found in applications/order/webapp/ordermgr/entry/cart. Freemaker is similar to other templating engines, and brings together what should normally be a thin layer of business logic with presentation options.

    <div class="screenlet">
          <div class="screenlet-header">
                <div class='boxhead'><b>${uiLabelMap.EcommerceCartSummary}</b></div>
          </div>
          <div class="screenlet-body">
                <#if (shoppingCartSize > 0)>
                      <#if hidetoplinks?default("N") != "Y">
                            <div><a href="<@ofbizUrl>view/showcart</@ofbizUrl>" class="buttontext">
                                  ${uiLabelMap.EcommerceViewCart}</a>&nbsp;
                                  <a href="<@ofbizUrl>checkoutoptions</@ofbizUrl>" class="buttontext">
                                        ${uiLabelMap.EcommerceCheckout}
                                  </a>
                            </div>
                      </#if>
                </#if>
          </div>
    </div>

Launching from a Button

To add an Evergreen lookup, we modify the showcart.ftl file, which contains the layout of the portion of the screen we are targeting.

    <a href="javascript:quicklookupevergreen_popup(document.quickaddform.add_product_id)"
          class="buttontext">${uiLabelMap.OrderQuickEvergreenLookup}
    </a>

Note the use of uiLabelMap, it is a common mechanism for keeping labels and other textual content out of the templates. The button initiates a snippet of javascript:

    function quicklookupevergreen_popup(element) {
          target = element;
          var searchTerm = element.value;
          var obj_lookupwindow = window.open('/woodchip/control/ils_bib_search?foo=' 
                + searchTerm,'FieldLookup',
                'width=700,height=550,scrollbars=yes,status=no,resizable=yes,top='+my+',
                left='+mx+',dependent=yes,alwaysRaised=yes');
          obj_lookupwindow.opener = window;
          obj_lookupwindow.focus();
    }

Once again, the URL invoked is the key construct in making something useful happen, in this case:

/woodchip/control/ils_bib_search

Thanks to the wizardry of Bill Erickson, this produces a search screen that retrieves records from the catalogue:

PO Screen

We would probably want to transfer much more information to Desiderata, but let's implement a very simple mechanism to add the record id and the title, and encapsulate this into the URL:

/catalog/control/EditProduct?record_id=5&title=Harry%20Potter%20and%20the%20deathly%20hallows

Once again, the ofbiz-component.xml file reveals the entry point for catalog:

<webapp name="catalog" 
      title="Catalog" 
      server="default-server" 
      location="webapp/catalog"
      base-permission="OFBTOOLS,CATALOG" 
      mount-point="/catalog"/>

No surprises here, and the WEB-INF/controller.xml tells us that a view is produced when this request arrives:

<request-map uri="EditProduct">
  <security https="true" auth="true"/>
  <response name="success" type="view" value="EditProduct"/>
</request-map>

and, in turn, the view is contained inside an XML file called ProductScreens.xml:

<view-map name="EditProduct" type="screen" 
  page="component://product/widget/catalog/ProductScreens.xml#EditProduct"/>

Note the syntax, this indicates that section we want in ProductScreens.xml is called EditProduct, and we can focus on that part of the screen definition:

<screen name="EditProduct">
  <section>
    <actions>
      <set field="titleProperty" value="PageTitleEditProduct"/>
      <set field="tabButtonItem" value="EditProduct"/>
      <set field="labelTitleProperty" value="ProductProduct"/>
      <set field="productId" from-field="parameters.productId"/>
      <script location="component://product/webapp/catalog/WEB-INF/actions/bibadd.bsh"/>
      <entity-one entity-name="Product" value-name="product"/>
    </actions>
    <widgets>
    <decorator-screen name="CommonProductDecorator" location="${parameters.mainDecoratorLocation}">
      <decorator-section name="body">
        <include-form name="EditProduct" 
          location="component://product/webapp/catalog/product/ProductForms.xml"/>
          <!-- include the duplicate product form template -->
          <platform-specific>
            <html>
              <html-template location="component://product/webapp/catalog/product/EditProductDupForm.ftl"/>
            </html>
          </platform-specific>
        </decorator-section>
      </decorator-screen>
    </widgets>
  </section>
</screen>

There is a lot going on here, but our addition is quite simple:

<script location="component://product/webapp/catalog/WEB-INF/actions/bibadd.bsh"/>

This syntax indicates that we want to run a beanshell script called bibadd.bsh, and this is where we specify what values we want for the fields in the resulting form:

import org.ofbiz.base.util.*;
import org.ofbiz.entity.*;
// simple for now, these variables could be passed directly
recordId = request.getParameter("record_id");
title = request.getParameter("title");
if (recordId != null)
  context.put("productId", recordId);
if (title != null)
  context.put("internalName", title);

There are several options for the kind of scripts that can be invoked, but beanshell has the advantage that it can leverage the Java APIs. Not very significant in this example, yet this often very handy for using Java for some quick heavy lifting while retaining a light scripting layer to pull the pieces together. In this case, the resulting form should show the values we transferred from the catalogue.

Transferred Bib Info Screen

This is a trivial example, but it hopefully illustrates one general approach to modifying OFBiz/opentaps.

acq/developers.txt · Last modified: 2009/10/23 15:14 by jatzberger

© 2008-2017 GPLS and others. Evergreen is open source software, freely licensed under GNU GPLv2 or later.
The Evergreen Project is a member of Software Freedom Conservancy.