Lighthouse has a new layout. Prefer the old one? Return to the old layout, and switch back any time from the link at the top of each page.

Implementation of menu()

Jay Phillips

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).

Posted by Jay Phillips · November 9th, 2007 @ 06:01 PM

5 replies

  1. tbehling (at monarchis)
    tbehling (at monarchis)

    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.

    November 9th, 2007 @ 06:36 PM

  2. tbehling (at monarchis)
    tbehling (at monarchis)

    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
    

    November 9th, 2007 @ 07:12 PM

  3. tbehling (at monarchis)
    tbehling (at monarchis)

    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 SimonGame component number doesn't reset number after end of game
      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

    November 9th, 2007 @ 07:50 PM

  4. Jay Phillips
    Jay Phillips

    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

    November 9th, 2007 @ 07:56 PM

  5. tbehling (at monarchis)
    tbehling (at monarchis)

    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.

    November 9th, 2007 @ 08:08 PM

You must be logged in as a project member to reply.