解決する困りごと
kViwerの一覧画面から詳細画面に移ると、次のような困りごとが起きがちです。
- 一覧から詳細を開くと戻ったときに検索条件やスクロールが消える
- 複数の詳細を並べて比較したいが、同じタブ遷移だと行き来に時間がかかる
- ユーザーが迷子になって問い合わせが増えている
一覧の「詳細」リンクが Ctrl+クリックでも別タブで開けないのが厄介ごとの原因です。
そこで、JavaScriptカスタマイズを追加して、通常クリックでも別タブ遷移できるようにします。
できるようになること
- 一覧の状態(検索条件・ソート・スクロール)を保ったまま詳細を別タブで開ける
- キーボード操作/フォーカス可視を保ったまま導入できる
- 実装は最小JS 1ファイル+セレクタ1行調整でOK
おまけ:tabnabbing対策(noopener) と、必要に応じた noreferrer の運用判断も整理します。
読んでほしい人・前置き
- 対象:kViewerの一覧から詳細画面の導線で、ユーザーが「元の一覧に戻りづらい」「複数詳細を並べて見たい」課題を抱える方
※比較的カスタマイズ経験の浅い方を想定しています。 - 前提:一覧画面の設定で「詳細」リンクを表示に設定していること
※「行全体をリンクにする」場合の別タブで開くスクリプトは要望があれば別記事で扱います。 - 目的:
- 一覧の状態(スクロール位置・検索条件)を保ったまま詳細を別タブで開く
- セキュリティとアクセシビリティに配慮した安全な別タブ化する
概要
- 一覧の「詳細」リンクを新規タブで開き、戻る問題と並行作業のストレスを減らす
-
target="_blank"だけでなくrel="noopener noreferrer"を付与し、逆タブ乗っ取りを防ぐ - 追加するコードは 1ファイルのみ
- 実際の画面のスクショで Before / After を確認できる
Before / After
実際に何ができるようになるか、画像で流れを説明します。
【Before】
▼523番の詳細リンクを押下

↓
▼クリックで画面遷移、詳細画面が開かれる

↓
▼一覧画面に戻ると状態が失われる:画面上部までスクロールが初期化

【After】
▼523番の詳細リンクを押下

↓
▼クリックで新規タブに遷移、詳細画面が新規タブで開かれる

↓
▼元のタブ(一覧画面)はそのまま残る

データを探すときや連続して閲覧したいユーザーにとって、かなり使いやすくなります。
実装(最小コード)
こちらのコードを参考に、環境に合わせてJavaScriptカスタマイズを対象の画面に追加してください。
▼サンプルコード全体
(() => {
'use strict';
kviewer.events.on('records.show', (context) => {
const recs = context.records;
const rows = Array.from(context.getRecordElements());
rows.forEach((tr, i) => {
const rec = recs[i];
// 詳細画面の遷移に必要なレコードコード
const code = rec.__kviewer_record_code__;
// 行の先頭セルにある「詳細」ボタンを取得
const btn = tr.querySelector('td:first-child button');
btn.addEventListener('click', (ev) => {
// Ctrl/⌘・中クリックの場合はブラウザ標準の別タブ動作に任せる
if (ev.ctrlKey || ev.metaKey || ev.button === 1) return;
// kViewer の通常遷移を止める(既存のクリック処理より先に割り込む)
ev.preventDefault();
ev.stopImmediatePropagation();
// 詳細URLを生成
const url = location.origin + location.pathname + '/detail/' + code;
// 別タブで開く
window.open(url, '_blank', 'noopener,noreferrer');
}, true); // capture=ture
});
});
})();
順番に解説します。
1.即時実行関数(IIFE)でスコープを閉じる
このスクリプト全体は「即時実行関数(IIFE)」で包んで、変数がグローバルに漏れないようにします。
(() => {
...
})();
2.strict mode(厳格モード)で安全寄りに動かす
'use strict' を宣言すると、危険な書き方をエラーにしてバグを早めに見つけやすくします。
'use strict';
3.一覧表示時のイベントに処理を登録する(kViewer)
レコード一覧が表示されたタイミングで発火するrecords.showにフックします。
そのときにcontextが渡されます。
kviewer.events.on('records.show', (context) => {
…
};
4.contextから「レコード配列」と「画面の行要素」を取り出す
context.recordsをrecsに、context.getRecordElements()をrowsに格納します。
Array.from()は「配列っぽいもの」を本物の配列に変換します。
const recs = context.records;
const rows = Array.from(context.getRecordElements());
5.行(DOM)とレコード(データ)を同じ順番で処理する
rows.forEach((tr, i) => ...)で画面の各行を順に処理し、同じインデックスのrecs[i]を対応づけます。
rows.forEach((tr, i) => {
const rec = recs[i];
...
});
6.詳細遷移に必要な「kViewerのレコードコード」を取り出す
詳細画面の遷移に必要なレコードコードとして、__kviewer_record_code__を使います。
このプロパティを使って詳細URLを組み立てる例が確認できます。
※実際のDOM要素に合わせてください。
const code = rec.__kviewer_record_code__;
7.行の先頭セルにある「詳細」ボタンを取得する
querySelector()は、指定したCSSセレクタに一致する最初の要素を返します(見つからない場合は null)。
const btn = tr.querySelector('td:first-child button');
8.クリックイベントを“キャプチャフェーズ”で先取りする
第3引数をtrueにするとキャプチャフェーズで発火し、既存処理より先に割り込みやすくなります。
btn.addEventListener('click', (ev) => {
...
}, true); // capture=true
9.Ctrl/⌘クリックはブラウザ標準の挙動に任せる
ctrlKey / metaKey / button は MouseEvent のプロパティです。
if (ev.ctrlKey || ev.metaKey || ev.button === 1) return;
10.kViewerの通常遷移(既定動作・既存リスナー)を止める
preventDefault()は「そのイベントに紐づく既定の動作」をキャンセルします。
stopImmediatePropagation()は、イベント伝搬を止めるだけでなく、同じ要素に登録済みの他のリスナーも呼ばせないようにします。
ev.preventDefault();
ev.stopImmediatePropagation();
11.詳細URLを組み立てる(origin + pathname + /detail/ + code)
location.originは「プロトコル + ドメイン +(必要なら)ポート」を返します。
location.pathnameはURLのパス部分(クエリやハッシュを除く)を返します。
それらを使って詳細ページのURL文字列を組み立てています。
const url = location.origin + location.pathname + '/detail/' + code;
12.別タブで開く(noopener / noreferrer 付き)
window.open(url, '_blank', ...)で新しいタブ(またはウィンドウ)を開きます。
第3引数のnoopener / noreferrerは、元ページ参照(window.opener)を無効化したり、参照元(Referer)を送らないようにするための指定です。
window.open(url, '_blank', 'noopener,noreferrer');
13.ここまでの処理をつなげた全体像(再掲)
上記の説明を踏まえたまとめです。
▼サンプルコード全体
(() => {
'use strict';
kviewer.events.on('records.show', (context) => {
const recs = context.records;
const rows = Array.from(context.getRecordElements());
rows.forEach((tr, i) => {
const rec = recs[i];
// 詳細画面の遷移に必要なレコードコード
const code = rec.__kviewer_record_code__;
// 行の先頭セルにある「詳細」ボタンを取得
const btn = tr.querySelector('td:first-child button');
btn.addEventListener('click', (ev) => {
// Ctrl/⌘・中クリックの場合はブラウザ標準の別タブ動作に任せる
if (ev.ctrlKey || ev.metaKey || ev.button === 1) return;
// kViewer の通常遷移を止める(既存のクリック処理より先に割り込む)
ev.preventDefault();
ev.stopImmediatePropagation();
// 詳細URLを生成
const url = location.origin + location.pathname + '/detail/' + code;
// 別タブで開く
window.open(url, '_blank', 'noopener,noreferrer');
}, true); // capture=ture
});
});
})();
付録:セレクタの見つけ方(30秒でできる)
- 一覧で「詳細」リンクを右クリック → 検証。
-
<a>要素に固有のクラスや属性があれば それをセレクタに。 - なければテンプレ側で
class="detail-link"を追加して安定化。
おまけ:セキュリティ対策の必要性
-
tabnabbing対策:
target="_blank"だけだと、リンク先からwindow.opener経由で 元タブを書き換えられる可能性があります。rel="noopener"(またはwindow.open(..., '_blank', 'noopener'))で opener を断ちます。
参考: MDN: rel="noopener" / MDN: Window.opener -
暗黙
noopenerでも“明示”が堅実:モダンブラウザは_blankに 暗黙でnoopenerを適用しますが、全ユーザが最新とは限りません。互換性と意図の明示のため、属性を付けておくことを推奨します。
参考: MDN / OWASP: Reverse Tabnabbing -
noreferrerの扱い:必要に応じて 参照元(Referrer)を送らないようにできます(多くの環境でnoopener相当の効果も含む)。ただし 計測に影響するため、外部リンクのみに付ける等の運用ルール化が無難です。
参考: MDN: rel="noopener" の説明内での言及 / StackOverflow 議論
まとめ
kViewerの一覧→詳細の導線を「別タブで開く」ようにすると、戻ったときに検索条件やスクロールが消える問題を避けやすくなり、複数レコードの比較もスムーズになります。
- 一覧の「詳細」クリックをフックして、
window.open()で詳細URLを別タブ表示 - 既存の遷移は
preventDefault()/stopImmediatePropagation()で止めて上書き - セキュリティは
noopener(必要ならnoreferrer)で reverse tabnabbing 対策
うまく動かない場合は、まず 「詳細」ボタンのセレクタ を見直してください(テンプレ差でここが一番ズレます)。
気になる点や「このテンプレだとセレクタが合わない」などがあれば、コメントで教えてください。
(可能なら、DevToolsで見た「詳細」ボタン周りのHTML断片も貼ってもらえると早いです)
次回予告
- スクリプト編集なしで複数画面へのリンクを掲載する汎用メニューをkViewerで作成する方法
- 複合条件で検索するモーダル(文字列・日付範囲・ラジオ・数値など)を実装する方法
この記事が役に立ったら いいね / ストック をお願いします。
「このテーマも欲しい」という要望があればぜひコメントください。
実機検証の結果(ブラウザ/OS/テンプレ)も共有いただけると、付録に反映して更新します。