3
\$\begingroup\$

I've taken a stab at making a context menu using BackboneJS.

Here's the overall structure:

Collections:

  • /collection/contextMenuGroups
  • /collection/contextMenuItems

Models:

  • /model/contextMenu
  • /model/contextMenuGroup
  • /model/contextMenuItem

Views:

  • /view/contextMenuView

To start off, lets take a look at my template:

<script type="text/template" id="contextMenuTemplate">

    <% groups.each(function(group){ %>
        <ul id="group_<%= group.cid %>">
            <% group.get('items').each(function(item){ %>
               <li>
                   <a id="groupItem_<%= item.cid %>" href="#"><%= item.get('text') %></a>
               </li>
            <% }); %>
        </ul>
    <% }); %>

</script>

My template is created such that each contextMenuGroup provided to it creates a new unordered list. Each item in each group creates a link inside of a listItem for the given unordered list.

Here's my view:

define(['contextMenu'], function (ContextMenu) {
    'use strict';

    var ContextMenuView = Backbone.View.extend({

        className: 'contextMenu',

        template: _.template($('#contextMenuTemplate').html()),

        parentSelector: 'body',

        render: function () {
            this.$el.html(this.template(this.model.toJSON()));

            //  Prevent display outside viewport.
            var offsetTop = this.top;
            var needsVerticalFlip = offsetTop + this.$el.height() > $(this.parentSelector).height();
            if (needsVerticalFlip) {
                offsetTop = offsetTop - this.$el.height();
            }

            var offsetLeft = this.left;
            var needsHorizontalFlip = offsetLeft + this.$el.width() > $(this.parentSelector).width();
            if (needsHorizontalFlip) {
                offsetLeft = offsetLeft - this.$el.width();
            }

            //  Show the element before setting offset to ensure correct positioning.
            this.$el.show().offset({
                top: offsetTop,
                left: offsetLeft
            });

            return this;
        },

        initialize: function () {
            //  TODO: If I implement Backbone View's more properly, then 'body' should be responsible for this, but for now this is fine.
            this.$el.appendTo(this.parentSelector);

            var self = this;
            //  Hide the context menu whenever any click occurs not just when selecting an item.
            $(this.parentSelector).on('click contextmenu', function () {
                self.$el.hide();
            });
        },

        show: function (options) {
            if (options.top === undefined || options.left === undefined) throw "ContextMenu must be shown with top/left coordinates.";
            if (options.groups === undefined) throw "ContextMenu needs ContextMenuGroups to be shown.";

            this.top = options.top;
            this.left = options.left;

            this.model = new ContextMenu({
                groups: options.groups
            });

            this.render();
        }
    });

    return ContextMenuView;
});

To display a contextMenu, I call show and pass to show top/left coordinates as well as a model to render inside of the context menu.

Usage:

showContextMenu: function (event, queueItem) {

    var queueContextMenuGroups = [{
        position: 0,
        items: [{
            position: 0,
            text: 'Clear Queue'
        }]
    }];

    if (queueItem) {
        queueContextMenuGroups.push({
            position: 1,
            items: [{
                position: 0,
                text: 'Remove ' + queueItem.get('title')
            }]
        });
    }

    this.contextMenuView.show({
        top: event.pageY,
        left: event.pageX + 1,
        groups: queueContextMenuGroups
    });

    return false;
}

Here, I create an anonymous array of objects (groups) which contains some items to render.

This all works great, but I have no idea how to bind generic events! Should I be extending my ContextMenuView with a 'specific' implementation which can define its events?

\$\endgroup\$
3
\$\begingroup\$

I like the code as is,

  • Use of 'use strict'
  • JsHint cannot find anything
  • Readable, well named variables
  • No obvious copy pasting or repeated code

As for events, I would

  • Wait until everything is rendered
  • Add a $.click() to each menu item, item by selecting on a classname used only by those menu items, or you could re-build the id's of each menu item and attach to each one individually ( first approach would be much better ).
  • Then I would use $.trigger() to send out an event with the text of the button
  • Register whoever needs to know that a menu item was clicked for that event
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.