Implementing 'Go Back'

Previous versions of the Tellme VoiceXML intepreter supported navigation to previously visited dialogs (aka "go back") using a Tellme Extension to the VoiceXML language, the anchor attribute of the form element. While this functionality has been deprecated, it can be emulated simply and effectively using VoiceXML and JavaScript. This document shows one way to implement "go back" functionality using a simple navigation history stack.

1. Understanding navigation history

Voice applications consists of a set of interconnected dialogs. VoiceXML provides several constructs for transitioning between dialogs. The most common of these, goto, allows you to specify a remote or local destination dialog using the next or expr attributes.

The following code snippet navigates to a dialog named "get_city".

<block>
   <goto next="#get_city"/>
</block>

The following code snippet also navigates to a dialog named "get_city", but it does so indirectly using the variable sDialog. You can change the value of sDialog at run-time, and the interpreter will navigate to the corresponding dialog. Specify a URL to navigate to a different document.

<vxml version="2.1"
 xmlns="http://www.w3.org/2001/vxml">
   <var name="sDialog" expr="'#get_city'"/>

   <form id="main">
      <block>
         <goto expr="sDialog"/>
      </block>
   </form>
</vxml>

A great voice user interface allows the user to navigate back to a previous dialog. In the following example, the user says "go back", and the application responds by reprompting the user for a city and state.

Tellme: Directory assistance. Say a city and state.
Caller: San Francisco, California.
Tellme: I heard you say San Francisco, California. If that's not right, say go back. (pause)
Tellme: What listing?
Caller: Go back.
Tellme: Say a city and state.
Caller: Mountain View, California.

In the next section you'll learn how to implement this functionality.

2. Implementing navigation history

In order to implement navigation history, you need to do three things:

2.1. Tracking visited dialogs

You can think of the list of dialogs to which a user has navigated as a stack. When the user visits a dialog, you add the URL of the dialog to the stack. When the user says "go back", you pop the last URL off the stack, and navigate to the URL.

To implement a stack, you can use a JavaScript array. JavaScript arrays are dynamic. You add an item to the end of the array using the push method and remove an item from the end of the array using the pop method.

<script>
    var aNavHistory = new Array();
    aNavHistory.push("dialog1");
    aNavHistory.push("dialog2");
    var sPrevDialog = aNavHistory.pop(); // returns dialog2
</script>

To allow the user to navigate back to a dialog they have visited, add its URL to the stack before navigating to the next dialog.

<form id="get_city">
   <!-- TODO: code to get a city from the user -->

   <block>
      <!-- push full url in case we jump to another document -->
      <script>aNavHistory.push("http://da.acme.org/voice/city.vxml#get_city");</script>
   </block>
</form>

If your voice application consists of multiple VoiceXML documents, you should declare the navigation history array at application scope. Since most applications consists of multiple VoiceXML documents in a hierarchy of directories, you may need to add the fully-qualified URL of the dialog to the history stack. By doing so, however, note that, when navigating back to a dialog in the current document using the history stack, the state of that document will be reinitialized. Any state that you want to preserve must be stored at application scope. For more information about variable scoping see Variable scoping in the documentation on variables.

2.2. Enabling the 'go back' grammar

To allow the user to generically navigate to a previous dialog, you need to add a command grammar that handles a request from the user to navigate to a previous dialog. "Go back" is the utterance commonly used to trigger this behavior. The following example demonstrates a link element that accepts the utterance "go back" and consequently throws the application-defined event myapp.ongoback. Use a name that is appropriate for your application.

<link event="myapp.ongoback">
   <grammar mode="voice" 
            type="application/application/srgs+xml"
            root="root_rule"
            tag-format="semantics/1.0"
            version="1.0"
            xml:lang="en-US"
            xmlns="http://www.w3.org/2001/06/grammar">
     <rule id="root_rule" scope="public">
       <item>
         <one-of>
           <item>
             go back
           </item>
         </one-of>
       </item>
     </rule>
   </grammar>
</link>

If you application consists of multiple VoiceXML documents, you should declare the link at application scope.

2.3. Implementing the event handler

Based on the code in the previous example, when the user says "go back", the VoiceXML interpreter throws the event myapp.ongoback. In your handler for that event, pop the last URL off the stack and navigate to it using the goto element. If the stack is empty, the interpreter jumps to a default URL defined in the application root. It is up to you to define that variable.

<catch event="myapp.ongoback">
   <script>
     var sJumpTo = (aNavHistory.pop() || application.sDefaultURL);   
  </script>
   <goto expr="sJumpTo"/>
</catch>

3. Putting it all together

The following is a complete listing of a simple application that implements a history stack. Because the application is contained in a single document, the navigation stack, aNavHistory and the "go back" link and corresponding event handler are contained at document scope. In addition, the URLs to each dialog are added to the navigation stack as document fragments rather than as fully qualified URLs.

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

<!-- navigation stack -->
<var name="aNavHistory" expr="new Array()"/>
<!-- where to jump if stack is empty -->
<var name="sDefaultURL" expr="'#main'"/>

<!-- from and to cities --> 
<var name="from"/>
<var name="to"/>

<!-- listen for 'go back' command -->
<link event="myapp.ongoback">
  <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>
          go back
        </item>
      </one-of>
    </rule>
  </grammar>
</link>

<!-- handle go back event -->
<catch event="myapp.ongoback">
   <var name="sJumpTo" expr="(aNavHistory.pop() || application.sDefaultURL)"/>
   <goto expr="sJumpTo"/>   
</catch>

<!-- allow the user to escape -->
<link event="myapp.onquit">
  <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>
          quit
        </item>
      </one-of>
    </rule>
  </grammar>
</link>

<catch event="myapp.onquit">
   <prompt>Thanks for using ACME travel</prompt>
   <exit/>
</catch>

<!-- entrypoint dialog -->
<form id="main">
<block>
   <prompt>Welcome to ACME travel.</prompt>
   <script>aNavHistory.push("#main")</script>
   <goto next="#from_city"/>
</block>
</form>

<!-- where from? -->
<form id="from_city">
<field name="from" slot="city">
   <prompt>Where are you flying from?</prompt>

   <grammar src="cities-voice.grxml" mode="voice" type="application/srgs+xml"/>

   <catch event="noinput nomatch">
      Sorry. I didn't get that.
      You can say quit at any time. 
      <reprompt/>
   </catch>

   <catch event="noinput nomatch" count="3">
      <throw event="myapp.onquit"/>
   </catch>

   <filled>
      <assign name="document.from" expr="from"/>
      <!-- add to history -->
      <script>aNavHistory.push("#from_city");</script>
      <goto next="#to_city"/>
   </filled>
</field>
</form>

<!-- where to? -->
<form id="to_city">
<field name="to" slot="city">
   <prompt>Where do you wanna go?</prompt>

   <grammar src="cities-voice.grxml" mode="voice" type="application/srgs+xml"/>

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

   <catch event="noinput nomatch" count="3">
      <throw event="myapp.onquit"/>
   </catch>

   <filled>
      <assign name="document.to" expr="to"/>
      <!-- add to history -->
      <script>aNavHistory.push("#to_city");</script>
      <goto next="#confirm"/>
   </filled>
</field>
</form>

<!-- did we get it right? -->
<form id="confirm">
<field name="ok" type="boolean">
   <prompt>
   You wanna fly from <value expr="from"/> to  <value expr="to"/>.
   If that's correct, say yes. If not, say no, or say go back.
   </prompt>
   <catch event="noinput nomatch">
      Sorry. I didn't understand. Please say yes or no.
   </catch>

   <catch event="noinput nomatch" count="3">
      <throw event="myapp.onquit"/>
   </catch>

   <filled>
     <if cond="ok">
       <goto next="#get_start_date"/>
     <else/>
       <throw event="myapp.ongoback"/>
     </if>   
   </filled>
</field>
</form>

<form id="get_start_date">
<block>
   <prompt>Get start date left as an exercise to the reader. Starting over</prompt>
   <break time="300ms"/>
   <script>aNavHistory.length = 0;</script>
   <goto next="#main"/>
</block>
</form>

</vxml>

The city grammar used by this application follows:

<?xml version= "1.0"?>


<grammar mode="voice"
         root="root_rule"
         tag-format="semantics/1.0"
         version="1.0"
         xml:lang="en-US"
         xmlns="http://www.w3.org/2001/06/grammar">
  <rule id="root_rule" scope="public">
    <one-of>
      <item>
        boston
        <tag>out.city = "boston";</tag>
      </item>
      <item>
        new
        york
        <tag>out.city = "new york";</tag>
      </item>
      <item>
        <one-of>
          <item>
            los
            angeles
          </item>
          <item>
            l
            a
          </item>
        </one-of>
        <tag>out.city = "los angeles";</tag>
      </item>
      <item>
        san
        francisco
        <tag>out.city = "san francisco";</tag>
      </item>
      <item>
        seattle
        <tag>out.city = "seattle";</tag>
      </item>
    </one-of>
  </rule>

</grammar>

[24]7 Inc.| Terms of Service| Privacy Policy| General Disclaimers