Remembering in KRL: Using Entity Variables with Forms


Dan asked a question in the Kynetx Developer Exchange about remembering user entered data in KRL. I gave him a brief outline of the solution but thought an example would be nice. This blog post is the detailed answer to how to gather, remember, and use user-supplied data.

The basic idea is to store the data in an entity variable. Trails (a type of persistent variable) are the most appropriate type of entity variable to use. The ruleset pattern has four rules: initialize, send the form, process the form, use the data. The actual ruleset has five because I added one to delete the user data since it makes testing much more convenient. I'll go over each rule in order.

Forget: The first rule just clears the entity variable ent:name when you visit a particular web page. You could, of course, do this under user control with a form submission or something, but this is easy enough and suits my purpose for testing.

rule clear_name is active {
   select when web pageview "www.foobar.com" 
   noop();
   always {
      clear ent:name;
      last
   }
 }

Note that if the rule is selected (the page URL matches) then the entity variable is cleared and this is the last rule executed in this ruleset.

Initialize: I like initializing the area of the page I'm going to write or modify and then do the modification in later rules since I can have different rules do different things to the area as needed. So the initialization rule just puts up an empty notification box with a div named #my_div that we'll use in late rules.

rule initialize is active {
  select when pageview ".*"
   pre {   
    blank_div = <<
<div id="my_div">
</div>
    >>;
  }
  notify("Hello Example", blank_div)
      with sticky=true;
}

Note: for all the rules in this ruleset that are selected by web pageviews, I've made the URL pattern as general as possible (.*). In a real ruleset, these would likely be much more restricted.

Send the Form: This rule puts the form into the div we initialized in the last rule if the entity variable ent:name is empty (we could probably add a primitive test that makes this easier). The rule also sets a watcher on that form. If this rule fires (i.e. the rule is selected and the action premise is true) then this will be the last rule executed in this ruleset.

rule set_form is active {
  select when pageview ".*"
  pre {   
    a_form = <<
<form id="my_form" onsubmit="return false">
<input type="text" name="first"/>
<input type="text" name="last"/>
<input type="submit" value="Submit" />
</form>
    >>;
  }
  if(not seen ".*" in ent:name)  then {
    append("#my_div", a_form);
    watch("#my_form", "submit");
  }
  fired {
     last;
  }
}

When this rule fires, you get a notification box that looks like this:

Process the form: We need a rule to process the form. This rule is selected when the form is submitted. The rule doesn't have an action (i.e. the action is noop();). All the real work is done in the postlude where we store the name in the entity variable ent:name and then raise an explicit event that will cause another rule to be selected.

rule respond_submit is active {
  select when web submit "#my_form"
  pre {
     first = page:param("first");
     last = page:param("last");
  }
  noop();
  fired {
     mark ent:name with first + " " + last;
     raise explicit event got_name
  }
} 

Use the data: The final rule uses the data in the entity variable to put the user's name in the div we placed in the initialization rule. This rule has two selection conditions. It can be selected on a pageview like the other rules or when an explicit event named got_name is raised. Remember that the previous rule raises that event when it's done. The action replaces the contents of the div having the ID my_div with a hello message that includes the name.

rule replace_with_name is active {
   select when explicit got_name 
            or web pageview ".*" 
   pre {
     name = current ent:name;
   }
   replace_inner("#my_div", "Hello #{name}");
}

When this rule fires, the notification box looks like this:

hello box

Whenever this ruleset is selected in the future the user will not see the form but simply see this box because the system remembers the name in the entity variable and has no need to ask for it again. If the data gets cleared, then the user is prompted to enter it again.

This ruleset also shows the use of explicit events. If we don't raise the explicit event got_name in the rule that stores the name, nothing will replace the contents of the notification box and show the user that the form submission is successful. We could have done it in that rule, but then we'd have two rules replacing the contents with a message and if the message changed we'd have to make sure we changed it in two places. This technique allows us to have one rule responsible for putting the hello message in the div. We just fire it under two different circumstances.

Update: Here's a video that shows this ruleset in action: