tags:

views:

805

answers:

2

I've got a couple menus like this:

// Contextual Menu
// triggers
<div id="contextMenuTrigger0">0</div>
<div id="contextMenuTrigger1">1</div>
// menu
<div dojoType="dijit.Menu" 
     targetNodeIds="contextMenuTrigger0, contextMenuTrigger1" 
     leftClicktoOpen="true" style="display:none">
      <div dojoType="dijit.MenuItem" class="first">Item One</div>
      <div dojoType="dijit.MenuItem">Item Two</div>
      <div dojoType="dijit.MenuItem">Item Three</div>
      <div dojoType="dijit.MenuItem">Item Four is really, really long item.</div>
</div>

and this:

// Tools Menu
// trigger
<div id="toolsButton">Tools</div>
// menu
<div dojoType="dijit.Menu" class="toolsMenu" 
     targetNodeIds="toolsButton" 
     leftClicktoOpen="true" style="display:none">
      <div dojoType="dijit.MenuItem" class="first">Item One</div>
      <div dojoType="dijit.MenuItem">Item Two</div>
      <div dojoType="dijit.MenuItem">Item Three</div>
      <div dojoType="dijit.MenuItem">Item Four</div>
</div>

Right now, when the menu opens, it appears under the mouse. I want it to appear in a specific position relative to the trigger*. I found the startup and onOpen events and tried writing a function that sets the style of the menu's domNode in there, but they didn't seem to take effect.

Also, I didn't see a way of finding out which node was the trigger in the context case where there are multiple ones.

I saw this & this, but wasn't able to get much further with 'em.

* FWIW, I want them positioned so that the top-left corner of the menu is aligned with the top-right corner of the context triggers, and with the bottom-left corner of the Tools menu.

A: 

As I can see from dijit.Menu source code the feature you want isn't supported out of box.
What I can think of is declaring a new widget inheriting from dijit.Menu and override bindDomNode method. It binds _openMyself handler to onClick event like this:

dojo.connect(cn, (this.leftClickToOpen)?"onclick":"oncontextmenu", this, "_openMyself")

_openMyself handler takes coords from the event object that comes in as an argument.
So the idea is to pass a fabricated event object with the desired coords.

dojo.connect(cn, (this.leftClickToOpen)?"onclick":"oncontextmenu", this, function(){
  var e = { target: desiredTarget, pageX: desiredX, pageY: desiredY };
  this._openMyself(e);
});
Kniganapolke
A: 

It turns out that dojo.popup.open (which I guess Menu inherits from) has a parameter (orient) that you can use to orient a menu relative to a node. I wound up defining a custom trigger class that knows how to take advantage of that. (I also created sub-classes for other menu-types that have different orientations, but I'll leave those out for clarity's sake.)

UPDATE: according to this page, the variable substitution method I was using in the templateString isn't recommended. Instead, you're supposed to create an attributeMap, which I've done below.

http://docs.dojocampus.org/quickstart/writingWidgets

// Define a basic MenuTrigger
dojo.declare("my.MenuTrigger", [dijit._Widget, dijit._Templated], {
    // summary:
    //      A button that shows a popup.
    //      Supply label and popup as parameter when instantiating this widget.

    label: null,
    orient: {'BL': 'TL', 'BR': 'TR'}, // see http://api.dojotoolkit.org/jsdoc/1.3.2/dijit.popup.__OpenArgs (orient)
    templateString: "<a  href='#' class='button enabled' dojoAttachEvent='onclick: openPopup' onClick='return false;' ><span dojoAttachPoint='labelNode'></span></a>",
    disabled: false,
    attributeMap: {
        label: {
            node: "labelNode",
            type: "innerHTML"
        }
    },

    openPopup: function(){
        if (this.disabled) return;
        var self = this;

        dijit.popup.open({
            popup: this.popup,
            parent: this,
            around: this.domNode,
            orient: this.orient,
            onCancel: function(){
                console.log(self.id + ": cancel of child");
            },
            onExecute: function(){
                console.log(self.id + ": execute of child");
                dijit.popup.close(self.popup);
                self.open = false;
            }
        });

        this.open = true;
    },

    closePopup: function(){
        if(this.open){
            console.log(this.id + ": close popup due to blur");
            dijit.popup.close(this.popup);
            this.open = false;
        }
    },

    toggleDisabled: function() {
        this.disabled = !this.disabled
        dojo.toggleClass(this.domNode, 'buttonDisabled');
        dojo.toggleClass(this.domNode, 'enabled');
        dojo.attr(this.domNode, 'disabled', this.disabled);
    },

    _onBlur: function(){
        // summary:
        //      This is called from focus manager and when we get the signal we
        //      need to close the drop down
        //      (note: I don't fully understand where this comes from
        //      I couldn't find docs. Got the code from this example:
        //      http://archive.dojotoolkit.org/nightly/dojotoolkit/dijit/tests/_base/test_popup.html
        this.closePopup();
    }
});

// create some menus & triggers and put them on the page
dojo.addOnLoad(function(){
    // MENU
    cMenu = new dijit.Menu();
    cMenu.addChild(new dijit.MenuItem({ label: "First Item" }));
    cMenu.addChild(new dijit.MenuItem({ label: "Second Item" }));
    cMenu.addChild(new dijit.MenuItem({ label: "Third Item" }));
    cMenu.addChild(new dijit.MenuItem({ label: "Fourth Item is truly a really, really, really long item" }));

    // TRIGGER
    cTrigger = new my.MenuTrigger({
        id: "cTrigger",
        popup: cMenu
    }).placeAt(dojo.body());
    cTrigger = new my.MenuTrigger({
        id: "cTrigger2",
        popup: cMenu
    }).placeAt(dojo.byId('contextTriggerContainer2'));
 });
sprugman