bootstrap-contextmenu.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. /*!
  2. * Bootstrap Context Menu
  3. * Author: @sydcanem
  4. * https://github.com/sydcanem/bootstrap-contextmenu
  5. *
  6. * Inspired by Bootstrap's dropdown plugin.
  7. * Bootstrap (http://getbootstrap.com).
  8. *
  9. * Licensed under MIT
  10. * ========================================================= */
  11. ;(function($) {
  12. 'use strict';
  13. /* CONTEXTMENU CLASS DEFINITION
  14. * ============================ */
  15. var toggle = '[data-toggle="context"]';
  16. var ContextMenu = function (element, options) {
  17. this.$element = $(element);
  18. this.before = options.before || this.before;
  19. this.onItem = options.onItem || this.onItem;
  20. this.scopes = options.scopes || null;
  21. if (options.target) {
  22. this.$element.data('target', options.target);
  23. }
  24. this.listen();
  25. };
  26. ContextMenu.prototype = {
  27. constructor: ContextMenu
  28. ,show: function(e) {
  29. var $menu
  30. , evt
  31. , tp
  32. , items
  33. , relatedTarget = { relatedTarget: this, target: e.currentTarget };
  34. if (this.isDisabled()) return;
  35. this.closemenu();
  36. if (this.before.call(this,e,$(e.currentTarget)) === false) return;
  37. $menu = this.getMenu();
  38. $menu.trigger(evt = $.Event('show.bs.context', relatedTarget));
  39. tp = this.getPosition(e, $menu);
  40. items = 'li:not(.divider)';
  41. $menu.attr('style', '')
  42. .css(tp)
  43. .addClass('open')
  44. .on('click.context.data-api', items, $.proxy(this.onItem, this, $(e.currentTarget)))
  45. .trigger('shown.bs.context', relatedTarget);
  46. // Delegating the `closemenu` only on the currently opened menu.
  47. // This prevents other opened menus from closing.
  48. //$('html').on('click.context.data-api', $menu.selector, $.proxy(this.closemenu, this));
  49. $('html').click(function () {
  50. this.closemenu();
  51. }.bind(this));
  52. return false;
  53. }
  54. ,closemenu: function(e) {
  55. var $menu
  56. , evt
  57. , items
  58. , relatedTarget;
  59. $menu = this.getMenu();
  60. if(!$menu.hasClass('open')) return;
  61. relatedTarget = { relatedTarget: this };
  62. $menu.trigger(evt = $.Event('hide.bs.context', relatedTarget));
  63. items = 'li:not(.divider)';
  64. $menu.removeClass('open')
  65. .off('click.context.data-api', items)
  66. .trigger('hidden.bs.context', relatedTarget);
  67. $('html')
  68. .off('click.context.data-api', $menu.selector);
  69. // Don't propagate click event so other currently
  70. // opened menus won't close.
  71. if (e) {
  72. e.stopPropagation();
  73. }
  74. }
  75. ,keydown: function(e) {
  76. if (e.which == 27) this.closemenu(e);
  77. }
  78. ,before: function(e) {
  79. return true;
  80. }
  81. ,onItem: function(e) {
  82. return true;
  83. }
  84. ,listen: function () {
  85. this.$element.on('contextmenu.context.data-api', this.scopes, $.proxy(this.show, this));
  86. //$('html').on('click.context.data-api', $.proxy(this.closemenu, this));
  87. $('html').on('keydown.context.data-api', $.proxy(this.keydown, this));
  88. }
  89. ,destroy: function() {
  90. this.$element.off('.context.data-api').removeData('context');
  91. $('html').off('.context.data-api');
  92. }
  93. ,isDisabled: function() {
  94. return this.$element.hasClass('disabled') ||
  95. this.$element.attr('disabled');
  96. }
  97. ,getMenu: function () {
  98. var selector = this.$element.data('target')
  99. , $menu;
  100. if (!selector) {
  101. selector = this.$element.attr('href');
  102. selector = selector && selector.replace(/.*(?=#[^\s]*$)/, ''); //strip for ie7
  103. }
  104. $menu = $(selector);
  105. return $menu && $menu.length ? $menu : this.$element.find(selector);
  106. }
  107. ,getPosition: function(e, $menu) {
  108. var mouseX = e.clientX
  109. , mouseY = e.clientY
  110. , boundsX = $(window).width()
  111. , boundsY = $(window).height()
  112. , menuWidth = $menu.find('.dropdown-menu').outerWidth()
  113. , menuHeight = $menu.find('.dropdown-menu').outerHeight()
  114. , tp = {"position":"absolute","z-index":9999}
  115. , Y, X, parentOffset;
  116. if (mouseY + menuHeight > boundsY) {
  117. Y = {"top": mouseY - menuHeight + $(window).scrollTop()};
  118. } else {
  119. Y = {"top": mouseY + $(window).scrollTop()};
  120. }
  121. if ((mouseX + menuWidth > boundsX) && ((mouseX - menuWidth) > 0)) {
  122. X = {"left": mouseX - menuWidth + $(window).scrollLeft()};
  123. } else {
  124. X = {"left": mouseX + $(window).scrollLeft()};
  125. }
  126. // If context-menu's parent is positioned using absolute or relative positioning,
  127. // the calculated mouse position will be incorrect.
  128. // Adjust the position of the menu by its offset parent position.
  129. parentOffset = $menu.offsetParent().offset();
  130. X.left = X.left - parentOffset.left;
  131. Y.top = Y.top - parentOffset.top;
  132. return $.extend(tp, Y, X);
  133. }
  134. };
  135. /* CONTEXT MENU PLUGIN DEFINITION
  136. * ========================== */
  137. $.fn.contextmenu = function (option,e) {
  138. return this.each(function () {
  139. var $this = $(this)
  140. , data = $this.data('context')
  141. , options = (typeof option == 'object') && option;
  142. if (!data) $this.data('context', (data = new ContextMenu($this, options)));
  143. if (typeof option == 'string') data[option].call(data, e);
  144. });
  145. };
  146. $.fn.contextmenu.Constructor = ContextMenu;
  147. /* APPLY TO STANDARD CONTEXT MENU ELEMENTS
  148. * =================================== */
  149. $(document)
  150. .on('contextmenu.context.data-api', function() {
  151. $(toggle).each(function () {
  152. var data = $(this).data('context');
  153. if (!data) return;
  154. data.closemenu();
  155. });
  156. })
  157. .on('contextmenu.context.data-api', toggle, function(e) {
  158. $(this).contextmenu('show', e);
  159. e.preventDefault();
  160. e.stopPropagation();
  161. });
  162. }(jQuery));