JQueryプラグインのTag-it!を使って、Redmineにタグ機能をつけて見ました。
Tag-it!
http://aehlke.github.io/tag-it/
Redmineのタグ関連プラグインに比べると機能的に劣りますが、SaaSのサービスでRedmineを運用していて、自由にプラグインのインストールができないなどの場合には、スクリプトだけで簡単に実現できるので、使ってみてはどうでしょうか。
1.まず最初に、「管理 » カスタムフィールド » 新しいカスタムフィールド」で、「タグ」の入力項目を作成します。
2.この時点では、チケット一覧画面での表示は以下のようになっています(通常のテキスト入力項目です)。
3.View Customize Pluginを使って、作成したカスタムフィールドにTag-it!を適用します。
外部JavaScriptを読み込むため、View Customize Pluginはv2.1.0以上を使用してください。
<!--
パスのパターン:/issues
挿入位置:全ページのヘッダ
種別:HTML
-->
<style>
/* 作成したカスタムフィールドの番号に合わせて変更してください */
.cf_52 {
display: none;
}
p.tagbox {
padding: 0.1em 0.5em;
background: #fcfcfc;
border: solid 1px #ccc;
border-radius: 5px;
float: left;
margin: 2px 3px 0px 0;
font-weight: 500;
color: #269;
line-height: 17px;
}
ul.tagit {
padding: 1px 5px;
overflow: auto;
margin-left: inherit;
/* usually we don't want the regular ul margins. */
margin-right: inherit;
}
ul.tagit li {
display: block;
float: left;
margin: 2px 5px 2px 0;
}
ul.tagit li.tagit-choice {
position: relative;
line-height: inherit;
}
input.tagit-hidden-field {
display: none;
}
ul.tagit li.tagit-choice-read-only {
padding: .2em .5em .2em .5em;
}
ul.tagit li.tagit-choice-editable {
padding: .2em 18px .2em .5em;
}
ul.tagit li.tagit-new {
padding: .25em 4px .25em 0;
}
ul.tagit li.tagit-choice a.tagit-label {
cursor: pointer;
text-decoration: none;
}
ul.tagit li.tagit-choice .tagit-close {
cursor: pointer;
position: absolute;
right: .1em;
top: 50%;
margin-top: -8px;
line-height: 17px;
}
/* used for some custom themes that don't need image icons */
ul.tagit li.tagit-choice .tagit-close .text-icon {
display: none;
}
ul.tagit li.tagit-choice input {
display: block;
float: left;
margin: 2px 5px 2px 0;
}
ul.tagit input[type="text"] {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
-moz-box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
border: none;
margin: 0;
padding: 0;
width: inherit;
background-color: inherit;
outline: none;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/tag-it@2.0.0/js/tag-it.min.js"></script>
<script>
$(function() {
tags_list = [];
tagitem_list = [];
const apiKey = ViewCustomize.context.user.apiKey;
//プロジェクトIDを取得
if (ViewCustomize.context.project) {
project_id = ViewCustomize.context.project.identifier;
project_id_tag = '/projects/' + project_id;
} else {
project_id_tag = "";
}
// タグ入力項目追加(初期表示)
makeavailableTags();
//チケット一覧グリッド・チケット詳細表示項目にタグ表示デザインを適用
//作成したカスタムフィールドの番号に合わせて変更してください(cf_52の部分)
$("td.cf_52, .cf_52 .value").each(function() {
const txt = $(this).text();
if (txt.length > 0) {
const resArray = txt.split(",");
let ret = "";
for (let i = 0; i < resArray.length; i++) {
ret += '<p class="tagbox"><a href="' + project_id_tag + '/search?utf8=✓&scope=&q=' + resArray[i] + '">' + resArray[i] + '</a></p>';
}
$(this).html(ret);
$('.cf_52').show();
}
});
//チケット一覧グリッドで表示項目の幅を確保
if ($('td.cf_52').length) {
$('td.cf_52').show();
$(".cf_52").css('min-width', '250px');
}
// ステータス変更時など、フォームの内容が書き変わるたびにタグ再処理
const _replaceIssueFormWith = replaceIssueFormWith;
replaceIssueFormWith = function(html) {
_replaceIssueFormWith(html);
makeavailableTags();
};
})
//カスタムフィールドをタグ入力可能にする
function tagit(tagitem_list) {
const tagAll = unique(tagitem_list);
$("#issue_custom_field_values_52").tagit({
singleField: true,
//自動補完するワードを設定
availableTags: tagAll
});
}
//自動補完するワードを準備
function makeavailableTags() {
if (tagitem_list.length === 0) { //画面初期表示時に登録済みタグを取得する
$.when(
gettagList100()
).done(function() {
//取得したタグをリスト化する
for (let i = 0; i < tags_list.length; i++) {
const tagArray = tags_list[i].split(',');
for (let j = 0; j < tagArray.length; j++) {
tagitem_list.push(tagArray[j]);
}
}
tagit(tagitem_list);
})
} else { //トラッカー・ステータス変更時には再取得しない
tagit(tagitem_list);
}
}
let tags_list = [];
let tagitem_list = [];
//チケットに登録されているタグを取得
function gettagList100() {
const deferred = new $.Deferred();
if (ViewCustomize.context.project) {
url = '' + '/issues.json' + '?project_id=' + project_id + '&status_id=*&cf_52=*&limit=100'; //最新の100件のみ取得
} else {
url = '' + '/issues.json' + '?status_id=*&cf_52=*&limit=100';
}
$.ajax({
type: "GET",
url: url,
headers: {
'X-Redmine-API-Key': apiKey
},
dataType: "text",
contentType: 'application/json',
}).done(function(data) {
data = JSON.parse(data);
data1 = data.issues;
for (i in data1) {
custom_fields = data1[i].custom_fields;
for (j in custom_fields) {
targetfield = custom_fields[j].id;
//作成したカスタムフィールドの番号に合わせて変更してください(52の部分)
if (targetfield === 52 && custom_fields[j].value !== "") {
tags_list.push(custom_fields[j].value);
}
};
};
deferred.resolve();
});
return deferred;
}
//タグリストの重複を削除
function unique(list) {
let result = [];
$.each(list, function(i, e) {
if ($.inArray(e, result) == -1) result.push(e);
});
return result;
}
</script>
4.スクリプト適用後の画面表示は以下のようになります。
5.タグをクリックすると、タグをキーワードにして検索画面に遷移します。
Selectize.jsを使用する場合
Selectize.js
https://selectize.github.io/selectize.js/
View Customize Pluginで読み込むjQueryプラグインを以下のように変更してください。
<!--
<script src="https://cdn.jsdelivr.net/npm/tag-it@2.0.0/js/tag-it.min.js"></script>
-->
<script src="https://cdn.jsdelivr.net/npm/selectize@0.12.6/dist/js/standalone/selectize.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/selectize@0.12.6/dist/css/selectize.bootstrap3.css">
スクリプトを以下のように変更してください。
//カスタムフィールドをタグ入力可能にする(selectizeを使用)
function selectize(tagitem_list) {
const tagAll = unique(tagitem_list);
let listresult = [];
for (i in tagAll) {
listresult.push({
name: tagAll[i]
});
}
$('#issue_custom_field_values_52').selectize({
plugins: ['remove_button', 'restore_on_backspace'],
valueField: 'name',
labelField: 'name',
searchField: 'name',
preload: 'focus',
delimiter: ',',
dropdownParent: 'body',
closeAfterSelect: true,
create: true,
options: listresult,
sortField: {
field: 'name'
}
});
}
//自動補完するワードを準備
function makeavailableTags() {
if (tagitem_list.length === 0) { //画面初期表示
$.when(
gettagList100()
).done(function() {
//取得したタグをリスト化する
for (let i = 0; i < tags_list.length; i++) {
const tagArray = tags_list[i].split(',');
for (let j = 0; j < tagArray.length; j++) {
tagitem_list.push(tagArray[j]);
}
}
//tagit(tagitem_list);
selectize(tagitem_list);
})
} else { //トラッカー・ステータス変更時
//tagit(tagitem_list);
selectize(tagitem_list);
}
}