3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

kViewer 一覧から詳細を安全に“別タブ化”しUXを改善する実装ガイド|サンプルコード付

3
Last updated at Posted at 2026-03-26

解決する困りごと

kViwerの一覧画面から詳細画面に移ると、次のような困りごとが起きがちです。

  • 一覧から詳細を開くと戻ったときに検索条件やスクロールが消える
  • 複数の詳細を並べて比較したいが、同じタブ遷移だと行き来に時間がかかる
  • ユーザーが迷子になって問い合わせが増えている

一覧の「詳細」リンクが Ctrl+クリックでも別タブで開けないのが厄介ごとの原因です。
そこで、JavaScriptカスタマイズを追加して、通常クリックでも別タブ遷移できるようにします。

できるようになること

  • 一覧の状態(検索条件・ソート・スクロール)を保ったまま詳細を別タブで開ける
  • キーボード操作/フォーカス可視を保ったまま導入できる
  • 実装は最小JS 1ファイルセレクタ1行調整でOK

おまけ:tabnabbing対策(noopener) と、必要に応じた noreferrer の運用判断も整理します。

読んでほしい人・前置き

  • 対象:kViewerの一覧から詳細画面の導線で、ユーザーが「元の一覧に戻りづらい」「複数詳細を並べて見たい」課題を抱える方
     ※比較的カスタマイズ経験の浅い方を想定しています。
  • 前提:一覧画面の設定で「詳細」リンクを表示に設定していること
     ※「行全体をリンクにする」場合の別タブで開くスクリプトは要望があれば別記事で扱います。
  • 目的:
    • 一覧の状態(スクロール位置・検索条件)を保ったまま詳細を別タブで開く
    • セキュリティとアクセシビリティに配慮した安全な別タブ化する

概要

  • 一覧の「詳細」リンクを新規タブで開き、戻る問題並行作業のストレスを減らす
  • target="_blank" だけでなく rel="noopener noreferrer" を付与し、逆タブ乗っ取りを防ぐ
  • 追加するコードは 1ファイルのみ
  • 実際の画面のスクショで Before / After を確認できる

Before / After

実際に何ができるようになるか、画像で流れを説明します。

【Before】

▼523番の詳細リンクを押下
before1.png

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

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

【After】

▼523番の詳細リンクを押下
before1.png

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

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

データを探すときや連続して閲覧したいユーザーにとって、かなり使いやすくなります。

実装(最小コード)

こちらのコードを参考に、環境に合わせて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.recordsrecsに、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秒でできる)

  1. 一覧で「詳細」リンクを右クリック → 検証
  2. <a>要素に固有のクラスや属性があれば それをセレクタに
  3. なければテンプレ側で class="detail-link" を追加して安定化

おまけ:セキュリティ対策の必要性

  • tabnabbing対策target="_blank" だけだと、リンク先から window.opener 経由で 元タブを書き換えられる可能性があります。rel="noopener"(または window.open(..., '_blank', 'noopener'))で opener を断ちます。
    参考: MDN: rel="noopener"MDN: Window.opener

  • 暗黙 noopener でも“明示”が堅実:モダンブラウザは _blank暗黙で noopener を適用しますが、全ユーザが最新とは限りません。互換性と意図の明示のため、属性を付けておくことを推奨します。
    参考: MDNOWASP: Reverse Tabnabbing

  • noreferrer の扱い:必要に応じて 参照元(Referrer)を送らないようにできます(多くの環境で noopener 相当の効果も含む)。ただし 計測に影響するため、外部リンクのみに付ける等の運用ルール化が無難です。
    参考: MDN: rel="noopener" の説明内での言及StackOverflow 議論

まとめ

kViewerの一覧→詳細の導線を「別タブで開く」ようにすると、戻ったときに検索条件やスクロールが消える問題を避けやすくなり、複数レコードの比較もスムーズになります。

  • 一覧の「詳細」クリックをフックして、window.open() で詳細URLを別タブ表示
  • 既存の遷移は preventDefault() / stopImmediatePropagation() で止めて上書き
  • セキュリティは noopener(必要なら noreferrer)で reverse tabnabbing 対策

うまく動かない場合は、まず 「詳細」ボタンのセレクタ を見直してください(テンプレ差でここが一番ズレます)。
気になる点や「このテンプレだとセレクタが合わない」などがあれば、コメントで教えてください。
(可能なら、DevToolsで見た「詳細」ボタン周りのHTML断片も貼ってもらえると早いです)

次回予告

  • スクリプト編集なしで複数画面へのリンクを掲載する汎用メニューをkViewerで作成する方法
  • 複合条件で検索するモーダル(文字列・日付範囲・ラジオ・数値など)を実装する方法

この記事が役に立ったら いいね / ストック をお願いします。
「このテーマも欲しい」という要望があればぜひコメントください。
実機検証の結果(ブラウザ/OS/テンプレ)も共有いただけると、付録に反映して更新します。

3
0
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
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?