Subversion Repositories Integrator Subversion

Rev

Blame | Last modification | View Log | Download | RSS feed

/*!
 * Bootstrap Context Menu
 * Author: @sydcanem
 * https://github.com/sydcanem/bootstrap-contextmenu
 *
 * Inspired by Bootstrap's dropdown plugin.
 * Bootstrap (http://getbootstrap.com).
 *
 * Licensed under MIT
 * ========================================================= */


;(function($) {

        'use strict';

        /* CONTEXTMENU CLASS DEFINITION
         * ============================ */

        var toggle = '[data-toggle="context"]';

        var ContextMenu = function (element, options) {
                this.$element = $(element);

                this.before = options.before || this.before;
                this.onItem = options.onItem || this.onItem;
                this.scopes = options.scopes || null;

                if (options.target) {
                        this.$element.data('target', options.target);
                }

                this.listen();
        };

        ContextMenu.prototype = {

                constructor: ContextMenu
                ,show: function(e) {

                        var $menu
                                , evt
                                , tp
                                , items
                                , relatedTarget = { relatedTarget: this, target: e.currentTarget };

                        if (this.isDisabled()) return;

                        this.closemenu();

                        if (!this.before.call(this,e,$(e.currentTarget))) return;

                        $menu = this.getMenu();
                        $menu.trigger(evt = $.Event('show.bs.context', relatedTarget));

                        tp = this.getPosition(e, $menu);
                        items = 'li:not(.divider)';
                        $menu.attr('style', '')
                                .css(tp)
                                .addClass('open')
                                .on('click.context.data-api', items, $.proxy(this.onItem, this, $(e.currentTarget)))
                                .trigger('shown.bs.context', relatedTarget);

                        // Delegating the `closemenu` only on the currently opened menu.
                        // This prevents other opened menus from closing.
                        $('html')
                                .on('click.context.data-api', $menu.selector, $.proxy(this.closemenu, this));

                        return false;
                }

                ,closemenu: function(e) {
                        var $menu
                                , evt
                                , items
                                , relatedTarget;

                        $menu = this.getMenu();

                        if(!$menu.hasClass('open')) return;

                        relatedTarget = { relatedTarget: this };
                        $menu.trigger(evt = $.Event('hide.bs.context', relatedTarget));

                        items = 'li:not(.divider)';
                        $menu.removeClass('open')
                                .off('click.context.data-api', items)
                                .trigger('hidden.bs.context', relatedTarget);

                        $('html')
                                .off('click.context.data-api', $menu.selector);
                        // Don't propagate click event so other currently
                        // opened menus won't close.
                        return false;
                }

                ,keydown: function(e) {
                        if (e.which == 27) this.closemenu(e);
                }

                ,before: function(e) {
                        return true;
                }

                ,onItem: function(e) {
                        return true;
                }

                ,listen: function () {
                        this.$element.on('contextmenu.context.data-api', this.scopes, $.proxy(this.show, this));
                        $('html').on('click.context.data-api', $.proxy(this.closemenu, this));
                        $('html').on('keydown.context.data-api', $.proxy(this.keydown, this));
                }

                ,destroy: function() {
                        this.$element.off('.context.data-api').removeData('context');
                        $('html').off('.context.data-api');
                }

                ,isDisabled: function() {
                        return this.$element.hasClass('disabled') ||
                                        this.$element.attr('disabled');
                }

                ,getMenu: function () {
                        var selector = this.$element.data('target')
                                , $menu;

                        if (!selector) {
                                selector = this.$element.attr('href');
                                selector = selector && selector.replace(/.*(?=#[^\s]*$)/, ''); //strip for ie7
                        }

                        $menu = $(selector);

                        return $menu && $menu.length ? $menu : this.$element.find(selector);
                }

                ,getPosition: function(e, $menu) {
                        var mouseX = e.clientX
                                , mouseY = e.clientY
                                , boundsX = $(window).width()
                                , boundsY = $(window).height()
                                , menuWidth = $menu.find('.dropdown-menu').outerWidth()
                                , menuHeight = $menu.find('.dropdown-menu').outerHeight()
                                , tp = {"position":"absolute","z-index":9999}
                                , Y, X, parentOffset;

                        if (mouseY + menuHeight > boundsY) {
                                Y = {"top": mouseY - menuHeight + $(window).scrollTop()};
                        } else {
                                Y = {"top": mouseY + $(window).scrollTop()};
                        }

                        if ((mouseX + menuWidth > boundsX) && ((mouseX - menuWidth) > 0)) {
                                X = {"left": mouseX - menuWidth + $(window).scrollLeft()};
                        } else {
                                X = {"left": mouseX + $(window).scrollLeft()};
                        }

                        // If context-menu's parent is positioned using absolute or relative positioning,
                        // the calculated mouse position will be incorrect.
                        // Adjust the position of the menu by its offset parent position.
                        parentOffset = $menu.offsetParent().offset();
                        X.left = X.left - parentOffset.left;
                        Y.top = Y.top - parentOffset.top;
 
                        return $.extend(tp, Y, X);
                }

        };

        /* CONTEXT MENU PLUGIN DEFINITION
         * ========================== */


        $.fn.contextmenu = function (option,e) {
                return this.each(function () {
                        var $this = $(this)
                                , data = $this.data('context')
                                , options = (typeof option == 'object') && option;

                        if (!data) $this.data('context', (data = new ContextMenu($this, options)));
                        if (typeof option == 'string') data[option].call(data, e);
                });
        };

        $.fn.contextmenu.Constructor = ContextMenu;

        /* APPLY TO STANDARD CONTEXT MENU ELEMENTS
         * =================================== */


        $(document)
           .on('contextmenu.context.data-api', function() {
                        $(toggle).each(function () {
                                var data = $(this).data('context');
                                if (!data) return;
                                data.closemenu();
                        });
                })
                .on('contextmenu.context.data-api', toggle, function(e) {
                        $(this).contextmenu('show', e);

                        e.preventDefault();
                        e.stopPropagation();
                });
               
}(jQuery));