CCXML 1.0-W3C Development GuideHome  |  Frameset Home

  tutorial Your First Dialog  |  TOC  |  tutorial Connecting Call Legs  

Tutorial: Advanced VoiceXML Dialogs

As we have now covered the basic concept of launching a VoiceXML dialog from the CCXML 1.0 context, and have studied how we can send events from the VoiceXML back into the CCXML 1.0 wrapper, it is now time to take a look at how we can make an application that provides a bit more robustness to the equation. If we fail to load our VoiceXML dialog properly, then we are really out of luck, and our callers are left with a less-than-satisfactory call experience, right? Not if we have anything to say about it. It goes without saying that you should really have a solid understanding of the previious tutorial on CCXML + VXML applications before attempting this, as we are going to skip over a lot of the "basics" that were covered previously.

In this tutorial, we will:

Note: The code presented within this tutorial is intended for the "Prophecy - CCXML 1.0" application type only. Attempts to utilize this code on the "CCXML - Voxeo" application type will not function, as the latter platform is based on the W3C specification from 2002.  Prophecy CCXML 1.0 is compliant to the most recent specification from 2006.


Step 1: creating our initial CCXML structure

We all know how this works. An XML header, and a CCXML header that contains an xml namespace:


<?xml version="1.0" encoding="UTF-8"?>
<!-- NOTE THAT WE *MUST* DECLARE THE xmlns ATTRIBUTE -->
<ccxml version="1.0" xmlns:voxeo="http://community.voxeo.com/xmlns/ccxml">
</ccxml>




Step 2: Setting up our framework

The first thing that we want to do after getting the basic stuff in place is to add in some variable declarations that we will be using later on in our tutorial. First thing we need to get defined is a variable for state control, which we should be pretty familiar with by now. As we plan on launching a "bad" dialog, and then handling it in a manner that doesn't impact the caller, we will declare a variable for both our "good" and "bad" dialogs. And really soon, we will need to define a "connectionID" value for our <dialogprepare> element to leverage, so we may as well get this declared, as well. Lastly, we will want to put in place some handlers for the various errors that can occur when we launch our dialogs, so we will add these in as well:



<?xml version="1.0" encoding="UTF-8"?>
<ccxml version="1.0" xmlns:voxeo="http://community.voxeo.com/xmlns/ccxml">

  <var name="mydialog" expr="''" />
  <var name="mydialog2" expr="''" />
  <var name="statevar" expr="'state0'" />
  <var name="connid" expr="''"/>

 
  <eventprocessor statevariable="statevar">
 
    <transition state="state0" event="connection.alerting">
      <accept/>
      <assign name="statevar" expr="'state1'"/>
    </transition>
   
    <transition state="state1" event="connection.connected">
      <assign name="connid" expr="event$.connectionid"/>
    </transition>
   
    <transition state="state3" event="dialog.exit">
      <log expr="'*** The dialog terminated ***'"/>
      <exit/>
    </transition>
           
    <transition state="state5" event="dialog.exit">
      <log expr="'*** The new dialog terminated, nice work ***'"/>
      <exit/>
    </transition>
   
    <transition event="error.*">
      <log expr="'Error occurred: ' + event$.reason"/>
      <exit/>
    </transition>
       
  </eventprocessor>
</ccxml>



Note that we also defined our "connid" variable with a value within the <transition> where the call actually connects, which will be filled with the unique "event$.connectionid" field value that is available within the "connection.connected" event: this will be important to us later on.


Step 3: Working with <dialogprepare>

The <dialogprepare> element is a very useful and powerful addition to the CCXML 1.0 specification. This element is used to get a dialog ready for actual execution via the <dialogstart> element, and allows us to do take appropriate action in the event that there is a problem with the dialog requested, and to handle it before a fatal error bubbles up into the session. As mentioned, we need to "marry" any particular dialog to a specific connectionid, (or conferenceid, depending on the scenario), and this is the reason why we took care to get this taken care of in our last step. We also have to make sure that we define a "dialogid" attribute when we prepare a dialog, and since we want to launch a bad dialog right off the bat, we will use the "mydialog" variable that we defined previously for this purpose.

So, what can happen when we invoke this new element? One of two things:



In the first case, we can then trap the event, and finally invoke the <dialogstart> to get the VXML dialog execution happening for our caller. Oh-ho, but in the event that the dialog fails, we can then trap the error, and switch to a secondary dialog target, without the caller ever knowing that Something Went Wrong. Which in this case, is what we want, so that we can show how it's all done. Let's take a look at the relevant code that we are going to add into our application for this contrived worst-case scenario:




<?xml version="1.0" encoding="UTF-8"?>
<ccxml version="1.0" xmlns:voxeo="http://community.voxeo.com/xmlns/ccxml">

  <var name="mydialog" expr="''" />
  <var name="mydialog2" expr="''" />
  <var name="statevar" expr="'state0'" />
  <var name="connid" expr="''"/>
 
  <eventprocessor statevariable="statevar">
 
    <transition state="state0" event="connection.alerting">
      <accept/>
      <assign name="statevar" expr="'state1'"/>
    </transition>
   
    <transition state="state1" event="connection.connected">
      <assign name="connid" expr="event$.connectionid"/>
      <assign name="statevar" expr="'state2'"/>
      <dialogprepare src="'bogus.vxml'" dialogid="mydialog" connectionid="connid"/>
    </transition>
   
    <transition state="state2" event="dialog.prepared">
      <!-- NOTE: this isnt going to happen -->
      <log expr="'*** The dialog has been prepared ***'"/>
      <dialogstart prepareddialogid="mydialog" connectionid="connid"/>
      <assign name="statevar" expr="'state3'"/>
    </transition>
   
    <transition state="state3" event="dialog.exit">
      <log expr="'*** The dialog terminated ***'"/>
      <exit/>
    </transition>
   
    <transition event="error.dialog.notprepared">
    </transition>

   
       
    <transition state="state5" event="dialog.exit">
      <log expr="'*** The new dialog terminated, nice work ***'"/>
      <exit/>
    </transition>
   
    <transition event="error.*">
      <log expr="'Error occurred: ' + event$.reason"/>
      <exit/>
    </transition>
       
  </eventprocessor>
</ccxml>



Those of you who understand the concept of foreshadowing will doubtlessly intuit that "Bogus.vxml" isn't going to load. Why, you ask? Let's take a look at the contents of the VXML file itself:



Internet Explorer cannot display the webpage
 
  Most likely causes:
    * You are not connected to the Internet.
    * The website is encountering problems.
    * There might be a typing error in the address.

  What you can try:
    Diagnose Connection Problems 

  * More information


Get it? There *is* no Bogus.vxml file exisitng on the webserver, so it's going to return a http 404 response code, which is just what we want. All the same, we add a <transition> in the CCXML 1.0 app that assumes that the dialog target is valid, so that we could play the dialog to the caller....but that isn't going to happen in this case. Instead, the "404" is going to tell the CCXML 1.0 interpreter thatit should throw the "dialog.notprepared" event, which leaves us some room to come up with Plan B.


Step 4: Implementing Plan B

Since we have proven beyond the shadow of a doubt that we know how to write code that will break, let's now see if we can clean up our mess, by launching an alternate dialog target. Since we may find it useful to find out why the initial dialog failed, we will also add a <log> statement within the "dialog.notprepared" handler to list out some useful diagnostic information for us. From right within our "dialog.notprepared" <transition>, we can just get another dialog target prepared using the same method that we did before. You'll note that we are still using a different "dialogid" than we did for "Bogus.vxml", but we need to change the application state if we want to hit the <transition> that contains our valid <dialogstart>. And of course, this means that we will want to have new <transition> elements declared for "dialog.prepared", "dialog.exit", and the like.


<?xml version="1.0" encoding="UTF-8"?>
<ccxml version="1.0" xmlns:voxeo="http://community.voxeo.com/xmlns/ccxml">

  <var name="mydialog" expr="''" />
  <var name="mydialog2" expr="''" />
  <var name="statevar" expr="'state0'" />
  <var name="connid" expr="''"/>
 
  <eventprocessor statevariable="statevar">
 
    <transition state="state0" event="connection.alerting">
      <accept/>
      <assign name="statevar" expr="'state1'"/>
    </transition>
   
    <transition state="state1" event="connection.connected">
      <assign name="connid" expr="event$.connectionid"/>
      <assign name="statevar" expr="'state2'"/>
      <dialogprepare src="'Bogus.vxml'" dialogid="mydialog" connectionid="connid"/>
    </transition>
   
    <transition state="state2" event="dialog.prepared">
      <log expr="'*** The dialog has been prepared ***'"/>
      <dialogstart prepareddialogid="mydialog" connectionid="connid"/>
      <assign name="statevar" expr="'state3'"/>
    </transition>
   
    <transition state="state3" event="dialog.exit">
      <log expr="'*** The dialog terminated ***'"/>
      <exit/>
    </transition>
   
    <transition state="state2" event="error.dialog.notprepared">
      <log expr="'An error occurred, and the dialog was NOT prepared: ' + event$.reason"/>
      <log expr="'Attempting to launch a new dialog...'"/>
      <assign name="statevar" expr="'state4'"/>
      <dialogprepare src="'RealDialog.vxml'" dialogid="mydialog2"/>

    </transition>
   
    <transition state="state4" event="dialog.prepared">
      <log expr="'*** The new dialog has been prepared ***'"/>     
      <dialogstart prepareddialogid="mydialog2" connectionid="connid"/>
      <assign name="statevar" expr="'state5'"/>
    </transition>

    <transition state="state4" event="error.dialog.notprepared">
      <log expr="'An error occurred, and the new dialog was NOT prepared: ' + event$.reason"/>
      <log expr="'Application will now exit...'"/>
      <exit/>
    </transition>

    <transition state="state5" event="dialog.exit">
      <log expr="'*** The new dialog terminated, nice work ***'"/>
      <exit/>
    </transition>
   
    <transition event="error.*">
      <log expr="'Error occurred: ' + event$.reason"/>
      <exit/>
    </transition>
       
  </eventprocessor>
</ccxml>


In this case the target is, in fact, valid, and the last hoop that we need to jump through is to make sure that the "preparedialogid" attribute of the <dialogstart> element matches the "dialogid" value that we declared when we prepared the dialog. That's all there is to it; we can now add an extra layer of protection to account for fetch/connectivity issues that can impact the launching of VXML dialogs from the CCXML 1.0 context.


  Download the CCXML source code!




  ANNOTATIONS: EXISTING POSTS
MattHenry
10/9/2007 12:20 PM (EDT)

Posting Courtesy of Mark Headd, IVR developer extraordinaire:


Because Prophecy 8.0.98.0 does not currently return an "error.dialog.notprepared" event, some additional steps are needed to obtain the results described above.  I have found a workaround that might be useful in the interim, until this functionality is added to Prophecy.

When using <dialogprepare/> the current version of Prophecy will always return a "dialog.prepared" event, even if the dialog is nonexistent or can not be prepared for some other reason.  When the Bogus.vxml dialog is initiated using <dialogstart/> and the "prepareddialogid" attribute, it will cause a "dialog.exit" event to land almost immediately.  When an error occurs in a VoiceXML dialog, Prophecy will set an "errorReason" property on the values object that is returned to CCXML.  This property will contain a description of the error that occurred.  Inspecting this property can help determine if the VoiceXML dialog was fetched properly.

To implement this workaround, the transition for dialog.exit when the application is in state3 needs to be modified:


    <transition state="state3" event="dialog.exit">
      <if cond="event$.values.errorReason.length > 0">
        <log expr="'*** There was a problem with the dialog: ' + event$.values.errorReason + '***'"/>
        <send target="session.id" name="'dialogNotFetched'"/>
      <else/>
        <log expr="'*** The dialog terminated ***'"/>
        <exit/>
      </if>
    </transition>


Additionally, a new transition must be added to mimic the behavior of the transition set up for an  "error.dialog.notprepared" event:


  <transition event="dialogNotFetched">
      <log expr="'An error occurred, and the dialog was NOT fetched properly."/>
      <assign name="statevar" expr="'state4'"/>
      <dialogprepare src="'RealDialog.vxml'" dialogid="mydialog2"/>
    </transition>


This workaround is not perfect.  As far as I can tell, the errorReason property is Voxeo-specific, and there isn't any way of telling whether the error in the VoiceXML dialog occurred before the caller heard anything (i.e., a bad fetch of the dialog iteslf), or after the dialog was started (i.e., a script or semantic error later in the dialog).

That said, it might be worth examining until the full <dialogprepare/> functionality is added to Prophecy.


MattHenry
10/9/2007 12:27 PM (EDT)

To add to Mark's suggestions above, I wanted to point out that the CCXML platform not throwing the "error.dialog.notprepared" event properly is fixed in more recent versions of the Prophecy 8 software.

For those who use a local deployment of P8, you would do well to check out Prophecy version 8.0.106, which does not suffer from this limitation. For those of you who use P7 hosted, you will want to  wait until we deploy P8 to the staging and production environments, as P7 doesn't really support the <dialogprepare> element fully.


~Matthew henry

login
  tutorial Your First Dialog  |  TOC  |  tutorial Connecting Call Legs  

© 2008 Voxeo Corporation  |  Voxeo IVR  |  VoiceXML & CCXML IVR Developer Site