2.10. Organizing code into VoiceXML applications

So far, we have authored VoiceXML documents that exercise a wide range of the language features. As the size and complexity of a voice application grows, it becomes impractical to store it entirely within a single document. We have already seen examples of this where one dialog references another with a <goto next="anotherdocument.xml"/> or <link next="destination.xml"/>.

The problem that arises here is that HTTP, the protocol by which a VoiceXML interpreter communicates with the server storing the VoiceXML documents, does not maintain state. Therefore any state information that may have been created during the interpreting of one VoiceXML document is lost as soon as the next document is loaded.

This problem is a common one in Web application development. The Web community has responded over the past few years with some standard tricks and techniques including browser cookies and hidden form variables. While hidden form variables are certainly feasible on any specification-compliant VoiceXML interpreter, as are client-side cookies that are also available on most implementations, VoiceXML provides one additional trick: the application-level variable scope.

2.10.1. VoiceXML application

The term VoiceXML application specifically refers to an application root VoiceXML document and one or more application leaf documents all of which specify the application root document as their root. This is accomplished using the application attribute of the vxml element:

<vxml version="2.0" application="CustomerService.xml">

The above vxml element declares CustomerService.xml as its application root. Therefore, any variables, catch blocks, links, or property settings declared in CustomerService.xml can be accessed from this VoiceXML document or any other document that declares CustomerService.xml as its application root.

2.10.2. An application example: Regional Electric Co-Op

This section uses the example of an automated customer support center of Regional Electric Co-Op. In this example, all callers can access certain features of the system, such as checking for local outages or speaking with a customer service representative. In order to access advanced features, such as checking your bill or changing your service options, the caller needs to log in by providing a user ID and password.

For this example, we've only implemented the main menu, the login dialog, and the application root. The following subsections examine each of these parts.

2.10.2.1. The application root document: CustomerService.xml

The purpose of the application root document is to define the global constructs available to all application leaf documents. For this simple example, we only define two global constructs: the "menu" link and the custID variable. The "menu" link allows the user to get back to the main menu from anywhere within the entire application simply by saying the word "menu." The custID variable holds the caller's user ID. This variable persists over the duration of the call, or until the VoiceXML interpreter goes out of the CustomerService application.

The code for the application root is shown in Example 2-66.

Example 2-66. Root document CustomerService.xml
<?xml version="1.0" encoding="iso-8859-1"?>
<vxml version="2.0">
  <link next="csmenu.xml">
    <grammar>menu</grammar>
  </link>

  <var name="custID" expr="0"/>

</vxml>


2.10.2.2. Customer Service main menu: csmenu.xml

The central document in the Customer Service application is the main menu, csmenu.xml. When deploying the Customer Service application, the VoiceXML interpreter should be configured so that csmenu.xml is the first document loaded to handle an incoming phone call.

In order for csmenu.xml to have access to the application environment, it must declare CustomerService.xml as its application root:

<vxml application="CustomerService.xml">

The menu is implemented in csmenu.xml using the menu element, as shown in Example 2-68. However, the contents of the menu need to be different depending on whether or not the user has logged in. For this reason we use a script element, shown in Example 2-67, to pre-compute the prompts for the menu.

Example 2-67. The script element that precedes the menu in csmenu.xml
<script>
var menuMsg;
var introMsg;
{
  if(custID != 0){
    introMsg = "Welcome user number " + custID + "<break/>";
  }else{
    introMsg = "Welcome to Regional Electric Co-Op Customer Service.
<break/>";
  }

  menuMsg = "You may say 'menu' at any time to return to this menu.
<break/>";
  menuMsg += "Please select from one of " + 
             "the following options. <break/>";
  menuMsg += "Say 'report' to hear about " + 
             "service outages in your area.
<break/>";
  menuMsg += "Say 'operator' to speak with " + 
             "a representative. <break/>";
  if(custID == 0){
    menuMsg += "Say 'log in' to log in and access your records.
<break/>";
  }else{
    menuMsg += "Say 'bill' to access your current bill. <break/>";
    menuMsg += "Say 'service' to access " + 
               "your current service options.<break/>";
    menuMsg += "Say 'log off' to exit the system. <break/>";
  }
}
</script>

Example 2-68. The menu element in csmenu.xml
<?xml version="1.0" encoding="iso-8859-1"?>
<vxml version="1.0">

...Example 2-67 goes here...

  <menu id="welcome">

    <prompt> 
      <value expr="introMsg"/>
      <break size="medium"/>
      <value expr="menuMsg"/> 
    </prompt>

    <choice next="cslogin.xml">log in</choice>
    <choice next="csoperator.xml">operator</choice>
    <choice next="csreport.xml">report</choice>
    <choice next="csbill.xml">bill</choice>
    <choice next="csservice.xml">service</choice>
    <choice next="cslogoff">log off</choice>
    <nomatch>
      I'm sorry. That is not a valid option.
      <reprompt/>
    </nomatch>
  </menu>
</vxml>

This ECMAScript block defines two document-level variables named introMsg and menuMsg. The introMsg contains a simple welcome message either welcoming an already logged-in user with his or her user ID or welcoming a non-logged-in user in a generic fashion.

Note that the userID variable was initialized to 0 in the CustomerService.xml application root document. We assume this value to mean that the user is not logged in.

The menuMsg will always inform the user that he or she can say "menu" at any time to return to this menu as well as use the options "report" and "operator." If the user has not logged in, menuMsg will then list the option to log in. If the user has already logged in, the menuMsg will not mention the option to log in, but instead will inform the user of the options "bill," "service," and "log off."

The remainder of csmenu.xml defines the menu dialog, as shown in Example 2-68.

The menu's prompt plays the introMsg followed by a pause and the menuMsg. Regardless of whether the user has logged in or not, the choice elements for all options are still present. This means that the grammar generated by this menu element will contain choices that are not presented in the prompt. This could be resolved had VoiceXML provided an attribute for choice to enable deactivation of a choice based on the evaluation of an ECMAScript expression. Unfortunately it does not, so we must make sure that the destination documents (csoperator.xml, csbill.xml, etc.) jump back to csmenu.xml if the user was not logged in - i.e. if custID equals 0.

2.10.2.3. The log in dialog: cslogin.xml

The dialog associated with the "log in" option of the main menu, cslogin.xml, is shown in Example 2-69.

Example 2-69. The log in dialog: the cslogin.xml document
<?xml version="1.0" encoding="iso-8859-1"?>
<vxml application="CustomerService.xml">
  <form id="login">
    <field name="id" type="digits?length=5">
      <prompt>
        Please say your 5 digit customer ID number now.
      </prompt>
      <filled>
        You said <value expr="id"/>.
      </filled>
    </field>

    <field name="password" type="number">
      <prompt>
        Please say the password.
      </prompt>
      <filled>
        <prompt>
          You said <value expr="password"/>.
        </prompt>
        <if cond="password != 42">
          <prompt>Invalid password.</prompt>
          <clear namelist="id password"/>
          <reprompt/>
        </if>
      </filled>
    </field>

    <filled mode="all">
      <assign name="custID" expr="id"/>
      <prompt> 
        You are now logged in as <value expr="custID"/>.
      </prompt>
      <goto next="csmenu.xml"/>
    </filled>
  </form>
</vxml>

The cslogin.xml document is a relatively simple form comprised of two fields and a filled. The first field sets the form variable id, and the second prompts the caller for the password, continually re-prompting until the user gives the correct password. In a real life implementation, this password would be retrieved from a database after collecting the user ID, but this is beyond of the scope of this section. See Chapter 4, “Enterprise voice application architecture,” on page 308 for more information on building enterprise voice applications.

Finally, once both user ID and a correct password have been collected, the filled block for the form sets the application-level variable custID to the value of the form-level variable id.

2.10.2.4. Testing the Customer Service voice application

With these three documents completed we can start to test the behavior of the Customer Service voice application. Consider the scenario in Example 2-70.

Example 2-70. A use scenario for the Customer Service application
IVR     : Welcome to Regional Electric Co-Op Customer Service.
          You may say 'menu' at any time to return to this menu.
          Please select from one of the following options.
          Say 'report' to hear about service outages in your area.
          Say 'operator' to speak with a representative.
          Say 'log in' to log in and access your records.
Caller  : log in.
IVR     : Please say your 5 digit customer ID number now.
Caller  : 1 1 2 3 1
IVR     : You said 1 1 2 3 1.
          Please say the password.
Caller  : 42
IVR     : You said 42.
          You are now logged in as 1 1 2 3 1.
          Welcome user number 1 1 2 3 1.
          You may say 'menu' at any time to return to this menu.
          Please select from one of the following options.
          Say 'report' to hear about service outages in your area.
          Say 'operator' to speak with a representative.
          Say 'bill' to access your current bill.
          Say 'service' to access your current service options.
          Say 'log off' to exit the system.

Since csmenu.xml is the first document loaded, the caller first visits the main menu. Before presenting the main menu, though, the VoiceXML interpreter first loads CustomerService.xml. It does this because it has not yet been loaded, but as the application root document it must be resident for the entire time that the VoiceXML interpreter is within the CustomerService.xml application.

Initially, the application-level variable custID is set to 0. Since this value is assumed to mean that the user is not logged in, the introMsg and menuMsg are the "not-logged-in" version. The caller chooses "log in" causing the VoiceXML interpreter to jump to cslogin.xml. Here the user tells the application his or her customer ID and the password. After the user answered these questions successfully, cslogin.xml sets the application-level custID variable to 11231 and returns the caller to csmenu.xml. This time, however, the custID variable is set to a non-zero value, so both the introMsg and the menuMsg played at the beginning of this menu are the "logged-in" version.

Let's assume the user had forgotten the password. The cslogin.xml application doesn't have a limit on the number of times that you can try different passwords, so you might think one may never get out of the loop! In fact, the application-level "menu" link solves this problem (assuming the caller remembers!). As soon as the caller says "menu," this will return him to the main menu.

2.10.2.5. Example summary

We have now seen an example of how many documents can share state and behavior by being leaf documents of the same application. In particular, we have looked at sharing the menu link and the custID variable, simply by declaring these elements in the CustomerService.xml document. We could use the same technique to declare system properties, such as parameters controlling the text-to-speech or speech-recognition engines. We could also use this technique to install global catch elements that provide a consistent interface for handling common errors. In summary, the application root document promotes consistency among all leaf documents in the application.

2.10.3. Leaving an application

When the VoiceXML interpreter leaves an application, all application state is lost. Therefore it is important to understand what constitutes "leaving an application." As we have seen in the previous example, moving from one leaf of an application root to another leaf of the same root preserves all application-level state.

For example, let's assume csmenu.xml's application root document is CustomerService.xml. This can be easily verified by checking csmenu's vxml element; it should have an attribute named application with a value of CustomerService.xml. Let's assume we need to switch to another document by writing <goto next="cslogin.xml"/>. If, after examining cslogin.xml's vxml element, we discover that it has CustomerService.xml as its application root document, then we can safely assume that this transition is a "leaf-to-leaf" transition within the same application - all application-level state will be preserved.

If, however, you find the element <goto next="validateCreditCard.xml"/> in csmenu.xml, and upon inspection discover that the vxml element in validateCreditCard.xml either has no application attribute or has an application attribute with a value other than CustomerService.xml, then executing this goto element and entering validateCreditCard.xml would amount to leaving the CustomerService application.

In this case all of the application-level state would be lost, and the application scope would be reinitialized to that of the validateCreditCard.xml. If the VoiceXML interpreter were to return into the CustomerService application, it would reinitialize the state once again to the initial state of the CustomerService application.

There are two situations where transition between applications is a bit more complicated. One pertains to the use of subdialogs, and the other, to going from an application leaf document to its own application root document. These will be covered in the next two sections.

2.10.4. Subdialog transitions

The subdialog element calls another dialog not by simply passing control to it, but instead by "pushing it onto the stack." What this means is that while the subdialog is being visited, the state of the previously visited dialog is preserved on the call-stack, so when the subdialog returns, the calling dialog can resume where it left off. This pattern should be easy to recognize for anyone familiar with a language that supports either functions or procedures, such as C/C++, Java, or Pascal.

Therefore, if an application leaf document calls a subdialog, the original application state will not be available over the duration of the subdialog, but will become available again once the subdialog returns.

It is important to understand that when a sub-dialog is called with the same application-root as the caller the values of the application-level variables will not be available to the sub-dialog. The sub-dialog will see its own copy of these variables set to their initial values. Likewise, the values of any application-level variables set during the sub-dialog invocation will not be available to the calling dialog. The calling dialog will see the values it had seen immediately before calling the sub-dialog!

This can be especially confusing for programmers experienced with languages like C and Pascal where there is a notion of "global variables." These programmers might be surprised to see that, upon calling a sub-dialog, application-level variables have been reset to their initial values. Or, perhaps equally surprising is that all of the hard work done by their sub-dialog in setting application-level variables is lost once the sub-dialog return! In summary, application-level variables cannot be used to maintain state between a dialog and a sub-dialog. Sub-dialog parameters, return values, and HTTP parameters should be used instead.

For example, let's assume csmenu.xml is a leaf document of CustomerService.xml. If csmenu.xml contained the tag <subdialog next="validateCreditCard.xml#validate"> and validateCreditCard.xml was not a leaf document of CustomerService.xml, then the application-level variables declared in CustomerService.xml would not be visible to the dialog validateCreditCard.xml#validate. However, after returning back to the dialog in csmenu.xml, all previous variable values will be restored.

The handling of variables allows for generic subdialogs to be written that do not presuppose anything about the application environment but, at the same time, cannot do anything to disrupt that application environment. Subdialogs thus behave as black boxes, accepting input from the param elements associated with the subdialog call and returning output with return.

2.10.5. Leaf-to-root transitions

One last exotic document transition that may or may not reinitialize application state is the transition from an application leaf document to that document's own root application. This sort of transition will only cause the application-level state to be reinitialized if the transition was caused by a submit. All other transitions, including choice, link, and goto, will preserve the application state.

2.10.6. Additional application considerations

There are a few remaining details that deserve attention with regard to the organization of VoiceXML documents into applications.

One might wonder if an application leaf document can be the root document to another leaf document. In other words, if the document leaf.xml contains <vxml application="root.xml">, could root.xml contain <vxml application="superRoot.xml"> and thus expose leaf.xml to the application-level state of both root.xml and superRoot.xml?

As it turns out, the VoiceXML Specifications prohibit this sort of application chaining. If the VoiceXML interpreter encounters a leaf document whose root document is also a leaf document, it will throw a semantic error (error.semantic) when attempting to load the leaf document - in the above example, this would occur upon loading leaf.xml.

Another issue regards the naming conventions of application-level variables. The Specification indicates that application-level variables should be prefixed with the word "application" followed by a period. For example, the application-level variable custID, when used in leaf documents, should be referred to as application.custID.

As it turns out, many VoiceXML implementations do not follow this guideline, and instead simply refer to variables in the leaf documents by their name omitting the "application." prefix. In fact, the application shown in Example 2-25 (see 2.4.2, “A variable's scope,” on page 54) does not follow the Specification guidelines, but simply references the variable custID from the leaf documents. This is because the interpreters we tested on actually did not recognize the variable application.custID; they would only recognize custID. Part of the reason why this deviation is so prevalent is the way many ECMAScript interpreters are implemented. Only time will tell whether VoiceXML implementations change to meet the spec, or the spec changes to meet the market.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.14.84.90