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