bootstrap-contextmenu.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  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')
  49. .on('click.context.data-api', $menu.selector, $.proxy(this.closemenu, this));
  50. return false;
  51. }
  52. ,closemenu: function(e) {
  53. var $menu
  54. , evt
  55. , items
  56. , relatedTarget;
  57. $menu = this.getMenu();
  58. if(!$menu.hasClass('open')) return;
  59. relatedTarget = { relatedTarget: this };
  60. $menu.trigger(evt = $.Event('hide.bs.context', relatedTarget));
  61. items = 'li:not(.divider)';
  62. $menu.removeClass('open')
  63. .off('click.context.data-api', items)
  64. .trigger('hidden.bs.context', relatedTarget);
  65. $('html')
  66. .off('click.context.data-api', $menu.selector);
  67. // Don't propagate click event so other currently
  68. // opened menus won't close.
  69. e.stopPropagation();
  70. }
  71. ,keydown: function(e) {
  72. if (e.which == 27) this.closemenu(e);
  73. }
  74. ,before: function(e) {
  75. return true;
  76. }
  77. ,onItem: function(e) {
  78. return true;
  79. }
  80. ,listen: function () {
  81. this.$element.on('contextmenu.context.data-api', this.scopes, $.proxy(this.show, this));
  82. $('html').on('click.context.data-api', $.proxy(this.closemenu, this));
  83. $('html').on('keydown.context.data-api', $.proxy(this.keydown, this));
  84. }
  85. ,destroy: function() {
  86. this.$element.off('.context.data-api').removeData('context');
  87. $('html').off('.context.data-api');
  88. }
  89. ,isDisabled: function() {
  90. return this.$element.hasClass('disabled') ||
  91. this.$element.attr('disabled');
  92. }
  93. ,getMenu: function () {
  94. var selector = this.$element.data('target')
  95. , $menu;
  96. if (!selector) {
  97. selector = this.$element.attr('href');
  98. selector = selector && selector.replace(/.*(?=#[^\s]*$)/, ''); //strip for ie7
  99. }
  100. $menu = $(selector);
  101. return $menu && $menu.length ? $menu : this.$element.find(selector);
  102. }
  103. ,getPosition: function(e, $menu) {
  104. var mouseX = e.clientX
  105. , mouseY = e.clientY
  106. , boundsX = $(window).width()
  107. , boundsY = $(window).height()
  108. , menuWidth = $menu.find('.dropdown-menu').outerWidth()
  109. , menuHeight = $menu.find('.dropdown-menu').outerHeight()
  110. , tp = {"position":"absolute","z-index":9999}
  111. , Y, X, parentOffset;
  112. if (mouseY + menuHeight > boundsY) {
  113. Y = {"top": mouseY - menuHeight + $(window).scrollTop()};
  114. } else {
  115. Y = {"top": mouseY + $(window).scrollTop()};
  116. }
  117. if ((mouseX + menuWidth > boundsX) && ((mouseX - menuWidth) > 0)) {
  118. X = {"left": mouseX - menuWidth + $(window).scrollLeft()};
  119. } else {
  120. X = {"left": mouseX + $(window).scrollLeft()};
  121. }
  122. // If context-menu's parent is positioned using absolute or relative positioning,
  123. // the calculated mouse position will be incorrect.
  124. // Adjust the position of the menu by its offset parent position.
  125. parentOffset = $menu.offsetParent().offset();
  126. X.left = X.left - parentOffset.left;
  127. Y.top = Y.top - parentOffset.top;
  128. return $.extend(tp, Y, X);
  129. }
  130. };
  131. /* CONTEXT MENU PLUGIN DEFINITION
  132. * ========================== */
  133. $.fn.contextmenu = function (option,e) {
  134. return this.each(function () {
  135. var $this = $(this)
  136. , data = $this.data('context')
  137. , options = (typeof option == 'object') && option;
  138. if (!data) $this.data('context', (data = new ContextMenu($this, options)));
  139. if (typeof option == 'string') data[option].call(data, e);
  140. });
  141. };
  142. $.fn.contextmenu.Constructor = ContextMenu;
  143. /* APPLY TO STANDARD CONTEXT MENU ELEMENTS
  144. * =================================== */
  145. $(document)
  146. .on('contextmenu.context.data-api', function() {
  147. $(toggle).each(function () {
  148. var data = $(this).data('context');
  149. if (!data) return;
  150. data.closemenu();
  151. });
  152. })
  153. .on('contextmenu.context.data-api', toggle, function(e) {
  154. $(this).contextmenu('show', e);
  155. e.preventDefault();
  156. e.stopPropagation();
  157. });
  158. }(jQuery));