Dynamic menus

I am loading a bunch of yacht racing mark details: name, lat, lon, from my web site and want user to select one from a list.
I am able to dynamically load them into a menu, but can't fathom how to determine which one they've selected.
The onMenuItem(Item) faithfully returns the symbol, but I can't see a way to dynamically generate the item's id when loading the menu.

The web site returns a JSON array of dictionaries in loadedMarks which I use to build the menu:
var marksMenu=new Menu();
for (var i = 0; i < loadedMarks.size(); i++) {
marksMenu.addItem(loadedMarks["name"],:selected);
}
Ui.pushView(marksMenu, new MenuTestMenuDelegate(), Ui.SLIDE_UP);
[/code]
This of course doesn't work because it allocates the same id (symbol :selected) to each item. I'm looking for a way to identify which item he selected in my onMenuItem(Item) callback.


I note that the spec says "...This class should be extended to get the chosen Menu item." and wonder if the documentation guy had my problem in mind!
I also noted somewhere in the programmers guide that menus should not be created programatically, but what else is a poor boy to do?
  • using the item.hashCode() to get the value specified in the add menu item works on both my FR630 and Epix (actual devices)
  • Yes, I believe I've indicated that comparing the symbol against a Number does work.. for some values of Number. I believe it will work for small number values, but will fail for larger ones (try using 66600).

    Travis
  • Hi all, is there a better way to do this nowadays? Looking to achieve a dynamic menu in a widget based on results returned for a webcall (pick a day)

  • Sorry for the delay in responding, for some reason I'm not getting notification of replies to my posts.

    addItem(name as Lang.String or Lang.Symbol, identifier as Lang.Symbol) as Void

    The identifier, which the doco indicates must be a Symbol, is returned in the delegate's onMenuItem callback is not required to be a symbol as indicated in the doco. 

    Just pass it a number that you dynamically allocate from the web call (which probably returns a JSON).

    But don't forget that the number of items is limited to 16 entries.

  • Here's what I do in one of my apps. I do my own "menu" (really just a list).  Works with touch/button/button-touch devices.  Highlight a day, press select, and it does to a full details page

  • It is weird. The issue is that the value passed to onMenuItem() is always a Lang.Symbol regardless of what is passed into the addItem() call. The type is completely lost. Interestingly enough, the symbol it can successfully be used in comparisons using the equals operator (as opposed to the equals() method) to find the matching object...

    using Toybox.Application as App;
    using Toybox.Lang as Lang;
    using Toybox.System as Sys;
    using Toybox.WatchUi as Ui;

    class XMenu extends Ui.Menu
    {
    function initialize() {
    Menu.initialize();
    }
    }

    class XMenuInputDelegate extends Ui.MenuInputDelegate
    {
    hidden var _M_callback;

    function initialize(callback) {
    MenuInputDelegate.initialize();
    _M_callback = callback;
    }

    function onMenuItem(item) {
    return _M_callback.invoke(item);
    }
    }

    var _symbol = :symbol;
    var _number = 1;
    var _long = 37000l;
    var _float = 8.0;
    var _double = 10.0d;
    var _string = "abc";
    var _array = [ 1, "2" ];
    var _dict = { 1 => "2" };

    class XBehaviorDelegate extends Ui.BehaviorDelegate
    {
    function initialize() {
    BehaviorDelegate.initialize();
    }

    function onMenu() {
    var menu = new XMenu();

    menu.addItem("Symbol", _symbol);
    menu.addItem("Number", _number);
    menu.addItem("Long", _long);
    menu.addItem("Float", _float);
    menu.addItem("Double", _double);
    menu.addItem("String", _string);
    menu.addItem("Array", _array);
    menu.addItem("Dictionary", _dict);

    var delegate = new XMenuInputDelegate(self.method(:onMenuItem));

    Ui.pushView(menu, delegate, Ui.SLIDE_UP);

    return true;
    }

    function onMenuItem(item) {

    if (item instanceof Lang.Symbol) {
    Sys.print(item);
    }
    else if (item instanceof Lang.Number) {
    Sys.print(Lang.format("Number: $1$", [ item ]));
    }
    else if (item instanceof Lang.Long) {
    Sys.print(Lang.format("Long: $1$", [ item ]));
    }
    else if (item instanceof Lang.Float) {
    Sys.print(Lang.format("Float: $1$", [ item ]));
    }
    else if (item instanceof Lang.Double) {
    Sys.print(Lang.format("Double: $1$", [ item ]));
    }
    else if (item instanceof Lang.String) {
    Sys.print(Lang.format("String: $1$", [ item ]));
    }
    else if (item instanceof Lang.String) {
    Sys.print(Lang.format("String: $1$", [ item ]));
    }
    else if (item instanceof Lang.Array) {
    Sys.print(Lang.format("Array: $1$", [ item ]));
    }
    else if (item instanceof Lang.Dictionary) {
    Sys.print(Lang.format("Dictionary: $1$", [ item ]));
    }
    else {
    Sys.print(Lang.format("Unknown: $1$", [ item ]));
    }

    if (_symbol.equals(item)) {
    Sys.println(" match for symbol");
    }
    else if (_number.equals(item)) {
    Sys.println(" _number.equals(item)");
    }
    else if (_number == item) {
    Sys.println(" _number == item");
    }
    else if (_long.equals(item)) {
    Sys.println(" _long.equals(item)");
    }
    else if (_long == item) {
    Sys.println(" _long == item");
    }
    else if (_float.equals(item)) {
    Sys.println(" _float.equals(item)");
    }
    else if (_float == item) {
    Sys.println(" _float == item");
    }
    else if (_double.equals(item)) {
    Sys.println(" _double.equals(item)");
    }
    else if (_double == item) {
    Sys.println(" _double == item");
    }
    else if (_string.equals(item)) {
    Sys.println(" _string.equals(item)");
    }
    else if (_string == item) {
    Sys.println(" _string == item");
    }
    else if (_array.equals(item)) {
    Sys.println(" _array.equals(item)");
    }
    else if (_array == item) {
    Sys.println(" _array == item");
    }
    else if (_dict.equals(item)) {
    Sys.println(" _dict.equals(item)");
    }
    else if (_dict == item) {
    Sys.println(" _dict == item");
    }
    else {
    Sys.println(" unknown value");
    }
    }
    }

    class XView extends Ui.View
    {
    function initialize() {
    View.initialize();
    }
    }

    class XApp extends App.AppBase
    {
    function initialize() {
    AppBase.initialize();
    }

    function getInitialView() {
    return [ new XView(), new XBehaviorDelegate() ];
    }
    }


    If I run that testcase and select each menu item in order, I see the following output...

    Device Version 0.1.0
    Device id 1 name "A garmin device"
    Shell Version 0.1.0
    symbol (8389891) match for symbol
    symbol (1) _number == item
    symbol (16) _long == item
    symbol (1090519040) _float == item
    symbol (17) _double == item
    symbol (15) _string == item
    symbol (18) _array == item
    symbol (20) _dict == item
    Complete
    Connection Finished
    Closing shell and port


    If you have a situation like the initial post and you make a dynamic menu, it gets weird.

    using Toybox.Application as App;
    using Toybox.Lang as Lang;
    using Toybox.System as Sys;
    using Toybox.WatchUi as Ui;

    class XMenu extends Ui.Menu
    {
    function initialize() {
    Menu.initialize();
    }
    }

    class XMenuInputDelegate extends Ui.MenuInputDelegate
    {
    hidden var _M_callback;

    function initialize(callback) {
    MenuInputDelegate.initialize();
    _M_callback = callback;
    }

    function onMenuItem(item) {
    return _M_callback.invoke(item);
    }
    }

    var _array = [
    { "name" => "A", "lat" => 1.0, "lon" => 1.0 },
    { "name" => "B", "lat" => 2.0, "lon" => 2.0 },
    { "name" => "C", "lat" => 3.0, "lon" => 3.0 }
    ];

    class XBehaviorDelegate extends Ui.BehaviorDelegate
    {
    function initialize() {
    BehaviorDelegate.initialize();
    }

    function onMenu() {
    var menu = new XMenu();

    for (var i = 0; i < _array.size(); ++i) {
    menu.addItem(_array["name"], _array);
    }

    var delegate = new XMenuInputDelegate(self.method(:onMenuItem));

    Ui.pushView(menu, delegate, Ui.SLIDE_UP);

    return true;
    }

    function onMenuItem(item) {
    Sys.println(item["name"]);
    }
    }

    class XView extends Ui.View
    {
    function initialize() {
    View.initialize();
    }
    }

    class XApp extends App.AppBase
    {
    function initialize() {
    AppBase.initialize();
    }

    function getInitialView() {
    return [ new XView(), new XBehaviorDelegate() ];
    }
    }
    [/code]

    The problem is that you can't treat item as if it were a Lang.Dictionary (the type of the object passed in) inside onMenuItem(), because the type has been lost. You can work around this by searching the original array for the matching item (using the equals operator), and when you find a match you can access the item from the original array...

    function onMenuItem(item) {
    // Sys.println(item["name"]); // UnexpectedTypeException: Expected Object/Array/Dictionary, given Symbol

    // this works...
    for (var i = 0; i < _array.size(); ++i) {
    if (_array== item) {
    Sys.println(_array["name"]);
    }
    }
    }
    [/code]

    This is just as ugly as my original suggestion (using a lookup table of symbols) because the data has to be shared between the view and the delegate.

    As you've pointed out, you can pass a Lang.Number as the second parameter to addItem(), but that still has the same problem... you need to do the same lookup trick to avoid the UnexpectedTypeException you get from passing a Lang.Symbol to the array index operator.

    ________________________________

    Valley All Star Moving Service