HTML5 Zone is brought to you in partnership with:

Clark is a web evangelist for Microsoft based in Illinois. A Chicago native who can’t spell, Clark as a kid actually made his money building cars, getting grease under his nails. That art of building would later lead him to software development where he drinks the Web Development Kool-Aid. Writing code is what keeps Clark awake at night, while continually working on his craft and rapping with others over a few cold CORS. You can hear Clark muse about software on his podcast Developer Smackdown, or find his family cruising around in a 1968 Camaro SS. Clark is a DZone MVB and is not an employee of DZone and has posted 32 posts at DZone. You can read more from them at their website. View Full User Profile

31 Days of Windows 8 for HTML5 | Day #16: Context Menus

11.23.2012
| 4024 views |
  • submit to reddit

This article is Day #16 in a series called 31 Days of Windows 8.  Each of the articles in this series will be published for both HTML5/JS and XAML/C#. You can find additional resources, downloads, and source code on our website.

advertisementsample

Today, our focus is on context menus aka PopupMenu.  These are those small little popup commands that appear from time to time in your application when you right click on something. Microsoft offers some very specific guidance on when to use these context menus vs. when to use the AppBar control instead, so we will be following those rules in this article. 

What Is a Context Menu?

If you’ve used Windows 8 at all, you’ve likely encountered these before.  Often they result from a right-click on something you couldn’t select, or on text you wanted to interact with.  Here’s a quick sample of a context menu:

An image of the context menu for text shown over editable text while there is no text in the clipboard to paste.

(image from http://msdn.microsoft.com/library/windows/apps/Hh465308)

You could also launch a context menu from an element on your page that is unselectable, like this image in my sample app:

 image

Right-clicking the image launches the context menu center right above it.  (I will show you how to make this happen next.)  Each of the command items will have an action assigned to it, which is executed when the item is clicked.

Creating the Context Menu

Creating the Context Menu that you see above is pretty straight forward. To start with, we will wire up a handler  to our image element such that anytime someone right clicks on the object they will get our context menu. This looks like any other event listener, and we will listen on ‘contextmenu’ like such.

document.getElementById("myImage").addEventListener("contextmenu",
    imageContentHandler, false);

Next, we need to create our handler, which I have managed to call imageContentHandler, I know pretty awesome naming there. In our function we will simply start out by creating our PopupMenu object and adding the items we want to be shown. Each item can have its on event handler as well and in the example below I am just giving them all the same event handler of somethingHandler ( I know more awesome naming ).

var contextMenu = Windows.UI.Popups.PopupMenu();

contextMenu.commands.append(
    new Windows.UI.Popups.UICommand("Clark Sell", somethingHandler));

contextMenu.commands.append(
    new Windows.UI.Popups.UICommandSeparator());

contextMenu.commands.append(
    new Windows.UI.Popups.UICommand("Jeff Blankenburg", somethingHandler));

contextMenu.commands.append(
    new Windows.UI.Popups.UICommand("31 Days of Windows 8", somethingHandler));

contextMenu.commands.append(
    new Windows.UI.Popups.UICommand("Edit", somethingHandler));

contextMenu.commands.append(
    new Windows.UI.Popups.UICommand("Delete", somethingHandler));

With our PopupMenu defined, now we just need to show it. We can do that by calling showAsync and passing it some x,y coordinates like you see below.

contextMenu.showAsync({ x: [someCoords], y: [someCoords] });

Now how do we figure out where exactly to put it?

Determining The Location

You may have noticed that context menus appear directly above and centered to the element that has been selected.  This doesn’t happen by magic.  We’ll actually have to determine the position of the clicked element by ourselves (as well as any applicable offsets), and pass that along when we show the menu. Time to sharpen some pencils.

When a user click on our image, our contextmenu event is going to fire, calling our handler, imageContentHandler. It’s going to pass some arguments that we need to kick off our fact finding. There are three critical pieces of data we’re looking for here. 1. the x and y of the click and 2. the offset of the element that was its target and 3. the width of the target object. ( target object here is the image ) . Let’s see the code, and then come back to it.

function showWhereAbove(clickArgs) {
    var zoomFactor = document.documentElement.msContentZoomFactor;
    var position = {
        x: (clickArgs.pageX - clickArgs.offsetX - window.pageXOffset + (clickArgs.target.width / 2)) * zoomFactor,
        y: (clickArgs.pageY - clickArgs.offsetY - window.pageYOffset) * zoomFactor
    }

    return position;
}

So I have created this simple function called showWhereAbove taking something called the clickArgs. clickArgs is nothing more than the args that was passed into our contextmenu handler. To figure out both x and y are actually pretty much the same.

x = ( where x click was – offset to top of target – windows x offset + ( target width /2 )) * window zoom factor

Let’s continue to break that down

  • where x click was = the x position of the mouse at the time of click
  • offset to top of target = how much to the right of the elements left edge are we
  • window x offset = where are we in relation to the window being scrolled
  • target width / 2 = we need to move the PopupMenu to the middle of the object so we’re going to take the width and get middle
  • window zoom factor = if the screen is zoomed in put it in apply that factor

Now the y axis is the same in principal as x, except we don’t have to worry about the height of the target. Since we’re working with the bottom center of the context menu, just getting our y offset is enough to put the bottom of the PopupMenu in the right position.

Again remember  – we’re trying to position the the bottom middle of the Popup Menu. Now that we know our position, we just need to show it.

contextMenu.showAsync(showWhereAbove(args));

Here is the finished function:

function imageContentHandler(args) {
    var contextMenu = Windows.UI.Popups.PopupMenu();

    contextMenu.commands.append(
        new Windows.UI.Popups.UICommand("Clark Sell", somethingHandler));
    contextMenu.commands.append(
        new Windows.UI.Popups.UICommandSeparator());
    contextMenu.commands.append(
        new Windows.UI.Popups.UICommand("Jeff Blankenburg", somethingHandler));
    contextMenu.commands.append(
        new Windows.UI.Popups.UICommand("31 Days of Windows 8", somethingHandler));
    contextMenu.commands.append(
        new Windows.UI.Popups.UICommand("Edit", somethingHandler));
    contextMenu.commands.append(
        new Windows.UI.Popups.UICommand("Delete", somethingHandler));

    contextMenu.showAsync(showWhereAbove(args));
}

 

Launching a Context Menu From Selected Text

Actually, nearly everything about this process is the same, except for the math on where to pop the box from.  Initially when setting out to solve this problem, I was hoping to add on to the existing context menu that already appears when you right-click on text:

16-XAML-TextMenuDefault

As it turns out, you can’t ( at least that we’re aware of ).  So, for our example, let’s say that we want to keep all of those options, but also add a new one titled “Delete” at the bottom.  To do this, we’re going to have to create our own. While we know how to do so already there are two differences:

  • We need to place our PopupMenu at the top of our selected text and in the middle.
  • Stop the default behavior.

Like wire up an event handler to the contextmenu event of something like a <p> element.

document.getElementById("myInputBox").addEventListener(
            "contextmenu", inputHandler, false);

- tip – I am using a paragraph element here for demonstration. Because of that I need to make my element selectable. I did so in CSS like so

p {
    -ms-user-select: element;
}

Now we need to create our handler in this case inputHandler. Remember I said that we needed to prevent the default and do our own thing. To do so, we’re going to call preventDefault AND we’re going to see if some text was in fact selected. At that point, we can then crate our PopupMenu and show it.

function inputHandler(args) {

    args.preventDefault();

    if (isTextSelected()) {
        //create the PopupMenu and its items
     
    }
}

function isTextSelected() {
    return (document.getSelection().toString().length > 0);
}

After your PopupMeu is created we need to show it, but unlike before when we called showAsync() this time we need to call showForSectinoAsync. Since we’re going to show above the selection we need to understand what exactly was selected rather than just the element itself.

contextMenu.showForSelectionAsync(
    showForSelectionWhere(
        document.selection.createRange().getBoundingClientRect()));

You can see I have created again a helper function called showForSelectionWhere to take care of that math. We’re going to pass into it the bounding rectangle of our selection. Lets look at the code and then come back to it.

function showForSelectionWhere(boundingRect) {
    var zoomFactor = document.documentElement.msContentZoomFactor;

    var position = {
        x: (boundingRect.left +
            document.documentElement.scrollLeft -
            window.pageXOffset) * zoomFactor,

        y: (boundingRect.top +
            document.documentElement.scrollTop -
            window.pageYOffset) * zoomFactor,

        width: boundingRect.width * zoomFactor,
        height: boundingRect.height * zoomFactor
    }

    return position;
}

showForSelectionAsync takes an object with 4 properties; x, y, width, height. Let’s look at x quickly.

  • x = left edge of the bounding rect + any pixels scrolled within the container element – the window scroll  offset * zoom factor
  • y is basically the same as x, different axis.

Very similar to when we were working with the image element other than we have to figure out our position from within the element we we’re part of. The results should be as you would expect.

image

Summary

Today we looked at the art of creating inline context menus for our users.  They are an excellent way to provide interactions with elements that aren’t selectable, or for commands that make more sense directly adjacent to the element being interacted with.

If you would like to see the entire code sample from this article, click the icon below:

downloadHTML

Tomorrow, we will venture further into the Clipboard functionality available to us in Windows 8 development.  See you then!

downloadTheTools

Published at DZone with permission of Clark Sell, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)