はじめに
とても便利なプラグイン「ViewCustomizePlugin」を使ってのカスタマイズです。
あっちこっち参考にさせて貰ったので、自分も還元。
動作環境
- Windows Server 2019 Standard
- Bitnami Redmine Stack 4.1.1-4
- View Customize plugin 2.7.0
テーブルの横幅変えられるのと、くっつくヘッダ
これはSEの人にも、頑張って作ってって言われた。
リサイズはラッパーかまして、resizableがそのまま使えた。
ヘッダ追従はヘッダだけコピーしたテーブル作って、コピーテーブルをスクロールイベで制御してます。
(チケット名と日付だけは、文字列折返ししたかったので、コード内で設定)
ヘッダの追従は色々試したり、調べた結果結構な力技になった印象…。
ガントチャートの方のヘッダ追従誰か作って…
ソースコード
/*
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;
}
くっついてくる横スクロールバー
「一番下まで行かないと横スクロールできないから常に表示できない?」って言われて、「ホイールクリックでなんとかして」って答えたけどヘッダと同じ要領で出来そうだから作った。
チケット一覧のスクロールバーと横幅が同じ、スクロールバーだけの要素追加して、追加したスクロールバーとチケット一覧のスクロールバーの動き同期してる。
ソースコード
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のラベルがあったら赤文字に設定。
ソースコード
/*
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;
}
});
});