Jay Phillips

Implementation of menu()

Posted by Jay Phillips on November 9th 2007 06:01 PM

Opening this thread so we can discuss the syntax for the menu() command, a dialplan method that should streamline the way IVRs are developed (and provide a realistic benefit over case statements).

  • tbehling (at monarchis)

    tbehling (at monarchis) November 9th, 2007 @ 06:36 PM

    Let me qualify this by saying I just got into Adhearsion last night, and I don't feel like I have quite found the optimal way to write apps for it yet. The main thing I am unclear about is how much logic should be in extensions.rb vs a helper or other library. I'm not using Activerecord, and I have a solidly separated model, but there's still a lot of controller logic going on.

    On the menu implementation, I think the lack of a nestable menu() method today causes the need for a lot of redundant code in extensions.rb. For example, I have basically this code 3 times over, to read off a menu, then accept a keypress, which is then used for the next nested menu. This example uses the Basecamp API to list all my projects, take a keypress for the user's choice, then (following this snippet) does largely the same thing to list the project's todo lists:

        bc = Basecamp.new(...)
    
        # List projects
        projects = bc.projects
        speak "Company " + projects[0][:company][:name]
        i = 1; choices = {}
        projects.each do |project|
            choices[i.to_s] = { :id => project[:id], :name => project[:name] }
            speak i.to_s + " for " + project[:name]
            i += 1
        end
    
        # Get project choice
        digit = wait_for_digit().to_s
        project_id = choices[digit][:id]
        speak "Lists for " + choices[digit][:name]
    

    I'll post separately about some brainstorming thoughts on a syntax.

  • tbehling (at monarchis)

    tbehling (at monarchis) November 9th, 2007 @ 07:12 PM

    Here's what I just came up with off the top of my head for a menu syntax. The assumption here is a standard IVR-type app, where the user e.g. has a menu of choices 1 - 9, chooses one, then either gets another menu or is asked for some kind of input (customer number, credit card number, etc.).

    Proposed methods for a menu() block:

    • append_to_speak:

    text passed as this sole parameter will be added to all "choice :speak" choices

    --------

    • store_result_in:

    converts its symbol to an instance variable (?), which will contain the :value of choice()

    --------

    • auto_index:

    if present, :index for choice() is not needed; choices will be assigned indexes starting at 1, in sequence

    --------

    • choice:

    param is hash of :speak or :play or :background, :index, and :value

    :speak causes its value to be passed to speak(), including append_speak() text if present. Special token ## will be replaced with the value of :index

    :play passes its value to play(), interpolating ##

    :background passes its value to background(), interpolating ##

    :index is interpolated into the values above (must be unique within menu). If :index is a single digit, wait_for_digit is invoked after all choices are read (see below)

    :value is stored in the variable specified by store_result_in(), if this choice is chosen; defaults to :index if not specified

    --------

    At the end of this, the chosen choice's :value could either be returned from menu(), or used within it for a nested menu call

    result = menu do
      store_result_in :result
      prepend_to_speak "For "
      append_to_speak " press ##"
      #auto_index
    
      choice :speak => "first option", :index => 1, :value => 1002
      choice :play => "hello-world", :index => 2, :value => "second"
      choice :speak => "third option", :index => 3, :value => some_method()
    end
    
    result2 = menu do
      store_result_in :result
      prepend_to_speak "For "
      append_to_speak " press ##"
      auto_index
    
      choice :speak => "first option"
      choice :play => "hello-world"
      choice :speak => "third option"
    end
    
  • tbehling (at monarchis)

    tbehling (at monarchis) November 9th, 2007 @ 07:50 PM

    I was just thinking more, and store_result_in() should be eliminated, and replaced with an instance variable that's reached with an accessor method like selection(). selection() would call wait_for_digit() if no selection had been made yet, allowing for syntax like:

    result = menu do
      auto_index
      choice :play => "hello-world"
      choice :play => "something-else"
    
      case selection
        when 1
          speak "you chose 1"
       end
    end
    

    Also, auto_index should not be necessary; choice() should, by default, increment an instance-variable index counter by 1, starting at 1. :index can be overridden for an individual choice, though, to allow skipping numbers; subsequent choice() calls would then increment from that index:

    result = menu do
      choice :speak => "first"   # 1
      choice :play => "something-else"   # 2
      choice :play => "something-else", :index => 5    #5
      choice :play => "something-else"   # 6
    end
    

    I think there should be an invalid_option method to follow choice()s, in case a bad choice is made, that would specify what to do, with the same :speak and :play options as choice(), plus :recover, which is either :repeat (re-invokes menu; possible?) or :return (returns nil). prepend_to_speak and append_to_speak would not apply here.

    invalid_option :speak => "## is an invalid selection", :recover => :repeat

  • Jay Phillips

    Jay Phillips November 9th, 2007 @ 07:56 PM

    Here's a mockup I came up with. Comments please.

    ivr {
      menu do |choice|
        choice.named "sales", :context => sales
        choice.named "technical support" do
          # Submenu maybe
        end
        choice.named "the operator", :key => 0 do
          dial :operator
        end
        choice.invalid :tries => 3 do
          speak "Sorry but that is not a valid extension. Please try again"
        end
        choice.when_done
      end
    }
    sales {
      speak "All sales representatives are presently busy."
    }
    

    kthxbai

  • tbehling (at monarchis)

    tbehling (at monarchis) November 9th, 2007 @ 08:08 PM

    Your mockup has some interesting features. I like having the blocks on the choices; makes a multilevel menu make more sense. I wish "choice." weren't needed everywhere. How would named() behave? Do you think replacing that with speak() and play() would make sense? What does when_done() do? I think what I was trying to achieve in my mockup was to return a defined value (which might not be the same as the keypress). I think there could be some really powerful but simple syntax that could come from integrating our two mockups... let me try that in a following comment.

You must be a project member to post a new comment.

New-ticket Create new ticket

Create your profile

Help contribute to this project by taking a few moments to create your personal profile. Create your profile ยป

Shared Ticket Bins

Recent Comment Activity

People watching this message