diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 394e05ae..af424852 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -885,7 +885,7 @@ module ApplicationHelper def context_menu(url) unless @context_menu_included content_for :header_tags do - javascript_include_tag('context_menu') + + javascript_include_tag('context_menu.jquery') + stylesheet_link_tag('context_menu') end if l(:direction) == 'rtl' @@ -895,7 +895,7 @@ module ApplicationHelper end @context_menu_included = true end - javascript_tag "new ContextMenu('#{ url_for(url) }')" + javascript_tag "jQuery(document).ContextMenu('#{ url_for(url) }')" end def context_menu_link(name, url, options={}) diff --git a/app/views/issues/show.rhtml b/app/views/issues/show.rhtml index a358201f..90014cff 100644 --- a/app/views/issues/show.rhtml +++ b/app/views/issues/show.rhtml @@ -138,9 +138,9 @@ <% content_for :header_tags do %> <%= auto_discovery_link_tag(:atom, {:format => 'atom', :key => User.current.rss_key}, :title => "#{@issue.project} - #{@issue.tracker} ##{@issue.id}: #{@issue.subject}") %> <%= stylesheet_link_tag 'scm' %> - <%= javascript_include_tag 'context_menu' %> + <%= javascript_include_tag 'context_menu.jquery' %> <%= stylesheet_link_tag 'context_menu' %> <%= stylesheet_link_tag 'context_menu_rtl' if l(:direction) == 'rtl' %> <% end %> -<%= javascript_tag "new ContextMenu('#{issues_context_menu_path}')" %> +<%= javascript_tag "jQuery(document).ContextMenu('#{issues_context_menu_path}')" %> diff --git a/public/javascripts/context_menu.jquery.js b/public/javascripts/context_menu.jquery.js new file mode 100644 index 00000000..40c062c5 --- /dev/null +++ b/public/javascripts/context_menu.jquery.js @@ -0,0 +1,211 @@ +(function($) { + var observingContextMenuClick; + var url; + var lastSelected; + var menu; + var menuId = 'context-menu'; + var selectorName = 'hascontextmenu'; + var contextMenuSelectionClass = 'context-menu-selection'; + var reverseXClass = 'reverse-x'; + var reverseYClass = 'reverse-y'; + + var methods = { + createMenu: function() { + if(!menu) { + $('#wrapper').append(''); + menu = $('#' + menuId); + } + }, + click: function(e) { + var target = $(e.target); + + if(target.is('a')) { + return; + } + + switch(e.which) { + case 1: + if(e.type === 'click') { + methods.hideMenu(); + methods.leftClick(e); + break; + } + case 3: + if(e.type === 'contextmenu') { + methods.hideMenu(); + methods.rightClick(e); + break; + } + default: + return; + } + }, + leftClick: function(e) { + var target = $(e.target); + var tr = target.parents('tr'); + if((tr.size() > 0) && tr.hasClass(selectorName)) + { + // a row was clicked, check if the click was on checkbox + if(target.is('input')) + { + // a checkbox may be clicked + if (target.is(':checked')) { + tr.addClass(contextMenuSelectionClass); + } else { + tr.removeClass(contextMenuSelectionClass); + } + } + else + { + if (e.ctrlKey || e.metaKey) + { + methods.toggleSelection(tr); + } + else if (e.shiftKey) + { + if (lastSelected !== null) + { + var toggling = false; + var rows = $(selectorName); + for (i = 0; i < rows.length; i++) + { + if (toggling || rows[i] == tr) + { + methods.addSelection(rows[i]); + } + if (rows[i] == tr || rows[i] == lastSelected) + { + toggling = !toggling; + } + } + } else { + methods.addSelection(tr); + } + } else { + methods.unselectAll(); + methods.addSelection(tr); + } + lastSelected = tr; + } + } + else + { + // click is outside the rows + if (target.is('a') === false) { + this.unselectAll(); + } else { + if (target.hasClass('disabled') || target.hasClass('submenu')) { + e.preventDefault(); + } + } + } + }, + rightClick: function(e) { + var target = $(e.target); + var tr = target.parents('tr'); + + if((tr.size() === 0) || !(tr.hasClass(selectorName))) { + return; + } + e.preventDefault(); + + if(!methods.isSelected(tr)) { + methods.unselectAll(); + methods.addSelection(tr); + lastSelected = tr; + } + methods.showMenu(e); + }, + unselectAll: function() { + var rows = $('.' + contextMenuSelectionClass); + rows.each(function() { + methods.removeSelection($(this)); + }); + }, + hideMenu: function() { + menu.hide(); + }, + showMenu: function(e) { + var target = $(e.target); + var params = target.parents('form').serialize(); + + var mouseX = e.pageX; + var mouseY = e.pageY; + var renderX = mouseX; + var renderY = mouseY; + + $.ajax({ + url: url, + data: params, + success: function(response, success) { + menu.html(response); + + var maxWidth = mouseX + (2 * menu.width()); + var maxHeight = mouseY + menu.height(); + + if(maxWidth > $(window).width()) { + renderX -= menu.width(); + menu.addClass(reverseXClass); + } else { + menu.removeClass(reverseXClass); + } + + if(maxHeight > $(window).height()) { + renderY =+ menu.height(); + menu.addClass(reverseYClass); + } else { + menu.removeClass(reverseYClass); + } + + if(renderX <= 0) { + renderX = 1; + } + if(renderY <= 0) { + renderY = 1; + } + + menu.css('top', renderY).css('left', renderX); + menu.show(); + } + }); + }, + addSelection: function(element) { + element.addClass(contextMenuSelectionClass); + methods.checkSelectionBox(element, true); + }, + isSelected: function(element) { + return element.hasClass(contextMenuSelectionClass); + }, + toggleSelection: function(element) { + if(methods.isSelected(element)) { + methods.removeSelection(element); + } else { + methods.addSelection(element); + } + }, + removeSelection: function(element) { + element.removeClass(contextMenuSelectionClass); + methods.checkSelectionBox(element, false); + }, + checkSelectionBox: function(element, checked) { + var inputs = element.find('input'); + inputs.each(function() { + inputs.attr('checked', checked ? 'checked' : false); + }); + } + }; + + $.fn.ContextMenu = function(u) { + url = u; + methods.createMenu(); + + if(!observingContextMenuClick) { + $(document).bind('click.contextMenu', methods.click); + $(document).bind('contextmenu.contextMenu', methods.click); + observingContextMenuClick = true; + } + + methods.unselectAll(); + lastSelected = null; + }; +})(jQuery);