Mouse context menu in a mixin


#1

Hi,

I want to use a jQuery contextMenu in a mixin to be able to use it in a more generic way along the ember app but, for some reason, it doesn’t seem to render at all. The menu works fine when I create it within the component but it doesn’t work when it’s in the mixin. Although in the following jsbin doesn’t work either way, in an ember-cli project it works when the menu is in the component:


#2

Henry, take a look at this jsbin.


You need the Component suffix in your JS objects.

This was really silly, it took me by surprise why the component code wasn’t working. Ember expects you to suffix the component name with Component and since you had a MyComponent, you actually needed MyComponentComponent.

This is why you had to use IndexView to get it to work, which you should avoid since they are deprecated now.

App.MyComponent        ->   App.MyComponentComponent
App.AnotherComponent   ->   App.AnotherComponentComponent

Bind jQuery.contextMenu to the component element

Instead of wrapping your component in a <div> with an specific CSS class, use the component’s element.

Sadly, I noticed that jQuery.contextMenu doesn’t allow you to pass over a DOM element and always expects a CSS selector. Since all components have a unique DOM ID, you can use that as a selector.

Ember.$.contextMenu({
    selector: '#' + this.get('elementId')
})

Note that I’m using Ember.$ instead of this.$(). The reason for this is that Ember.$ is an alias to jQuery, and this.$() is an alias to jQuery(componentElement).

This is very important to your mixin, because this.$().contextMenu() will look for the ID inside your component (this won’t work since you are looking for an ID inside your element#ID); while Ember.$.contextMenu() will look for the ID at the document root’s level (which works).


Destroy your jQuery.contextMenu() when the component is going to be removed from the DOM

To avoid errors and memory leaks, you should always clear any jQuery events or plugins that you use. This is particularly easier on a component than in a route (which needs to bubble a few times to get the DOM element).

destroyContextMenu: function() {
  Ember.$.contextMenu('destroy', '#' + this.get('elementId'));
}.on('willDestroyElement')

Finally, I don’t think this needs to be a mixin at all. This could easily be a component and extend it.

Ember.Component.extend(App.contextMenuComponent, {
  contextMenuItems: {},
  contextMenuCallback: function() {}
});

Final note: Whenever you implement something like this, always provide a way to override properties sent to the plugin. See the contextMenuItems and contextMenuCallback properties.

Hope it helps you out, good luck.


#4

Woah, I don’t know why I didn’t see your reply before but huge thanks for it! you have explained me a lot of stuff I haven’t realized. Thank you very much.

Just a few questions:

Whenever you implement something like this, always provide a way to override properties sent to the plugin. See the contextMenuItems and contextMenuCallback properties.

In this case, contextMenuCallback works in a different context than contextMenuItems but I need this last one to check the matches with the key parameter (or sending the action associated with that item). Is there a right way to be able to access the context of the contextMenu and also allow override the property? is it right to set something like:

var contextParent = this;
...
build: function ($trigger, e) {
        return {
          contextParent: contextParent,
          ...

Finally, I don’t think this needs to be a mixin at all. This could easily be a component and extend it.

Correct me if I’m wrong but, if this is a component I can’t extend a controller from it, right? so I couldn’t have contextMenus for controllers…


#5

Henry,

Is there a right way to be able to access the context of the contextMenu and also allow override the property?

Let’s see, you can definitely pass over a context that way, or you could probably call the function and pass over a context variable as an argument. At this point, I don’t think there is a “right way” of doing things.

Correct me if I’m wrong but, if this is a component I can’t extend a controller from it, right? so I couldn’t have contextMenus for controllers…

And yes, you are right. You can’t extend a Controller with a Component, but you probably shouldn’t anyway. This type of functionality you are writing is a great use case for a Component, not a RouteController. And the RouteControllers will be deprecated soon after 2.0, so you are probably best of sticking with Components or Mixins.


#6

Thanks again. It’s very clear now :blush: