List Navigation

A list is one of the most commonly used interface elements in a voice application. Here are some examples of lists you might find in a voice application:

  • A items in a virtual shopping cart.
  • The movies playing at a particular theater.
  • The restaurants in a particular neighborhood.
  • The stocks in a portfolio.

Lists can be implemented in many ways. The most basic list plays one item after another and provides a grammar containing the name of each item in the list. Such a list provides little in the way of navigation beyond the ability to repeat the entire list once the end of the list is reached.

This example uses a simple shopping list to demonstrate basic list navigation. It presents each item to the user and listens for the following commands: "next", "previous", "stop", and "repeat."

Try It!
  1. Set your Application URL to http://studio.247-inc.net/library2/code/ex-104/index.vxml
  2. Call the number shown in VXML Tools page
  3. Sign in, and play!

How it works

The items in the shopping list are fetched from an external data source using the data element. The contents of items.xml follows:

<?xml version="1.0"?>
<?access-control allow="*"?>
<items>
  <item>non-fat yogurt</item>
  <item>loaf of ciabatta</item>
  <item>granola</item>
  <item>unsweetened apple sauce</item>
  <item>grapefruit juice </item>
  <item>bananas</item>
  <item>broccoli rabe</item>
  <item>bag of oranges</item>
  <item>1 pound of fresh flounder</item>
  <item>1 pound of sugar</item>
  <item>dish detergent</item>
  <item>fabric softener</item>
</items>

Although the data in this example is stored in a static XML file, you can retrieve data from a database management system (DBMS) on the fly by authoring a server-side script (e.g. Java Server Page) to extract the data from the DBMS and return it to the VoiceXML interpreter as XML.

Once the data has been retrieved, the interpreter parses the XML and reflects it into the application as a set of ECMAScript objects conforming to the Document Object Model specification (DOM). The code calls a user-defined function, ParseItems, to extract the items from the DOM and populate an ECMAScript array. To play each item in the array in succession, the application maintains an index variable, iIndex, that is initialized to 0.

The prompt element plays the current item in the array to the caller. If the caller says nothing within the designated timeout, a noinput event is thrown, and the noinput handler throws a custom event event.mycompany.myapp.next. The handler for this event checks for the end of the list and otherwise increments iIndex by one. If the caller says, "Next," the same custom event is thrown, thus yielding the same result. In this scenario, the execution of the clear element is necessary to reset the form item variable command. This causes the VoiceXML interpreter to resume form interpretation (FIA) for the PlayItems dialog.

If the user says previous, the filled element is executed, and the conditional throws the custom event event.mycompany.myapp.previous. The handler for this event checks for the beginning of the list and otherwise decrements iIndex by one. To resume interpretation of the PlayItems dialog, it clears the associated form item variable.

To support repetition of the current element, when the user says "repeat", the VoiceXML interpreter simply clears the form item variable command and executes a reprompt.

To support user-initiated list termination, when the user says "stop", the VoiceXML interpreter simply navigates to the "again" dialog where the user is given the opportunity to hear the list again by responding to a simple confirmation dialog that takes advantage of the built-in boolean grammar.

VoiceXML 2.x Code

The VoiceXML code for this example follows:

<?xml version="1.0"?>
<!-- 
Tellme Studio Code Example 104 
Copyright (C) 2005-2006 Tellme Networks, Inc. All Rights Reserved. 

THIS CODE IS MADE AVAILABLE SOLELY ON AN "AS IS" BASIS, WITHOUT WARRANTY 
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, 
WARRANTIES THAT THE CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A 
PARTICULAR PURPOSE OR NON-INFRINGING. 
--> 

<vxml version="2.1"
 xmlns="http://www.w3.org/2001/vxml">

<var name="common_audio_url" expr="'http://naturalsound.svc.tellme.com/common-audio/'"/>

<!-- array of items. We'll fetch these from an external data source -->
<var name="aItems" expr="new Array()"/>

<script><![CDATA[
// Simple function get list of items out of
// fetched XML data
function ParseItems(dom)
{
  var a = new Array();
  var r = dom.documentElement;
  if (r.nodeName != 'items') {
    vxmllog('unexpected root node ' + r.nodeName);    
  } else {
    for (var i = 0; i < r.childNodes.length; i++) {
      var n = r.childNodes.item(i);
      if (n.nodeType == Node.ELEMENT_NODE && 
          n.nodeName == 'item') {
          a.push(n.firstChild.data);          
      }
    }
  }
  return a;
}
]]></script>

 <!--*********************************************************
 Previous Item: decrement the counter and play it.
 **********************************************************-->   
<catch event="event.mycompany.myapp.previous">
    <if cond="iIndex &gt; 0">
        <audio expr="common_audio_url + 'listprev-se.wav'"/>
        <assign name="iIndex" expr="iIndex - 1"/>
     <else/>
        <prompt> You are at the beginning of the list </prompt>
     </if>    
     <clear namelist="command"/>
     <reprompt/> 
</catch>

<!--*********************************************************
Next Item: Throws a no input events which gets the next item
**********************************************************-->           
<catch event="event.mycompany.myapp.next">
    <if cond="iIndex &lt; aItems.length - 1">
       <audio expr="common_audio_url + 'listnext-se.wav'"/>
       <assign name="iIndex" expr="iIndex + 1"/>
       <clear namelist="command"/>
       <reprompt/> 
    <else/>
       <audio expr="common_audio_url + 'listend-se.wav'"/>
       <prompt> That's all the items </prompt>
       <goto next="#again"/>
    </if>
</catch>

   <!--*************************************************************
   Entry Form
   *************************************************************-->

   <form id="ProgramIntro"> 
   <catch event="error.badfetch">
     Sorry. We were unable to retrieve your grocery list.
     <disconnect/>
   </catch>

   <block> 
      <!-- fetch the list of items -->
      <data name="domItems" src="items.xml"/>
      <!-- populate the array -->
      <assign name="aItems" expr="ParseItems(domItems)"/>
      <if cond="aItems.length &gt; 0">
        Here are the items in your grocery list. 
        You can say PREVIOUS, NEXT, or REPEAT to navigate through the list. 
        <goto next="#PlayItems"/> 
      <else/>
        There are no items in your grocery list.
        <disconnect/>
      </if>
   </block> 
   </form> 
   
   <!--*********************************************************
   State PlayItems: Play each item and wait for user input
   **********************************************************-->

   <form id="PlayItems">

   <!--*********************************************************
   Variables
   **********************************************************-->  
   <var name="iIndex" expr="0"/>
    
   <field name="command"> 
      
      <!--*************************************************************
      'timeout' property is the time it takes before a noinput event
      is thrown as a reslult of silence.  
      *************************************************************-->

      <property name="timeout" value="0.5"/>
      
      
      <grammar mode="dtmf"
               root="root_rule"
               tag-format="semantics/1.0"
               type="application/srgs+xml"
               version="1.0">
        <rule id="root_rule" scope="public">
          <one-of>
            <item>
              <item repeat="1-">
                4
              </item>
              <tag>out.command = "previtem";</tag>
            </item>
            <item>
              <item repeat="1-">
                6
              </item>
              <tag>out.command = "nextitem";</tag>
            </item>
            <item>
              <item>
                * 7
              </item>
              <tag>out.command = "stop";</tag>
            </item>
            <item>
              <item>
                * 5
              </item>
              <tag>out.command = "repeat";</tag>
            </item>
          </one-of>
        </rule>
     </grammar>

     <grammar mode="voice"
              root="root_rule"
              tag-format="semantics/1.0"
              type="application/srgs+xml"
              version="1.0"
              xml:lang="en-US">
       <rule id="root_rule" scope="public">
         <one-of>
           <item>
             <item>
               previous
             </item>
             <tag>out.command = "previtem";</tag>
           </item>
           <item>
             <item>
               next
             </item>
             <tag>out.command = "nextitem";</tag>
           </item>
           <item>
             <item weight="0.1">
               stop
             </item>
             <tag>out.command = "stop";</tag>
           </item>
           <item>
             <item weight="0.001">
               repeat
             </item>
             <tag>out.command = "repeat";</tag>
           </item>
         </one-of>
       </rule>
    </grammar>

      
      <!--*********************************************************
      Play prompt
      **********************************************************-->
      <prompt>
         <value expr="aItems[iIndex]"/>
      </prompt>  
      
      <!--*********************************************************
      In case of no input, increment the counter and play
      the next item.  If last item exit the module
      **********************************************************-->      
      <noinput>
         <throw event="event.mycompany.myapp.next"/>
      </noinput>

      <nomatch> 
         Sorry. I didn't get that. You can say next, previous, or stop while navigating through the list.
         <break time="300ms"/>
      </nomatch>

     <filled>   
      <if cond="command == 'previtem'">
           <throw event="event.mycompany.myapp.previous"/>       
      <elseif cond="command == 'nextitem'"/>
           <throw event="event.mycompany.myapp.next"/>       
      <elseif cond="command == 'stop'"/>
          <audio expr="common_audio_url + 'stop-se.wav'"/>
          <goto next="#again"/>
      <elseif cond="command == 'repeat'"/>
          <clear namelist="command"/>
          <reprompt/> 
      </if>     
     </filled>
   </field>
   </form>
   
   <!--*************************************************************
   Ask the user if they want to hear the list again
   *************************************************************-->

   <form id="again">
   <field name="yesno" type="boolean">
      <prompt> Do you want to hear the list again? </prompt>

      <catch event="noinput nomatch">
         Sorry.  I didn't get that.
         <reprompt/>
      </catch>

      <filled>
         <if cond="yesno">
            <goto next="#PlayItems"/>
         <else/>
            <exit/>
         </if>
      </filled>
   </field>
   </form>
   
</vxml>