Reactive rule-based dynamic forms in hybris using Drools7 – hybrismart | SAP hybris under the hood

Reactive rule-based dynamic forms in hybris using Drools7


Web applications heavily rely on forms. Questionnaires and comprehensive registration forms are common components of the HR, financial and service solutions. The business rules behind these forms are complex and frequently updated. The well-known example is job application forms. Depending on the user input, some components of the form expands to gather more data from the applicant, timely and in context.

This article is about my experiments with the rule engine driven forms. I used Drools, as a default rule engine that comes with hybris out of the box. I also used the code of JBoss Tohu project, long abandoned.

In the end of the article, you will find a link to source code of the project. This is the alpha release of the system, so it need some polishing to get used in production.

I’ve set myself the following objectives:

  • The solution should support
    • dynamic forms:
      • Some fields of the form are available only for some form configurations and/or user groups
        • For example, some fields are available only for the customers with a subscription
        • For example, some fields are visible only if other fields are filled with the particular values
      • Some form elements have different views on the different form configurations and/or user groups
        • For example, a select box can have a separate set of options and it depends of the set of checkboxes from the previous page
    • multi-page forms:
      • Wizard-like, with Prev/Next/Done buttons
      • Conditional (dynamic) pages
    • complex validation rules:
      • Validation rules depend on the form configurations
      • Validation rules depend on the user profile attributes
      • If some fields are not visible, their validation rules should be turned off
    • server-side scripting
    • as lightweight as possible

The first thing that comes to mind is Angular Forms. This topic deserves a separate article. Many choose this way, it is a good solution. However, it has some drawbacks. Angular is a front-end thing, so for interacting with the server-side entities you need to build an API layer.  Secondly, you need to redeploy and retest the angular code each time you decide to change the rules.

I did some research and stumbled upon the reactive form framework called Tohu. It was developed more than seven years ago, but both the architecture and code are quite clear, supportable and extensible. I decided to play around with the framework and integrate it with SAP hybris. It was quite challenging because the dependencies of the original code are also obsoleted. For example, Tohu uses Drools 5 while hybris comes with Drools 7. Tohu is built with the old jQuery 1.3.2 while the latest SAP hybris uses jQuery 3.2.1. I refactored the framework to support the latest libraries without touching the original Java code.

As a result, I created a sample project, where the dynamic forms are integrated into hybris as an extension. The source code is available on github.

In order to demonstrate the capabilities of the solution, I needed a comprehensive example. I took some examples from Tohu, refactored and expanded, and this is what I got:

  • There are six pages
    • Contact Details page. Contains Joint/Single application selectbox.
    • *Joint Contact Details Page (second participant).
    • Demographic details page
    • *Demographic details page for the joint application (second participant)
    • Loyalty data page
    • *Loyalty data page for the joint application (second participant)
  • Contact Details Page
    • Personal Details Group
      • Application type (Select Box) Single/Joint. This selection affects the fields, sections and pages.
      • Given names (Text).
      • Family name (Text).
      • Date of birthday (Calendar)
      • E-mail (Text). Should be validated against the text pattern.
      • Phone (Text).
    • Main Address Group
      • Street Address (Text)
      • Suburb (Text)
      • City (Text)
      • Postcode (Text)
    • Postal Address Group
      • Different Postal Address (Boolean)
      • Postal Address (Text)
      • Postal Suburb (Text)
      • Postal City (Text)
      • Postal Postcode (Text)
  • *Joint Contact Details Page
    • (Only displayed if Application Type = Joint)
    • Joint Personal Details
      • Extra Given names (Text).
      • Extra Surname (Text).
      • Extra Date of birthday (Calendar)
      • Extra e-mail (Text).
      • Extra phone (Text).
  • Demographic Details Page
    • IncomeDetails  (Select Box). The selection affects other fields on this page. Possible Answers:
      • Less than 20,000
      • 20,000 to 39,999
      • 40,000 to 59,999
      • 60,000 to 99,999
      • 100,000 and over
    • Own Home (Boolean).
    • Household: Number of people (Number). Default value: 1
      • If a number of people > 1 the following question is displayed:
        • Children Under 18 (Boolean).
          • If Children Under 18 is yes, the following section is displayed:
            • Preschoolers (boolean)
            • Primary (boolean)
            • Intermediate (boolean)
            • Secondary (boolean)
            • Tertiary (boolean)
            • Other (boolean)
    • Ok to receive Promo Materials? (Boolean)
      • if yes, display a list of topics
        • books (Boolean)
        • music (Boolean)
        • crafts (Boolean)
        • motoring (Boolean)
        • cooking (Boolean)
        • home improvement (Boolean).
          • Only displayed if Own Home is checked
        • travel (Boolean)
          • Only displayed if income > 20000
        • toys (Boolean)
          • Only displayed if Children Under 18 is true
        • wine (Boolean)
        • investing (Boolean)
          • Only displayed if income > 100000
  • Loyalty Programs Page
    • Main Name Summary. Not editable. Copied from Given Names.
    • Last name. Not editable. Copied from Family Name.
    • Loyalty Programs:
      • Budget Airways (Boolean).
        • If selected, the following fields are displayed
          • Membership Number (Text)
          • Membership Name (Text)
          • Ok to receive Special Offers? (Boolean)
          • Membership Type (Radio buttons)
            • Gold
            • Silver
            • Bronze
      • Online Food (Boolean)
        • If selected, the following fields are displayed
          • Membership Number (Text)
          • Membership Name (Text)
          • Ok to receive Special Offers? (Boolean)
      • Mocha Coffee (Boolean)
        • If selected, the following fields are displayed
          • Membership Number (Text)
          • Membership Name (Text)
          • Ok to receive Special Offers? (Boolean)
  • *Joint Loyalty Programs Page
    • The same as previous section, but for the second participant
    • Only displayed if Application Type = Joint

Let’s put all business rules together:

  • “Joint…” pages are displayed only if the application type is “Joint”
  • E-mail should be validated against the pattern
  • Some options from the list of promotional materials are available
    • only for some income ranges
    • only if the  
      children under 18

      checkbox is set

    • only if the
      own home

       checkbox is set

  • the details of the loyalty program are displayed only if the checkbox for the particular loyalty program is set
  • the details of the loyalty program have the same questions for all loyalty programs with one exception:  Budget Airways has a membership type, while others don’t have it.
  • joint contact page has its own set of questions and it is displayed only for the application type = Joint
  • Joint loyalty page is the same as main contact loyalty page in terms of the structure and business rules.

This form with these business rules are implemented without a single line of custom java code, only with the Drools rules.

Let’s consider the rules closer.

Rule #1. Initial Setup: Definition of Pages


GeSHi Error: GeSHi could not find the language drools (using path /home/admin/web/hybrismart.com/public_html/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Rule #2. Initial Setup: Contact Details Page Rule

This rule just says that the page contains three sections: PersonalDetails, MainAddress and PostalAddress.
There is no “when” part in the rule that means that the rule is a sort of the initial setup as well.


GeSHi Error: GeSHi could not find the language drools (using path /home/admin/web/hybrismart.com/public_html/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Rule #3. Initial Setup: Definition of PersonalDetails

This rule creates the fields for the Personal Details section. There is a condition,

(Group.id='PersonalDetails' and items = null)

Once the code from the “then” block is executed, items won’t be empty and the rule won’t be fired again.
Drools is based on reactive programming concept and if you empty the items of the group, the rule will be fired again and the form will be populated again.


GeSHi Error: GeSHi could not find the language drools (using path /home/admin/web/hybrismart.com/public_html/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Rule #4. Initial Setup: Definition of MainAddress section

The similar code for the MainAddress section. It is collapsed by default because there is nothing new.
Expand the code


GeSHi Error: GeSHi could not find the language drools (using path /home/admin/web/hybrismart.com/public_html/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Rule #5. Initial Setup: Definition of PostalAddress section

The PostalAddress block has a dynamic component. This component becomes visible only if the “Different Postal Address” checkbox is on.
The dynamic component is defined in a separate rule, “additionalPostalFields”.


GeSHi Error: GeSHi could not find the language drools (using path /home/admin/web/hybrismart.com/public_html/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Rule #6. Definition of AdditionalPostalFields

This rule contains the first “real condition” in our tutorial. This condition is true if the checkbox created in the the previous rule is on.
In this rule, you can see “insertLogical” rather than “insert”. There is a difference between them.

The pieces of data inserted using insertLogical will remain in the session until the condition of the rule that inserted it becomes false. So once you uncheck “additional postal fields” checkbox, this form will disappear. The pieces of data inserted using “insert” are solid, and they need to be removed manually if the needs arise.


GeSHi Error: GeSHi could not find the language drools (using path /home/admin/web/hybrismart.com/public_html/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Rule #8. Initial Setup: Definition of DemographicDetailsPage

The code of the rule is collapsed because there is nothing new. It is very similar to ContactsDetailPage. It contains three sections, IncomeDetails, HouseHold and PromotionalMaterial. It doesn’t have a condition that means that the rule is executed as part of Initial Setup.
Expand the code


GeSHi Error: GeSHi could not find the language drools (using path /home/admin/web/hybrismart.com/public_html/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Rule #9. Definition of IncomeDetails

The code of the rule is collapsed because there is nothing new. It defines two questions, incomeBracket (select box) and ownHome (checkbox).
Expand the code


GeSHi Error: GeSHi could not find the language drools (using path /home/admin/web/hybrismart.com/public_html/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Rule #10. Definition of Household

The next rule creates the question about the number of people and the “placeholder” for the question about the kids.
Note: childrenQuestion is listed as an item but it hasn’t been created yet. It is ok. The system will ignore it. It will be created once the user entered a number larger than 1. Once it is created, the system will use the placeholder for the question. It is exactly as we expect.


GeSHi Error: GeSHi could not find the language drools (using path /home/admin/web/hybrismart.com/public_html/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Rule #11. Definition of childrenQuestion

In this rule, you can see a condition “numberOfPeople>1”. The components are inserted using insertLogical that means that the components will be removed automatically if the condition is false (numberOfPeople is NOT larger than 1). This rule creates another section, “childrenAgeRanges”. This section is defined by a separate rule.


GeSHi Error: GeSHi could not find the language drools (using path /home/admin/web/hybrismart.com/public_html/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Rule #12. Definition of childrenAgeSection

The code of the rule is collapsed because there is nothing new. It defines the questions, one per age range: preschool, primary, intermediate, secondary, tertiary, other. Note that they are considered as a group.
Expand the code


GeSHi Error: GeSHi could not find the language drools (using path /home/admin/web/hybrismart.com/public_html/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Rule #13. Defining PromotionalMaterial

The code of the rule is collapsed because there is nothing new. It defines the section “Promotional Materials” with two questions, a checkbox and promotional types group (promotionalTypesGroup).
Expand the code


GeSHi Error: GeSHi could not find the language drools (using path /home/admin/web/hybrismart.com/public_html/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Rule #13. Defining ptromotionalTypesGroup

In this rule, we only do the fields that always appear – others in different rules. However, all are listed in setItems.
We could create different subgroups for the different logic elements but it is easier to revert to the parent specifying the child approach.


GeSHi Error: GeSHi could not find the language drools (using path /home/admin/web/hybrismart.com/public_html/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Rule #14. Defining Home Improvement

This rule adds an item for the promotional type list if the two conditions are true:

  • receiveMaterials checkbox is on
  • ownHome checkbox is on

Also note that the item is added via insertLogical. It means that the item will be removed automatically once any of these checkboxes is unchecked.


GeSHi Error: GeSHi could not find the language drools (using path /home/admin/web/hybrismart.com/public_html/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Rule #15. Defining Travel

This rule is similar to the previous one. It is triggered only for users who selected the income range other than the lowest one AND who agreed to receive promotional materials.


GeSHi Error: GeSHi could not find the language drools (using path /home/admin/web/hybrismart.com/public_html/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Rule #16. Defining Investing

This rule is similar to the previous rules. As a result, the “investing” item will be added only if you checked “receiveMaterials” and selected the last item in the selectbox.


GeSHi Error: GeSHi could not find the language drools (using path /home/admin/web/hybrismart.com/public_html/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Rule #17. Defining Toys

The item “toys” is added only if the user has children and they agreed to receive the promotional materials.


GeSHi Error: GeSHi could not find the language drools (using path /home/admin/web/hybrismart.com/public_html/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Rule #18. Defining MainOtherLoyaltyProgramsPage


GeSHi Error: GeSHi could not find the language drools (using path /home/admin/web/hybrismart.com/public_html/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Rules #19-#24. Defining the rules for Loyalty checkboxes

rule "Main BA Yes Clicked support"
dialect "mvel"
when
q : Question(id == "budgetAirways", answer == true);
then
String groupId = q.id + "_details" ;
createDetails(drools, groupId, q.id);
end
rule "Main OF Yes Clicked support"
dialect "mvel"
when
Question(id == "onlineFood", answer == true);
then
String id = "onlineFood";
String groupId = id + "_details" ;
createDetails(drools, groupId, null);
end
rule "Main MC Yes Clicked support"
dialect "mvel"
when
Question(id == "mochaCoffee", answer == true);
then
String id = "mochaCoffee";
String groupId = id + "_details" ;
createDetails(drools, groupId, null);
end

rule "Main BA Yes clicked"
salience 15
no-loop
when
q : Question(id == "budgetAirways");
a : Answer(questionId == q.id, value == "true");
questionnaire : Questionnaire(branched == "false");

then
String groupId = q.getId() + "_details" ;
questionnaire.navigationBranch(new String[]{groupId}, groupId);
update(questionnaire);
end

rule "Main OF Yes clicked"
salience 15
no-loop
when
q : Question(id == "onlineFood");
a : Answer(questionId == q.id, value == "true");
questionnaire : Questionnaire(branched == false);

then
String groupId = q.getId() + "_details" ;
questionnaire.navigationBranch(new String[]{groupId}, groupId);
update(questionnaire);
end

rule "Main MC Yes clicked"
salience 15
no-loop
when
q : Question(id == "mochaCoffee");
a : Answer(questionId == q.id, value == "true");
questionnaire : Questionnaire(branched == false);
then
String groupId = q.getId() + "_details" ;
questionnaire.navigationBranch(new String[]{groupId}, groupId);
update(questionnaire);
end

function void createDetails(KnowledgeHelper drools, String pageId, String baQuestionId) {

Group page = new Group(pageId);
page.setLabel("What are the details of the other loyalty card");
String groupId = pageId + "_group";
page.addItem(groupId);

Group group = new Group(groupId);
group.setLabel("8");
group.setPresentationStyles(new String[]{"section", "programDetails"});

String prefix = groupId + "_";
String id1 = prefix + "membershipNumber";
String id2 = prefix + "membershipName";
String id3 = prefix + "specialOffers";
String id4 = prefix + "membershipType";

group.addItem(id1);
group.addItem(id2);
group.addItem(id3);
if (baQuestionId != null) {
group.addItem(id4);
}
drools.insertLogical(group);

Question question = new Question(id1);
group.addItem(question.getId());
question.setAnswerType(Question.TYPE_TEXT);
question.setPreLabel("Membership Number");
drools.insertLogical(question);

question = new Question(id2);
question.setAnswerType(Question.TYPE_TEXT);
question.setPreLabel("Membership Name");
drools.insertLogical(question);

question = new Question(id3);
question.setAnswerType(Question.TYPE_BOOLEAN);
question.setPreLabel("Do you want to receive combined special offers");
drools.insertLogical(question);

if (baQuestionId != null) {
MultipleChoiceQuestion mcQuestion = new MultipleChoiceQuestion(id4);
mcQuestion.setAnswerType(Question.TYPE_TEXT);
mcQuestion.setPreLabel("Membership Type");
mcQuestion.setPossibleAnswers(new PossibleAnswer[]{
new PossibleAnswer("1", "Gold"),
new PossibleAnswer("2", "Silver"),
new PossibleAnswer("3", "Bronze")}
);
mcQuestion.setPresentationStyles(new String[]{ "radio" });
drools.insertLogical(mcQuestion);
}

drools.insertLogical(page);

}

Rule #25-#27. Adding Joint Application page

Add Joint Loyalty Programs Page


GeSHi Error: GeSHi could not find the language drools (using path /home/admin/web/hybrismart.com/public_html/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Joint Loyalty Programs Page


GeSHi Error: GeSHi could not find the language drools (using path /home/admin/web/hybrismart.com/public_html/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Remove Joint Loyalty Page


GeSHi Error: GeSHi could not find the language drools (using path /home/admin/web/hybrismart.com/public_html/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Rule #27-#32. Adding Joint Application Support for checkboxes

These rules use the same shared code for creating the similar subforms. There two rules per each checkbox. The first rule creates the form, and another rule creates NavigationBranch, a sort of subform that replaces the main form completely.


GeSHi Error: GeSHi could not find the language drools (using path /home/admin/web/hybrismart.com/public_html/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Adding new validations using hybris API

If we want to add new validators, everything we need is to create a new file with a new rule.
In the rule below, the system makes a call to hybris API to validate an e-mail from the form.


GeSHi Error: GeSHi could not find the language drools (using path /home/admin/web/hybrismart.com/public_html/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Adding new fields prefilled with hybris data


GeSHi Error: GeSHi could not find the language drools (using path /home/admin/web/hybrismart.com/public_html/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Leave a Reply