Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Part 1: In this part, you will learn how to create and load your own component (custom component) and add first screen(view) to show “This is practice application”

Part 2: Going ahead, you will create some advanced GUI patterns, interact with existing database table and will learn how to secure your web application.

Part 3: Here you learn interacting with database entities, you will write services for performing CRUD operations (Create, Retrive, Update, Delete) and event for form fields validation purpose.

Part 4: Now you will learn how to call automatic triggers (service(s)) based on your defined condition(s) entity(EECA)/service based(SECA), calling multiple
services as group and share common parameters of services by implementing/using the concept of interface.

Part 5: In this part of the tutorial you will create custom entity, extend an existing OOTB entity and prepare xml data for your application.

Part 6: At the final step you will ajaxify request to set a communitcation between client and server side application. After completing this application, you will become an OFBiz developer. (smile)

...

  1. Take reference from ExampleMenus.xml file for having login and logout options in your menu.
    Targets for these options will be available from "component://common/webcommon/WEB-INF/common-controller.xml", which we have to include in our controller.xml.
    or you can do these entries in your controller.xml file under
    Code Block
    <!- Request Mappings ->
    <!-- Security Mappings -->
     <request-map uri="checkLogin" edit="false">
        <description>Verify a user is logged in.</description>
            <security https="true" auth="false"/>
            <event type="java" path="org.ofbiz.webapp.control.LoginWorker" invoke="checkLogin" />
            <response name="success" type="view" value="main"/>
            <response name="error" type="view" value="login"/>
        </request-map>
        <request-map uri="login">
            <security https="true" auth="false"/>
            <event type="java" path="org.ofbiz.webapp.control.LoginWorker" invoke="login"/>
            <response name="success" type="view" value="main"/>
            <response name="error" type="view" value="login"/>
        </request-map>
        <request-map uri="logout">
            <security https="true" auth="true"/>
            <event type="java" path="org.ofbiz.webapp.control.LoginWorker" invoke="logout"/>
            <response name="success" type="request" value="checkLogin"/>
            <response name="error" type="view" value="main"/>
        </request-map>
    
    These requests are needed to add in your controller only when you have not included any of the other component controller which consist of these requests. So if you have already included common-controller.xml file then you don't need to explicitly do these entries in your controller.    
    and the same view we have in place can be used for which we have entry in common-controller.xml file we can also have our own:
    Code Block
    <view-map name="login" type="screen" page="component://common/widget/CommonScreens.xml#login"/>
    
  2. Make changes in requests in controller.xml file make auth="true" means now these requests needs authentication.
    This is first security level which you have implemented. you request should look like :
    Code Block
    <request-map uri="main">
        <security https="true" auth="true"/>
        <response name="success" type="view" value="main"/>
        <response name="error" type="view" value="main"/>
    </request-map>
    
    Now run your application and observe the difference. you can login by user name : admin and pwd: ofbizHere we should understand why we had given the permission "OFBTOOLS" in base-permission in ofbiz-component.xml file. To understand this please read following carefully and perform steps as mentioned:
    Confirm that user 'admin' has the 'OFBTOOLS' permission.
    1. Login into partymanager to confirm that the user admin has the required permission https://127.0.0.1:8443/partymgr/control/main
    2. Once your logged in search for the user by typing 'admin' in the User Login field and then clicking the Lookup Party button.
    3.    This does a wild char*  search and you should see multiple users returned.Click the 'Details' button for the admin user.
      Note : Important point to note here is that the person 'Mr. THE PRIVILEGED ADMINISTRATOR' has a partyId admin has multiple login Ids as listed in the
      User Name(s) form.
    4. We interested in the admin user login so click on the 'Security Groups' button and confirm that the use 'admin' is part of the 'FULLADMIN' group. The Groups that the user belongs to is shown in the bottom list form Drill down on the FULLADMIN.
    5. Click on the Permissions tab. This tab shows all the permissions for the FULLADMIN security group. Navigate between the permissions till you find the OFBTOOLS permissions.
      'OFBTOOLS_VIEW Permission to access the Stock OFBiz Manager Applications.' This confirms that the userlogin 'admin' has the permission 'OFBTOOLS'
    6. Take a moment  to review the entity model as it relates to users and permissions. The arrow represents the many side of the relationship.An really important reading at this moment is at : OFBiz Security

Part 3

Writing

...

CRUD Operations

Create, Update and Delete operations for an entity will be done by services which we will be writing in minilang. At first approach we will write our own services for performing these operations for making a better understanding with it. Onwards we will be doing this by calling already implemented services. For doing so we will take the entities from Party model which are:
Party
Person
A person is a party so for the creation of a person record, first a party record needs to be created with partyTypeId="PERSON". So there can be two ways to do that:

...

  1. Create directory by name "servicedef" in component directory "practice". This directory will contain all the service definition files e.g. services.xml, secas.xml.
    Note : *
    If it is a service which is written in Java then it will be placed in "src" directory and if it is a service which is written in minilang then it will be placed in script directory. e.g. for java applications/party/src/org/ofbiz/party/party/PartyServices.java and for minilang applications/party/script/org/ofbiz/party/party/PartyInvitationServices.xml. Respective class path and file path will be mentioned in the service definition.
  2. In controller you have to create an entry for the request for the execution of a service and set the response like.
    Code Block
    <request-map uri="createPracticePerson">
        <security https="true" auth="true"/>
        <event type="service" invoke="createPracticePerson"/>
        <response name="success" type="view" value="PersonForm"/>
    </request-map>
    
  3. Now all the services which you have written needs to be loaded when server starts so you need to do an entry for service definition in ofbiz-component.xml file which will be like:
    Code Block
    <service-resource type="model" loader="main" location="servicedef/services.xml"/>
    
    So whenever you make any change in any service definition then you must restart the server to have changes in effect.

Writing

...

CRUD Operations for Party Entity

First we will be writing services for Party then while writing services for creating Person we will be calling the service for party.

  1. Create a file by name "services.xml" in servicedef directory.
  2. Define services for CRUD operations for Party entity. Name of services will be createPracticeParty, updatePracticeParty, deletePracticeParty and specify the correct location to the file where these services will be implemented like /framework/example/script/org/ofbiz/example/example/ExampleServices.xml.
  3. Create directory structure and PracticeServices.xml file in your component directory for giving the implementation of these services. (For implementation take reference from services.xml and ExampleServices.xml files of Example component)
    Note
    titleImportant
    • Do not use the <override> tag as it is introduced later in the tutorial.
      From this place if you want to run these services then you can run them by webtools--> Run Service . By this place you can test your services.
    • At this place you must read http://markmail.org/message/dj4wvtm4u2nxoz3r. This feature has been added against the traditional approach of writing CRUD operations for an entity.
    This new feature enables you to just define the services by mentioning the operation you want to perform.Basically just set the engine attribute to "entity-auto" and the invoke attribute to "create", "update", or "delete".
    like you can take a look in the following code from services.xml of example component:  
    Code Block
    <service name="createExample" default-entity-name="Example" engine="entity-auto" invoke="create" auth="true">
        <description>Create a Example</description>
        <permission-service service-name="exampleGenericPermission" main-action="CREATE"/>
        <auto-attributes include="pk" mode="OUT" optional="false"/>
        <auto-attributes include="nonpk" mode="IN" optional="true"/>
        <override name="exampleTypeId" optional="false"/>
        <override name="statusId" optional="false"/>
        <override name="exampleName" optional="false"/>
    </service>
    
    engine="entity-auto" invoke="create" play the role for creating the records for the default-entity "Example."
    Here for practice you may go by following further steps those steps will help you in understanding the concept then onwards you can practice the pattern given above  in your code as its the best practice for these kind of simple operations in OFBiz.

Writing

...

CRUD Operations for Person Entity

- Here for the creation of record for person entity we will need to have the partyId for that so we will first call the service createPracticeParty then after getting the partyId we will create the record for person.
- Here we will be adding one add form in the bottom of the list form which we have for the person entity. This form will be calling the services for creating a record for person.

  1. Create the add form for the creation of person and add this in the same screen for person form.
    Code Block
    <form name="CreatePerson" type="single" target="createPracticePerson">
          <auto-fields-service service-name="createPracticePerson"/>
          <field name="submitButton" title="Create" widget-style="smallSubmit"><submit button-type="button"/></field>
    </form>
  2. Write CrUD CRUD operations for person entity.this is a code for createPracticePerson in services.xml
    Code Block
    <service name="createPracticePerson" default-entity-name="Person" engine="simple"
              location="component://practice/script/org/hotwax/practice/PracticeServices.xml" invoke="createPracticePerson" auth="true">
         <description>Create a Person</description>
         <auto-attributes include="pk" mode="OUT" optional="false"/>
         <attribute name="salutation" mode="IN" type="String" optional="true"/>
         <attribute name="firstName" mode="IN" type="String" optional="false"/>
         <attribute name="middleName" mode="IN" type="String" optional="true"/>
         <attribute name="lastName" mode="IN" type="String" optional="false"/>
         <attribute name="suffix" mode="IN" type="String" optional="true"/>
    </service>  
    

    similar for Update and Delete 
    # Now convert the List form with editable field (Ref. ListExampleItems from ExampleForms.xml) and add Update and delete option with it and also in the same screen there is add form also. As shown bellow:
    Code Block
    <form name="ListPersons" type="list" list-name="persons" list-entry-name="person" target="updatePracticePerson" paginate-target="personForm">
            <auto-fields-service service-name="updatePracticePerson" default-field-type="edit" map-name="person"/>
            <field name="partyId"><hidden/></field>
            <field name="submitButton" title="Update" widget-style="smallSubmit"><submit button-type="button"/></field>
            <field name="deletePracticePerson" title="Delete Person" widget-style="buttontext">
            <hyperlink target="deletePracticePerson?partyId=${person.partyId}" description="Delete"/>
          </field>
    </form>
    

    # Create controller entries for these services which are going to be called by this form.
    Now run the application and see the output screen as bellow:
    Output Screen:

...

  1. Make entry of /js in allowedPaths of web.xml. So now allowed paths parameter will look like given below:
    1. This will allow .js files which are under /js folder to load.
    2. Step -7 will make you understand more, why we are doing this entry here.
      Code Block
      <init-param>
          <param-name>allowedPaths</param-name>
          <param-value>/control:/select:/index.html:/index.jsp:/default.html:/default.jsp:/images:/includes/maincss.css:/js</param-value>
      </init-param>
      
      *Note: * Here you must not miss that this will require a server restart.
  2. Include validation.js and prototype.js in main-decorator in practice/widget/CommonScreens.xml. For this you have to write below given code in <actions> block of main-decorator.
    1. We are including these library files in main-decorator screen, because all other screens uses main-decorator and thus both the libraries will be available in other screens as well.
      Code Block
      <set field="layoutSettings.javaScripts[+0]" value="/images/prototypejs/validation.js" global="true"/>
      <set field="layoutSettings.javaScripts[]" value="/images/prototypejs/prototype.js" global="true"/>
      
      validation.js and prototype.js are located at framework/images/webapp/images/prototypejs/
  3. Add another menu item to application menu bar by name "Ajax". Below given is the controller entry:
    Code Block
    <!-- Request Mapping -->
    <request-map uri="Ajax">
        <security https="true" auth="true"/>
        <response name="success" type="view" value="PersonFormByAjax"/>
    </request-map>
    
    <!-- View Mapping -->
    <view-map name="PersonFormByAjax" type="screen" page="component://practice/widget/PracticeScreens.xml#PersonFormByAjax"/>
    
  4. Create new screen called "PersonFormByAjax" in PracticeScreens.xml. Example code is given below:
    1. PracticeApp.js is the custom js file where we will be writing our custom js code for ajaxifying our request.
    2. person.ftl is the same file we created above.
    3. CreatePerson.ftl is a new file which you need to create now. This file contains form for creating new person, which is same as we created in step-1 of Part-3 under "Writing CrUD CRUD operations for Person entity" section. Only difference is that this form is written in freemarker.
      Code Block
      <screen name="PersonFormByAjax">
          <section>
              <actions>
                  <set field="headerItem" value="ajax"/>
                  <set field="titleProperty" value="PageTitlePracticePersonForm"/>
                  <property-map resource="PartyUiLabels" map-name="uiLabelMap" global="true"/>
                  <set field="layoutSettings.javaScripts[]" value="/practice/js/PracticeApp.js" global="true"/>
                  <entity-condition entity-name="Person" list="persons"/>
              </actions>
              <widgets>
                  <decorator-screen name="CommonPracticeDecorator" location="${parameters.mainDecoratorLocation}">
                      <decorator-section name="body">
                          <platform-specific>
                              <html>
                                  <html-template location="component://practice/webapp/practice/person.ftl"/>
                                  <html-template location="component://practice/webapp/practice/CreatePerson.ftl"/>
                              </html>
                          </platform-specific>
                      </decorator-section>
                  </decorator-screen>
              </widgets>
          </section>
      </screen>
      
  5. Create new file CreatePerson.ftl explained above in practice/webapp/practice/ and place below given code:
    1. Also notice ids used in this .ftl file.
    2. We will be using these ids in our js file.
      Code Block
      <h2>${uiLabelMap.PartyCreateNewPerson}</h2>
      <div id="createPersonError" style="display:none"></div>
      <form method="post" id="createPersonForm" action="<@ofbizUrl>createPracticePersonByAjax</@ofbizUrl>">
        <fieldset>
          <div>
            <label>${uiLabelMap.FormFieldTitle_salutation}</label>
            <input type="text" name="salutation" value=""/>
          </div>
          <div>
            <label>${uiLabelMap.PartyFirstName}*</label>
            <input type="text" name="firstName"  value=""/>
          </div>
          <div>
            <label>${uiLabelMap.PartyMiddleName}</label>
            <input type="text" name="middleName" value=""/>
          </div>
          <div>
            <label>${uiLabelMap.PartyLastName}*</label>
            <input type="text" name="lastName" class="required" value=""/>
          </div>
          <div>
            <label>${uiLabelMap.PartySuffix}</label>
            <input type="text" name="suffix" value=""/>
          </div>
          <div>
            <a id="createPerson" href="javascript:void(0);" class="buttontext">${uiLabelMap.CommonCreate}</a>
          </div>
        </fieldset>
      </form>
      
    3. Click on "Ajax" menu to observe the PersonFormByAjax screen.
  6. Add new div in person.ftl file. Now person.ftl will look like:
    1. Here again div's id will be used in PracticeApp.js file
      Code Block
      <#if persons?has_content>
        <div id="personList">
          <h2>Some of the people who visited our site are:</h2>
          <br>
          <ul>
            <#list persons as person>
              <li>${person.firstName!} ${person.lastName!}</li>
            </#list>
          </ul>
        </div>
      </#if>
      
  7. Now create PracticeApp.js in practice/webapp/practice/js/ and place the below given code :
    1. Here on first line, Event.observe(element, eventName, handler), registers an event handler on a DOM element.
      1. Argument 1: The DOM element you want to observe; as always in Prototype, this can be either an actual DOM reference, or the ID string for the element.
      2. Argument 2: The standardized event name, as per the DOM level supported by your browser. This can be as simple as 'click'.
      3. Argument 3: The handler function. This can be an anonymous function you create on-the-fly, a vanilla function.
    2. So here on window load, on-the-fly function is called. where form validations and request calling is done.
    3. Important thing to notice is why we write other observation code on window load event, and answer is here we keep restriction, that on window load, all the elements of the form will get activated and then we put observation on form's elements.
    4. In CreatePerson.ftl you see that class="required" are used on forms's input element, You then activate validation by passing the form or form's id attribute as done in second line. More on this can be learned from learn validation
    5. On third line, observer is on "createPerson" which is id of anchor tag (button) in CreatePerson.ftl,
    6. so that when end user clicks "create button" , the instance method, validate(), will return true or false. This will activate client side validation.
    7. And then createPerson function is called which is out of the scope of window load observer.
    8. In request variable, createPersonForm's action is stored. $('createPersonForm') is again a id of form in CreatePerson.ftl.
    9. new Ajax.Request(url) : Initiates and processes an AJAX request.
    10. The only proper way to create a requester is through the new operator. As soon as the object is created, it initiates the request, then goes on processing it throughout its life-cyle.
    11. Request life cycle:
      1. Created
      2. Initialized
      3. Request sent
      4. Response being received (can occur many times, as packets come in)
      5. Response received, request complete
    12. So here createPracticePersonByAjax request will be called from controller.xml, which will call createPracticePerson service and do needful entries.
    13. Form's elements are serialized and passed as a parameter in ajax request. This is represented in last line of createPerson function.
    14. Now if response is successful and server has not returned an error, "new Ajax.Updater($('personList'), 'UpdatedPersonList'" will be executed.
    15. Ajax updater, performs an AJAX request and updates a container's contents based on the response text. To get more on this please read : ajax updater
    16. So "personList" is the id of div in person.ftl, which will be replaced by response of UpdatedPersonList request.
      Code Block
      Event.observe(window, 'load', function() {
          var validateForm = new Validation('createPersonForm', {immediate: true, onSubmit: false});
          Event.observe('createPerson', 'click', function() {
             if (validateForm.validate()) {
                 createPerson();
             }
          });
      });
      
      function createPerson() {
          var request = $('createPersonForm').action;
          new Ajax.Request(request, {
              asynchronous: true,
              onComplete: function(transport) {
                  var data = transport.responseText.evalJSON(true);
                  var serverError = getServerError(data);
                  if (serverError != "") {
                      Effect.Appear('createPersonError', {duration: 0.0});
                      $('createPersonError').update(serverError);
                  } else {
                      Effect.Fade('createPersonError', {duration: 0.0});
                      new Ajax.Updater($('personList'), 'UpdatedPersonList', {evalScripts: true});
                  }
              }, parameters: $('createPersonForm').serialize(), requestHeaders: {Accept: 'application/json'}
          });
      }
      getServerError = function (data) {
          var serverErrorHash = [];
          var serverError = "";
          if (data._ERROR_MESSAGE_LIST_ != undefined) {
              serverErrorHash = data._ERROR_MESSAGE_LIST_;
      
              serverErrorHash.each(function(error) {
                  if (error.message != undefined) {
                      serverError += error.message;
                  }
              });
              if (serverError == "") {
                  serverError = serverErrorHash;
              }
          }
          if (data._ERROR_MESSAGE_ != undefined) {
              serverError += data._ERROR_MESSAGE_;
          }
          return serverError;
      };
      
  8. Now do required controller.xml entries :
    1. Here you may see that after service invocation request is chained and and is redirected to json request.
    2. json request is in common-controller.xml which invokes common json reponse events and send back json reponses.
      Code Block
      <request-map uri="createPracticePersonByAjax">
          <security https="true" auth="true"/>
          <event type="service" invoke="createPracticePerson"/>
          <response name="success" type="request" value="json"/>
          <response name="error" type="request" value="json"/>
      </request-map>
      <request-map uri="UpdatedPersonList">
          <security https="true" auth="true"/>
          <response name="success" type="view" value="UpdatedPersonList"/>
      </request-map>
      <!--View Mappings -->
      <view-map name="UpdatedPersonList" type="screen" page="component://practice/widget/PracticeScreens.xml#UpdatedPersonList"/>
      
  9. Finally create UpdatedPersonList screen in practice/widget/PracticeScreens.xml
    1. This is same as Person Screen
    2. Now this screen will be shown by Ajax updater.
      Code Block
      <screen name="UpdatedPersonList">
          <section>
              <actions>
                  <script location="component://practice/webapp/practice/WEB-INF/actions/person.groovy"/>
              </actions>
              <widgets>
                  <platform-specific>
                      <html>
                          <html-template location="component://practice/webapp/practice/person.ftl"/>
                      </html>
                  </platform-specific>
              </widgets>
          </section>
      </screen>
      
  10. Now submit the form and and run your ajax request.

...