Skip to content

画面のHTML・CSSとレイアウトの構造 ​

第2版作成 最終更新 (日本時間)
確認バージョン1.5.8.1

プリザンターの画面をカスタマイズするときは、画面全体の枠、フォーム、項目の枠、入力要素を分けて見ると、CSSを当てる場所と影響範囲を判断できます。一覧では、表そのものとスクロール領域にも境界があります。

ここでは通常の記録テーブルの一覧・新規・編集画面を中心に、本体ソースから構造を説明します。テーマ、権限、項目設定、コメントの有無によって要素は増減します。図とツリーは主要部分を整理したもので、生成HTMLの全文ではありません。テーマ改善の動作確認モジュールの構造は別ページで扱います。

画面全体の枠と画面固有の領域 ​

共通部分は HtmlTemplates.MainContainer が作り、Content が #Application の中へ画面固有の処理を差し込みます。新規・編集画面の項目配置や一覧のフォームは、ResultUtilities 側が作ります(共通の枠、画面の差し込み)。

図を読み込み中…

同じ #MainForm でも、編集画面では #Editor の内側、一覧では #Application の画面固有領域に置かれます。#MainForm input のような広い指定は、画面によって別の入力群に当たります。編集画面だけを対象にするなら #Editor を起点にします(一覧のフォーム、編集のフォーム)。

要素・領域主な役割調べるソース
#MainContainer・#Application共通の画面枠、画面固有の内容の受け皿HtmlTemplates.cs
#Header・#Footer共通ヘッダー・フッターの見た目style.scss・legacy.scss
#Editor・#MainForm編集画面と入力を送るフォームResultUtilities.cs
#EditorTabsContainer・#EditorTabsタブ全体とタブの見出しリストResultUtilities.cs
#MainCommandsContainer・#MainCommands作成・更新などの操作領域style.scss、各画面のコマンド生成処理
#Message操作結果などのメッセージHtmlTemplates.cs・style.scss

CSSはどの順で重なるか ​

テーマによって読み込む本体CSSが違う ​

cerulean・green-tea・mandarin・midnight はCSS用のテーマバージョンが2.0、その他は1.0として判定されます(テーマ判定)。

HtmlStyles.LinkedStyles は、Normalize、テーマのjQuery UI、Material Symbolsを読み込んだ後、テーマバージョンに応じた本体CSSを読み込み、テーマの custom.css を読み込みます(読み込みの分岐)。

条件本体CSS元になるファイル
テーマバージョンが2.0未満legacy.min.csswwwroot/src/styles/legacy.scss
テーマバージョンが2.0以上style.min.csswwwroot/src/styles/style.scss
モバイル・レスポンシブなどの条件を満たす世代に対応する *-responsive.min.csslegacy-responsive.scss・style-responsive.scss
各テーマthemes/{テーマ}/custom.csswwwroot/src/clone/assets/themes/

ソースのパスは Implem.PleasanterFrontend からの相対パスです。レスポンシブCSSの条件には Parameters.Mobile.Responsive、context.Mobile、context.Responsive、サイトのResponsive設定も含まれます。画面幅だけで読み込むファイルを決めているわけではありません。

第二世代テーマにも従来のレイアウトが残る ​

style.scss の先頭は @use 'legacy' as * です。第二世代のCSSは従来CSSから独立した全面的な置き換えではなく、従来の定義を含めて上書きします。 例えばフォーム全体にFlexを使っていても、その中の項目にはfloatによる配置が残ります(従来CSSの読み込み、フォームのFlex、項目のfloat)。

色は var(--base-text)・var(--editor-bg)・var(--primaryColor) などのCSS変数を参照します。ceruleanの custom.css がこれらの値を定義しています。背景色や文字色を揃えるときは、現在のテーマが使う変数と、Computedでの最終値を確認します(変数を使う本体CSS、テーマの変数定義)。

サイトのスタイルと拡張スタイル ​

headでは本体・テーマCSSに続いて拡張スタイルへのリンクが出力されます。通常のテンプレートでは、画面内容の後にサイトの全画面用スタイルと userStyle がstyle要素として出力されます(headの順序、サイトスタイルの出力位置、style要素の生成)。

後から読み込まれたCSSでも、セレクタの詳細度や !important によっては上書きできません。開発者ツールのStylesで適用・打ち消しを調べ、Computedで最終の値を確認します。

編集画面――フォーム全体とコメント領域 ​

text
#Editor
└─ form#MainForm.main-form.confirm-unload
   ├─ #RecordHeader
   ├─ #EditorComments                コメントを表示する場合
   ├─ #EditorTabsContainer.tab-container
   │  ├─ ul#EditorTabs
   │  ├─ fieldset#FieldSetGeneral
   │  ├─ fieldset#FieldSetTab{ID}    追加タブ
   │  ├─ fieldset#FieldSetHistories  履歴の条件を満たす場合
   │  ├─ #FieldSetRecordAccessControl
   │  └─ コマンド領域
   └─ hidden input群

根拠は編集領域の組立て、タブのリンク先、追加タブです。コマンド領域は、この編集画面ではタブコンテナを生成する処理の中にあります。CSSファイルのコメントに書かれた親子関係だけでなく、HTMLの生成処理と実際のDOMも確認してください。

従来CSSはタブ領域を73%、コメント領域を27%とし、タブ領域の .max では100%にします。第二世代CSSではMainFormを折り返し可能なFlexにし、コメント領域を order: 1 と position: sticky にします。DOMではコメントがタブより先にあっても、画面上の順序はCSSでも変わります(領域の幅、フォーム、コメントの配置)。

コメント欄の幅だけを変えてタブ領域をそのままにすると、合計幅や折り返しに影響します。フォームにはコメント側の背景を作る幅27%の疑似要素もあるため、左右の領域全体を変えるときは、その疑似要素も含めて確認します(MainFormの疑似要素)。

1項目の構造――FieldとControlを分ける ​

通常の編集可能なテキスト項目を整理すると、次の構造になります。

html
<!-- 構造説明用の例。実際には属性や追加要素が入ります。 -->
<div id="Results_TitleField" class="field-normal">
  <p class="field-label">
    <label for="Results_Title">タイトル</label>
  </p>
  <div class="field-control">
    <div class="container-normal">
      <input id="Results_Title" class="control-text" type="text">
    </div>
  </div>
</div>

Field は外側のdiv、Label は p.field-label とlabel、Control は div.field-control と .container-normal を生成します。項目の種類によって入力要素やコンテナのタグは変わります(Field、Label、Control)。

狙うものタイトル項目での例用途
項目全体#Results_TitleField項目幅、項目全体の余白・表示切替
ラベルの枠#Results_TitleField > .field-labelラベル列の幅・配置
ラベル文字label[for="Results_Title"]文字色・太さ
入力側の枠#Results_TitleField > .field-control入力領域の配置
入力側の内枠.field-control > .container-normalラベル幅を避ける余白
入力要素#Results_Title値、入力枠、フォーカス

通常の項目生成では、controlIdに Field を付けて外側のIDを作ります。プレビュー、一覧編集の接尾辞、読取専用などでは同じ入力要素があるとは限りません。入力タグを決めつけず、現在の画面で確認してください(IDの受け渡し、FieldのID)。

項目幅とラベル幅は別の値 ​

従来CSSの通常項目はfloatで並び、幅340pxです。ラベル枠は幅120px、入力側の内枠は左余白120pxで、ラベルと重ならないようにします。field-wide は幅100%かつ clear: both です。第二世代CSSは通常項目の高さを4em、wide項目の最小高さを4emへ上書きします(通常項目、wide項目、高さの上書き)。

入力要素の幅だけを広げても、外側の340pxの枠まで広がるとは限りません。ラベル枠の幅だけを変えると、入力側の左余白120pxとの関係が崩れます。幅を変えるときは、この3層を確認します。

サーバー設定が付けるクラス ​

FieldCss の生成処理は、項目設定に加えて NoWrap なら both、非表示なら hidden、文字寄せやExtendedFieldCssを追加します。ControlCssは入力側へ、自動ポストバックなら control-auto-postback、文字寄せやExtendedControlCssを追加します(FieldCss、ControlCss)。

both は clear: both です。NoWrap をテキストの折り返し禁止の指定だと解釈して、white-space: nowrap と同一視しないでください。hidden は display: none、hidden is-important は display: none !important です(both、hidden)。

CSSに値を書く設定と、クラス名を追加する設定も区別します。各プロパティの適用先はCSSをコントロールする要素を参照してください。

タブとセクションの内側 ​

タブの見出しは ul#EditorTabs で、基本タブのリンク先は #FieldSetGeneral です。タブパネルには TabsPanelField がfieldsetを出力し、その内側に .tabs-panel-inner を作ります(タブ見出し、パネルの内枠)。

ブラウザ側では、jqueryui.js が .tab-container にjQuery UIの .tabs() を適用します。サーバーが生成したHTMLと、ライブラリ適用後にElementsで見るDOMの両方を確認するのが、タブの表示や選択状態を調べる入口です(タブの初期化)。

セクションは次の2層を持ちます。

text
#SectionFields1Container.section-fields-container
├─ 見出しを含む領域
│  └─ label.field-section[for="SectionFields1"]
└─ #SectionFields1.section-fields
   └─ セクション内の項目

外側を隠すと見出しも消え、中身を隠すと見出しを残せます。折りたたみ状態では中身へ hidden が付きます。また Hide がtrueならセクションは生成されません。存在しない要素はCSSやToggleで表示できません(セクションの生成)。

一覧――表とスクロール枠は別の構造 ​

一覧のMainFormは、ビュー選択、フィルタ、集計、#ViewModeContainer、コマンド領域などを含みます。通常の表は table.grid とその外側の grid-container.grid-wrap です(一覧フォーム、表の生成、表の外枠)。

text
#MainForm
└─ #ViewModeContainer
   └─ grid-container.grid-wrap
      └─ table#Grid.grid
         ├─ thead
         │  └─ tr > th[data-name="項目物理名"]
         └─ tbody
            └─ tr.grid-row[data-id="レコードID"] > td

ヘッダーには項目物理名の data-name、行にはレコードの data-id と行用クラスが付きます。行CSSとセルCSSは別の適用先です。列ヘッダーに data-name があることから、すべてのtdにも同じ属性があると仮定しないでください(ヘッダー、行の属性、セルCSSの組立て)。列のセルを取得する方法は項目の取得とDOMイベントで扱います。

GridTable はid引数を受け取った場合、外枠へそのIDと Wrap を合わせた名前を付けます。ただし通常の一覧の生成は、属性として表へ Grid のIDを渡しています。外枠に常に #GridWrap があると決めつけず、grid-container.grid-wrap と実際のDOMで確認します(IDの生成、一覧側の引数)。

Shadow DOMの中にあるスクロール領域 ​

スクロールが有効な grid-container はopenなShadow Rootを作り、内側に .app-grid-inner・.app-grid-frame・slotを生成します。表は通常のDOMに残り、slotから表示されます。スクロールする枠の .app-grid-frame はShadow DOM内です(Shadow Rootの生成、slotの構造、スクロールCSS)。

そのため #Grid の文字色などは通常のCSSで指定できますが、通常のサイトCSSに .app-grid-frame { ... } と書いてもShadow DOMの内部を対象にはできません。開発者ツールでShadow Rootを開き、表とスクロール枠を分けて確認します。

第一世代テーマやモバイル・レスポンシブ条件では、サーバーがこの表のスクロールを無効にする分岐があります。Shadow Rootがある前提のコードにせず、その有無を確認してください(スクロールの分岐)。

実例――タイトルの幅とラベルを変える ​

テーブルのスタイルへ置き、新規・編集画面を対象にします。通常の編集可能なタイトル項目を前提に、外側の項目幅を変え、ラベルの文字を太くします。

css
#Editor #Results_TitleField {
    box-sizing: border-box;
    width: min(100%, 680px);
    clear: both;
}

#Editor #Results_TitleField > .field-label > label {
    font-weight: 600;
}

#Results_Title のinputだけではなくFieldの幅を変えます。ラベル列の幅と入力側の左余白は変えません。入力タグが違う読取専用状態や、別のテーブル種類へ使う場合は、IDと適用結果を確認します。

このCSSは通常の記録テーブル用の指定です。テーマや表示幅ごとの最終描画は実機で確認してください。画面全体の .field-normal を変更するより、対象項目と画面を絞って、隣の項目やフィルタへの影響を確認しやすくします。

調査サンプル――適用後のレイアウトを一覧する ​

対象の画面で、ブラウザの開発者ツールのConsoleに貼ります。DOMとComputedの値を読み、Shadow DOMのスクロール枠も存在する場合だけ調べます。

js
(function () {
    const selectors = [
        '#MainContainer', '#Application', '#Editor', '#MainForm',
        '#EditorTabsContainer', '#EditorComments', '#Results_TitleField',
        '#Results_TitleField > .field-label',
        '#Results_TitleField > .field-control',
        '#Results_TitleField .container-normal', '#Results_Title', '#Grid'
    ];
    const elements = selectors.flatMap(function (selector) {
        return Array.from(document.querySelectorAll(selector)).map(function (element) {
            const style = getComputedStyle(element);
            return {
                selector, tag: element.tagName, id: element.id,
                classes: element.className,
                parent: element.parentElement?.id || element.parentElement?.tagName,
                display: style.display, float: style.cssFloat, width: style.width,
                marginLeft: style.marginLeft, clear: style.clear, order: style.order
            };
        });
    });
    console.table(elements);
    console.table(Array.from(document.querySelectorAll('grid-container')).map(function (host) {
        const frame = host.shadowRoot?.querySelector('.app-grid-frame');
        return {
            id: host.id, classes: host.className,
            hasShadowRoot: Boolean(host.shadowRoot),
            scrollTop: frame?.scrollTop, scrollLeft: frame?.scrollLeft,
            overflow: frame ? getComputedStyle(frame).overflow : undefined
        };
    }));
})();

一覧では編集画面用のセレクタに該当する行が出ません。これは、該当するDOMがない場合を区別するための動作です。ダイアログ編集は別のフォームを使うので、MainFormだけで判断せずフォームと送信バッファも確認します。

再描画で何が失われるか ​

自動ポストバックの返却では、値だけを変更する応答と、ReplaceAll でフィールドを丸ごと差し替える応答があります。差し替えられる要素へブラウザで直接付けたクラス、追加した説明、直接登録したイベントは、新しい要素には引き継がれません(フィールド応答)。

スタイルシートのID・クラスに対する指定は、新しい要素が同じセレクタに一致すれば再び適用されます。独自要素の作り直しはdispatchの実装サンプル、差し替え後も動くイベントはDOMイベントの接続を参照してください。

関連ページ ​

変更履歴

第2版テーマ改善前後のHTML・CSS構造を比較図で解説
第1版画面のHTML・CSS構造とレイアウト調査の解説を追加