Redmine: チケット一覧のコンテキストメニューに、サーバにリクエストを送る項目を追加する (View customize plugin)

チケット一覧のコンテキストメニューに項目を追加して、その項目をクリックしたらサーバ側に選択しているチケットの番号を送って、サーバ側で処理させることができないかという質問をいただいたので、View customize pluginで試行錯誤してみました。

チケット一覧でのコンテキストメニュー生成の仕組み

チケット一覧のコンテキストメニューは、チケット一覧上での右クリックのタイミングで、選択されている行の情報などをサーバ側に送り、コンテキストメニューの情報(HTMLの断片)を受け取って、それをdiv#context-menuに設定しています。

具体的には、context_menu.js内の、contextMenuShow関数で行っています。$.ajaxで通信し、そのコールバックでコンテキストメニューを作ってしまっているので、Redmine側のコード上だとフックするポイントがありません。。

function contextMenuShow(event) {
  var mouse_x = event.pageX;
  var mouse_y = event.pageY;  
  var mouse_y_c = event.clientY;  
  var render_x = mouse_x;
  var render_y = mouse_y;
  var dims;
  var menu_width;
  var menu_height;
  var window_width;
  var window_height;
  var max_width;
  var max_height;

  $('#context-menu').css('left', (render_x + 'px'));
  $('#context-menu').css('top', (render_y + 'px'));
  $('#context-menu').html('');

  $.ajax({
    url: contextMenuUrl,
    data: $(event.target).parents('form').first().serialize(),
    success: function(data, textStatus, jqXHR) {
      $('#context-menu').html(data);
      menu_width = $('#context-menu').width();
      menu_height = $('#context-menu').height();
      max_width = mouse_x + 2*menu_width;
      max_height = mouse_y_c + menu_height;

      var ws = window_size();
      window_width = ws.width;
      window_height = ws.height;

      /* display the menu above and/or to the left of the click if needed */
      if (max_width > window_width) {
       render_x -= menu_width;
       $('#context-menu').addClass('reverse-x');
      } else {
       $('#context-menu').removeClass('reverse-x');
      }

      if (max_height > window_height) {
       render_y -= menu_height;
       $('#context-menu').addClass('reverse-y');
        // adding class for submenu
        if (mouse_y_c < 325) {
          $('#context-menu .folder').addClass('down');
        }
      } else {
        // adding class for submenu
        if (window_height - mouse_y_c < 345) {
          $('#context-menu .folder').addClass('up');
        } 
        $('#context-menu').removeClass('reverse-y');
      }

      if (render_x <= 0) render_x = 1;
      if (render_y <= 0) render_y = 1;
      $('#context-menu').css('left', (render_x + 'px'));
      $('#context-menu').css('top', (render_y + 'px'));
      $('#context-menu').show();

      //if (window.parseStylesheets) { window.parseStylesheets(); } // IE
    }
  });
}

jQueryの関数でフックする

しょうがないので、今回は、jQueryのshow関数でフックするような対応を取りました。

#context-menuに対してのshowの場合に、メニューを追加する処理を行うイメージです。

View customize の設定内容

Path pattern

チケット一覧を対象とします。

/issues$

Code

Type:JavaScriptとして下記を設定します。

送り先のURLなどは適宜変更してください。

$(function() {

  // 対象のURL(同一ドメイン)
  var commandUrl = '/test';
  var commandTitle = 'コマンド実行';

  // コンテキストメニューを表示したタイミングでフックするために
  // jQueryのshow関数を差し替え
  jQuery.fn._show = jQuery.fn.show;

  jQuery.fn.show = function() {
    if (this.attr('id') == 'context-menu') {
      // コンテキストメニューを表示したタイミングでコマンドを追加
      var menu = $('<a>').text(commandTitle).click(function() {
        execute();
        return false;
      })

      this.children('ul').append($('<li>').append(menu));
    }

    return jQuery.fn._show.apply(this, arguments);
  };

  // コマンドの実処理
  var execute = function() {

    // チェックされているチケット番号を取得(複数存在する場合はカンマ区切り)
    var issues = $('input[name="ids[]"]:checked').map(function() { return $(this).val();}).get().join(',');

    // 対象のURIにチケット番号をパラメータとして送信
    $.ajax({
      type: 'GET',
      url: commandUrl,
      data: 'issues=' + issues // チケット番号をパラメータとして
    }).done(function(data, textStatus, jqXHR){

      // 成功時の処理
      alert('成功しました。');

    }).fail(function(jqXHR, textStatus, errorThrown){

      // 失敗時の処理
      alert('失敗しました。 status:' + jqXHR.status);
    });

    contextMenuHide();
  };
});

設定後のイメージ

f:id:onozaty:20161018005342p:plain

コンテキストメニューに項目が追加され、これをクリックすると、対象のURLに対してチケット番号を送るようになります。