KNS Questions and Dialogs

Several use cases exist where extra document processing needs to occur between the submitting of a document for routing or approval and the validation on that document. For instance, a document may be created to purchase an airplane ticket. The initial submitter is not required to enter the airline that will be traveled on. However, if the initial submitter attempts to route the document without an airline being entered, a prompt can come up to ask if the submitter really meant to not enter the airline. If the answer is yes, the document will go on to validation; if the answer is no, then the document will return to allow the user to edit.

Prompting Before Validation

This kind of prompt is easily accomplished by giving the document an org.kuali.rice.kns.rule.PromptBeforeValidation implementation. This is done via the data dictionary:

<bean id="BudgetAdjustmentDocument" parent="AccountingDocumentEntry">
    <property name="documentTypeName" value="BA"/>
    <property name="documentClass" value="org.kuali.kfs.fp.document.BudgetAdjustmentDocument"/>
    <property name="promptBeforeValidationClass" value="org.kuali.kfs.fp.document.validation.impl.BudgetAdjustmentDocumentPreRules"/>
        ...
</bean>

The PromptBeforeValidation interface only has one method, processPrompts. It is responsible for holding the current form at a current point, rendering a question, getting the answer to that question, and applying that answer to the next forward. It provides a lot of flexibility.

If, however, all of the questions to ask the user can be formulated as yes/no questions, it is more advisable to simply extend org.kuali.rice.kns.rules.PromptBeforeValidationBase and override the doPrompts method. PromptBeforeValidationBase provides all the functionality necessarily to easily ask a yes/no question or even a series of yes/no questions.

Analysis of an example from KFS should help clarify how this works. org.kuali.kfs.module.ar.document.validation.impl.CustomerPreRules will be examined. Here is how it overrides the doPrompts method:

@Override
public boolean doPrompts(Document document) {
    boolean preRulesOK = true;
    preRulesOK &= conditionallyAskQuestion(document);
    return preRulesOK;
}

doPrompts takes the document to act upon as a parameter and it returns a boolean variable. If true is returned, the document will plow forward into validation. If false is returned, then the view should return to another forward. Which forward used will be soon revealed.

Given this information, it’s obvious that the real work is occurring in conditionallyAskQuestion. And indeed it is:

protected boolean conditionallyAskQuestion(Document document) {
    MaintenanceDocument maintenanceDocument = (MaintenanceDocument) document;
    Customer newCostomer = (Customer) maintenanceDocument.getNewMaintainableObject().getBusinessObject();
    boolean shouldAskQuestion = maintenanceDocument.isNew() && checkIfOtherCustomerSameName(newCostomer);

    if (shouldAskQuestion) {
        String questionText = SpringContext.getBean(KualiConfigurationService.class).getPropertyString(ArKeyConstants.CustomerConstants.MESSAGE_CUSTOMER_WITH_SAME_NAME_EXISTS);
        boolean confirm = super.askOrAnalyzeYesNoQuestion(ArKeyConstants.CustomerConstants.GENERATE_CUSTOMER_QUESTION_ID, questionText);
        if (!confirm) {
            super.abortRulesCheck();
        }
    }
    return true;
}

The document in this example is a maintenance document, but the method works precisely the same given a transactional document.

Logic determines, in this case, if the customer is new and if it shares the name of an existing customer. If that is the case, then it asks a yes/no question about if the user meant to enter a second customer with the same name. Note that question text is specified via a User Message; this is a best practice.

The question is asked and the yes/no answer returned through the super.askOrAnalyzeYesNoQuestion. That needs to be handed an ID which uniquely represents every asking of this question – that, in conjunction with information from the document itself, is used to identify the user response, which ends up in the session. The other method argument is the question text itself.

It returns true or false. Note though, that if the response was false, that false is not returned, but instead a method super.abortRulesCheck(); is called.

abortRulesCheck() is simply a convenience method that sets the forward to return to as the BASIC_MAPPING:

public void abortRulesCheck() {
    event.setActionForwardName(RiceConstants.MAPPING_BASIC);
    isAborting = true;
}

If application requirements determine that a “no” answer should navigate the user to a different mapping than “basic”, then abortRulesCheck should not be used, but instead, a false should be returned from the method, and the correct action forward name should be set on the event property inherited from PromptBeforeValidationBase.

There is no limit to the number of times super.askOrAnalyzeYesNoQuestion can be called in a single pre-rules check; several questions can be chained together.

HTML Markup

In the question framework some markup support is present for formatting the question text. This markup follows a custom syntax as opposed to HTML. Standard HTML characters will be escaped in the question text. This is to prevent cross-site scripting attacks. The custom syntax for the supported tags is then translated to the corresponding HTML when rendering the question page.

The custom syntax uses brackets to indicate tags as opposed to the standard HTML left and right angle quote characters. Like HTML, an opening and closing tag must be present: e.g. [tag] ... [/tag]. The custom syntax does not support empty body tags: e.g. [tag/].

The following is a list of the tags supported along with the corresponding HTML translation.

* All 1 character HTML tags
Examples:
[p] ... [/p] translates to <p> .... </p>
[b] ... [/b] translates to <b> .... </b>


* All 2 character HTML tags
Examples:

[br] ... [/br] translates to <br> .... </br>
[tr] ... [/tr] translates to <tr> .... </tr>

[td] ... [/td] translates to <td> .... </td>

* The font tag with color specified as hex or by name
Examples:
[font #000000] ... [/font] translates to <font color="#000000"> .... </font>
[font red] ... [/font] translates to <font color="red"> .... </font>

* The table tag

Example:

[table] ... [/table] translates to <table> .... </table>

* The table tag with style class
Example:
[table questionTable] ... [/table] translates to <table class="questionTable"> .... </table>


* The td tag with style class

Example:
[td leftTd] ... [/td] translates to <td class="leftTd"> .... </td>

Note since the style tag is not allowed any CSS classes used must be declared in the Kuali style sheet (by default kuali.css). In addition be aware that the one and two character tags are not verified as valid HTML tags. In essence, the brackets are simply replaced by the angle quotes and outputted for these tags.

When forming the question text, consideration should be given to the text length. The question text is sent as one of the request parameters on the URL which is limited by the browser supported max length. Keeping the text under 1000 characters will be safe across all supported browsers.