Implementation of menu()
5 replies
-
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.
-
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 -
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 endAlso, 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 endI 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
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)
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 logged in as a project member to reply.