7
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

SEじゃない人にウケが良かったRedmainのカスタマイズ

Last updated at Posted at 2021-03-16

はじめに

とても便利なプラグイン「ViewCustomizePlugin」を使ってのカスタマイズです。
あっちこっち参考にさせて貰ったので、自分も還元。

動作環境

  • Windows Server 2019 Standard
  • Bitnami Redmine Stack 4.1.1-4
  • View Customize plugin 2.7.0

テーブルの横幅変えられるのと、くっつくヘッダ

これはSEの人にも、頑張って作ってって言われた。
リサイズはラッパーかまして、resizableがそのまま使えた。
ヘッダ追従はヘッダだけコピーしたテーブル作って、コピーテーブルをスクロールイベで制御してます。
(チケット名と日付だけは、文字列折返ししたかったので、コード内で設定)

ヘッダの追従は色々試したり、調べた結果結構な力技になった印象…。
ガントチャートの方のヘッダ追従誰か作って…

Videotogif (1) (1).gif

ソースコード

/*
Path pattern: /issues$
Type: JavaScript
テーブルの列の横幅リサイズ可能と、ヘッダのスクロール追従(ラッパー追加とヘッダコピーの関係上分離できなかった…)
*/
$(window).on('load', function() {
  // th,tdのセレクタ名生成
  var $th_selecter = $('table.list th');
  var $td_selecter = $('table.list td');
  var $table_list = $('table.list');

  // フロートヘッダ用のスティッキーテーブル生成
  $table_list.before('<table class="list issues odd-even sort-by-id sort-desc sticky" style="table-layout: fixed; position:fixed; top: 0px; display: none; z-index:2; transition: .0s;"> </table>');
  // ヘッダをコピー
  $('table.list thead').clone().appendTo('table.sticky');
  // スティッキーテーブルのセレクタ名生成
  var $sticky_table = $('table.sticky');
  var $sticky_selecter = $('table.sticky th');
  // スティッキーテーブル用の各要素取得
  var headerHeight = $sticky_table.outerHeight();
  var startPos = 0;
  var tableTop = $table_list.offset().top;
  var tableLeft = $table_list.offset().left;
  // ブラウザ全体のスクロールイベント
  $(window).on('load scroll', function() {
    var scroll_top = $(this).scrollTop();
    // スクロール位置がテーブル位置の上まで来たら、フロートヘッダを表示
    if(scroll_top > tableTop) {
      $sticky_table.css('display', 'table');
    } else {
      $sticky_table.css('display', 'none');
    }
    startPos = scroll_top;
  });
  // テーブル内の横スクロールイベント
  $('div.autoscroll').on("scroll", function(){
    // フロートヘッダの位置を横スクロールに合わせて移動
    $sticky_table.css("left", -$(this).scrollLeft() + tableLeft);
  });

  // resizable設定用のラッパーを作成
  $th_selecter.wrapInner('<div class="resizable_wrap">'); 
  $td_selecter.wrapInner('<div class="resizable_wrap">'); 
  $sticky_selecter.wrapInner('<div class="resizable_wrap">'); 
  // フロートヘッダと通常ヘッダを同期させるために、列毎に取り出す
  $th_selecter.each(function(index, element) {
    // リサイズ設定。横のみ。リサイズを行う時にtablelayoutをfixedに変更(overflowを有効にするため。最初からfixedに設定するとレイアウトが崩れるのでリサイズ行う時のみ)。
    $(this).resizable({
      alsoResize: $sticky_selecter.eq(index),
      handles: 'e',
      start: function() { setTableFixed() }
    });
    $sticky_selecter.eq(index).resizable({
      alsoResize: $(this),
      handles: 'e',
      start: function() { setTableFixed() }
    });
  });
  
  $th_selecter.each(function(index, element) {
     // layout:fixedにすると横幅が崩れるので横幅を固定設定
     $(this).width( $(this).width() );
     $sticky_selecter.eq(index).width( $(this).width() );
  });
  // 元テーブルの横幅取得
  setTableWidth();
  
  // テーブルサイズの変更イベントあると、テーブルサイズが変わるため横幅再取得
  $('table.list').resize(function() {
    setTableWidth();
  });
  // Windowの変更イベントあると、テーブルサイズが変わるため横幅再取得
  $(window).resize(function() {
    setTableWidth();
  });
  
  function setTableWidth() {
    $sticky_table.css('width', $('table.list').not('.sticky').width());
  }
  
  function setTableFixed() {
    $table_list.css('table-layout','fixed');
    // 最初から自動改行にすると横幅が取れないので、リサイズ後にチケットの題名を自動改行するように変更
    $('.subject .resizable_wrap').css('white-space','normal');
    $('.parent-subject .resizable_wrap').css('white-space','normal');
    $('.due_date .resizable_wrap').css('white-space','normal');
  }
});

CSS

作業時間とレポートはリサイズだけ抜き出して適応してます。

/*
Path pattern: /issues$|/time_entries$|/time_entries/report$
Type: JavaScript
横幅のリサイズ時に文字幅以下になったら文字を隠す
*/
.resizable_wrap{
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

くっついてくる横スクロールバー

「一番下まで行かないと横スクロールできないから常に表示できない?」って言われて、「ホイールクリックでなんとかして」って答えたけどヘッダと同じ要領で出来そうだから作った。

チケット一覧のスクロールバーと横幅が同じ、スクロールバーだけの要素追加して、追加したスクロールバーとチケット一覧のスクロールバーの動き同期してる。

Videotogif (1) (1).gif

ソースコード

div.autoscrollを#gantt_areaに変えればガントチャートでも使えます。

/*
Path pattern: /issues$|/time_entries$
Type: JavaScript
チケット一覧に横スクロールバーの表示
*/
$(window).on('load', function() {
  $autoscroll = $('div.autoscroll');
  
  // スクロールバー用の要素挿入
  $autoscroll.append ('<div class="issue_hor_scroll" style="display: block;height: 18px;width: 0px;position: fixed;bottom: 0px;overflow-x: scroll;overflow-y: hidden;"> <div class="max_hor_scroll" style="width: 0px;height: 1px;"></div> </div>');
  var $scroll_bar = $('div.issue_hor_scroll');
  
  // スクロールバーの表示設定
  function showScrollBar() {
    var autoscroll_bottom = $autoscroll.offset().top + $autoscroll.height();
    var scroll_top = $(this).scrollTop() + $(this).height();
    // スクロールバーが最大値以下かつ、スクロール位置がテーブル上まで来たら、スクロールバーを表示
    if($autoscroll.get(0).scrollWidth > $autoscroll.width() && scroll_top < autoscroll_bottom) {
      $scroll_bar.css('display', 'block');
    } else {
      $scroll_bar.css('display', 'none');
    }
    // スクロール位置同期
    $scroll_bar.scrollLeft($autoscroll.scrollLeft());
  }
  // スクロールバーの幅設定
  function setScrollBar() {
    $('div.max_hor_scroll').css('width', $autoscroll.get(0).scrollWidth);
    $scroll_bar.css('width', $autoscroll.width());
  }
  setScrollBar();
  showScrollBar();
  
  // ブラウザ全体のスクロールイベント
  $(window).on('load scroll', function() {
    showScrollBar();
  });
  
  // スクロールバーのスクロールイベント
  $scroll_bar.on('load scroll', function() {
    // スクロール位置同期
    $autoscroll.scrollLeft($(this).scrollLeft());
  });
  // スクロールバーのスクロールイベント
  $autoscroll.on('load scroll', function() {
    // スクロール位置同期
    $scroll_bar.scrollLeft($(this).scrollLeft());
  });
  
  // テーブルサイズの変更イベント
  $('table.list').resize(function() {
    setScrollBar();
    showScrollBar();
  });
  // テーブルサイズの変更イベント
  $(window).resize(function() {
    setScrollBar();
    showScrollBar();
  });
});

ガントチャートの遅れ進捗率を赤字に

進捗遅れのチケットの指定要素探すのが、地味に大変だった…。

赤いバーの表示(or期限切れ)要素からチケットID取得して、同じチケットIDのラベルがあったら赤文字に設定。

無題.png

ソースコード

/*
Path pattern: /issues/gantt$
Type: JavaScript
ガントチャートの進捗遅れチケットのラベルを赤字に設定
*/
$(window).on('load', function() {
  // 遅れバー(赤)が表示されてる要素 issue-behind-schedule icon icon-issue
  $('span.issue-behind-schedule').each(function() {
    // 遅れチケットの番号取得
    var late_issue = $(this).not('.issue-closed').parent().attr('id');
    $('div.task.label').each(function(i) {
      var issue_no = $(this).attr('data-collapse-expand');
      // 遅れチケットと同じだったら赤字設定
      if(issue_no == late_issue) {
        $(this).css('color','red');
      }
    });
  });
});

Redmine Work Time pluginのステータスと進捗率の連動

自動入力みたいな、操作が減る改造はウケが良かった。

普通のステータスと進捗率の連動のコードは他であると思うので、こっちのせます。
valueにMがくっつくので、ちょっと面倒くさい。
(特定操作でMの数が合わなくなって破綻した気がする……リロードすると直るから放置)

※valueの値がチケットのステータスによって違うので注意

ソースコード

/*
Path pattern: /work_time
Type: JavaScript
work_timeのステータスと進捗率の連動
*/
$(function() {
  function status_new(_this) {
    // ステータスのid生成
    var statud_id=_this.attr('id').replace(/done_ratio/g, 'status_id');
    $('#'+statud_id).children('[value=1]').remove();
    $('#'+statud_id).children('[value=M1]').remove();
    $('#'+statud_id).append($('<option value="M1">新規</option>'));
    $('#'+statud_id).val('M1');
  }
  function status_proseccing(_this) {
    // ステータスのid生成
    var statud_id=_this.attr('id').replace(/done_ratio/g, 'status_id');
    $('#'+statud_id).children('[value=2]').remove();
    $('#'+statud_id).children('[value=M2]').remove();
    $('#'+statud_id).append($('<option value="M2">進行中</option>'));
    $('#'+statud_id).val('M2');
  }
  function status_done(_this) {
    // ステータスのid生成
    var statud_id=_this.attr('id').replace(/done_ratio/g, 'status_id');
    $('#'+statud_id).children('[value=5]').remove();
    $('#'+statud_id).children('[value=M5]').remove();
    $('#'+statud_id).append($('<option value="M5">完了</option>'));
    $('#'+statud_id).val('M5');
  }
  
  // ステータスが変わった場合
  $('select[id*="status_id"]').change(function(e) {
    // 進捗率のid生成
    var done_ratio_id=$(this).attr('id').replace(/status_id/g, 'done_ratio');
    // 選択してるvalue取得(何故か選択する度Mが付くので数字のみに)
    var select_val=$(this).val().replace(/[^0-9]/g, '');

    // ステータスに応じて進捗率を設定
    // 例: 終了→進捗率100%を設定
    switch(select_val) {
      case '1':
        $('#'+done_ratio_id).val('0');
        break;
      case '5':
        $('#'+done_ratio_id).val('100');
        break;
      case '7':
        $('#'+done_ratio_id).val('100');
        break;
    }
  });
  // 進捗率が変わった場合
  $('select[id*="done_ratio"]').change(function(e) {
    // 選択してるvalue取得(何故か選択する度Mが付くので数字のみに)
    var select_val=$(this).val();
    // 進捗率に応じてステータスを設定
    // 例: 進捗率100%→終了を設定
    switch(select_val) {
      case '0':
        status_new($(this));
      break;
      case '10':
        status_proseccing($(this))
      break;
      case '20':
        status_proseccing($(this))
      break;
      case '30':
        status_proseccing($(this))
      break;
      case '40':
        status_proseccing($(this))
      break;
      case '50':
        status_proseccing($(this))
      break;
      case '60':
        status_proseccing($(this))
      break;
      case '70':
        status_proseccing($(this))
      break;
      case '80':
        status_proseccing($(this))
      break;
      case '90':
        status_proseccing($(this))
      break;
      case '100':
        status_done($(this))
      break;
    }
  });
});
7
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?