Skip to content

Latest commit

 

History

History
150 lines (113 loc) · 14.5 KB

TagField.md

File metadata and controls

150 lines (113 loc) · 14.5 KB

TagField - タグ入力

レコードにタグを付ける・外す・候補から選ぶ・タグで探すためのフィールドです。画面ではチップ (× で外せる) で表示します。

画面での動き (先にこちら)

入力欄 (詳細画面・一覧の列)

  • 付いているタグがチップで並ぶ。並びは付けた順 (左が古い)
  • タグの確定は Enter か スペース (半角・全角)。打った文字にスペースが入っていれば (貼り付けなど複数が一度に入ったとき)、そこで分けてそれぞれタグにする。タグにスペースは入らない (2 語のタグは _ や - でつなぐ)。「,」「、」「,」は区切らない (タグの一部)。区切りの文字は設定にしない (2026-10-08 決定)
  • 区切りの集合は TagField.Separators の 1 か所で持ち、区切って整える処理 (TagField.Normalize)・入力欄 (TagInputComponent)・キー入力 (tag-interop.js) はそれを使う
  • タグ名は 200 文字まで (入力欄・タグ付けモジュールの Name・DDL の列の長さが同じ値)。長すぎるタグは足さずにエラーを出す。同じ操作でほかのタグが足されても、このエラーは残る (次に正しく足し外ししたときに消える)
  • × を押すとそのチップが外れる。入力欄が空のときに Backspace を押すと右端のチップが外れる
  • 入力欄の外へフォーカスが移ると、打ちかけの文字もタグになる (ウィンドウの切り替えでは確定しない)
  • IME の変換中のキーは無視する。変換を確定する Enter でタグは作られず、確定後にスペースがあれば分ける
  • 同じタグは重ねない。同一判定は完全一致 (AI と ai は別のタグ)。表記の揺れは、入力中に既存のタグが候補に出ることで防ぐ
  • タグが 1 つも無ければ、入力欄にプレースホルダが出る
  • 閲覧 (読取専用) のときはチップだけを表示する

候補

  • 入力欄に入ると (何も打っていなくても)、同じモジュールのレコードに付いているタグがよく使われている順 (同じ件数なら名前順) に最大 10 件出る。文字を打つと、打った文字を含むものに絞って同じ順で出る
  • 候補はサーバーが絞ったものをそのまま出す (手元では絞り直さない)。既に付いているタグだけ除く。文字が変わると前の候補は捨てる。打った文字に合うタグが無ければ何も出ない
  • 候補の問い合わせは、文字が変わるごとに 1 回 (区切りで確定したときも、残りの文字で 1 回)。何も打っていないときの候補は 1 回だけ問い合わせて、あとは使い回す
  • ↑↓ と Enter、またはクリックで選ぶ。選ばずに Enter を押せば、打った文字がそのままタグになる
  • 候補の集計はタグ付けモジュールに対して走るので、効くのはタグ付けモジュールの読み取り条件 (UserRead / DataRead)。タグを付けるモジュール (親) の行の条件は自動では効かない。見せたくないタグがあるなら、タグ付けモジュールに読み取り条件を書く

保存

  • 足し外しは何も書かない。レコードを保存したときに一緒に書く (1 つのトランザクション・レコードの書き込み権限)。保存せずにやめれば何も残らない
  • レコードを消すと、そのタグは一緒に消える (DeleteTogether。既定 on。一覧フィールドの DeleteTogether と同じ仕組みで、レコードの削除の前にタグ付け行を 1 行ずつ消す。タグ付けモジュールの CanDelete が要る)。論理削除のモジュールでも、タグ付けモジュールに論理削除が無ければタグ付け行は物理的に消える (編集履歴の「復活」は版から作り直す)
  • DeleteTogether を off にすると本体は消さない。DDL の外部キー (ON DELETE CASCADE) に任せるとき (SQLite は接続文字列に Foreign Keys=True を付ける)、または論理削除のモジュールでタグ付け行を残して復活でそのまま戻したいときに使う
  • レコードをコピーすると、タグもコピーされる (コピーを保存したときに新しい行として書く。コピー元のタグには触らない)

検索 (検索レイアウトに置いたとき)

  • 同じチップの入力欄で、探すタグを 1 つ以上入れる。候補も同じように出る
  • 横の選択ですべて含む (既定) / いずれかを含むを切り替える (IsSimpleSearchParameter で選択を隠し、既定で探す)
  • タグ名は完全一致 (「展示会」で「展示会2026」は当たらない。大文字小文字も区別する)
  • 誰にも付いていないタグを入れると、「すべて含む」では 0 件、「いずれかを含む」ではそのタグは足しにならない
  • タグを 1 つも入れていなければ絞らない (選択に関係なく)
  • 条件はタグ名で持つので、画面に戻ったとき・URL を共有したときにも復元でき、後から付いたタグも反映される
  • 親の検索画面に子のタグを置いたとき (例: 会社の検索に 社員.タグ)、「すべて含む」はその会社の人のタグを合わせて全部ある会社に合う (人ごとに全部持っている必要はない)。この 2 段 (一覧 → 一覧) の動きは Extras のテスト (会社 → 社員 → タグ、すべて含む / いずれか) で確かめている

一覧の列

  • 一覧レイアウトにも置ける。ページの行の分のタグは、一覧の読み込みに同梱されて届く (行ごとの問い合わせは出ない)
  • 編集できるセルではチップ + 入力欄、閲覧ならチップだけ

データの形

tag_tests (タグを付けるモジュール)      tag_test_tags (タグ付け。タグを付けるモジュールごとに 1 つ)
├── id ◄──────────────────────────── owner_id   FK ON DELETE CASCADE (DeleteTogether を使わないときの保険)
└── ...                               name       タグ名をそのまま持つ
                                      一意: (owner_id, name)   インデックス: name
  • タグ付け (TagLinkContractField を置いたモジュール): 1 行 = あるレコードに付いたタグ 1 つ。フィールドは Id / OwnerId (IdField。タグを付けたレコードの Id) / Name (TextField。タグ名) の 3 つ。契約の役割 OwnerId / TagName がどのフィールドかを示す (既定はこの 2 つの名前)。タグのマスタは無い
  • TagField: タグ付けモジュールを従属レコードとして持つ素のフィールド (Calendar / Gantt / TaskBoard / MarkerList と同じ形)。設定はタグ付けモジュール名 1 つ。結び付き (OwnerId.Value = Id.Value・並び Id・読む列 Id / OwnerId / Name) はこの設定と契約から組み立てて本体に見せる (本体 1.3.44 の IChildRecordsFieldDesign。JSON には出ない)。本体はこれで検索 (タグ付け表への EXISTS)・親の検索の 社員.タグ の解決・一覧の行への同梱を行う。編集履歴 (タグの追加 / 削除が版の差分に出る・復元) は従属レコードの宣言 (GetOwnedRecords) から読む
  • 読み込み: 詳細では 1 回問い合わせる。一覧の行では同梱された行を使う (無ければ読まない)。保存: 足したタグは行の追加、外したタグは行の削除として、レコードの保存に載る
  • データはタグ付け行の一覧 (ListFieldData)。同梱・編集履歴・意味検索の文章はこれを読む

タグに色・説明・並び順を持たせたい、複数のモジュールでタグの一覧を共有したい、「決まったタグだけ」にしたい、が要件になったときに、そのための設定やモジュールを足します。今は入れていません。

タグのセットアップ

  • デザイナ: メニュー Tools > タグのセットアップ (対象モジュールを選ぶだけ)
  • CLI (headless): <designer.exe> tag-setup "<projectDir>" --target <Module> [--ddl-out <path.sql>]

対象モジュールから決まるもの: フィールド名 Tags、タグ付けモジュール名 <Module>Tags、テーブル名 (モジュール名の snake_case 複数形)、列 owner_id / name、データソース (対象と同じ)。

生成内容:

  1. タグ付けモジュール <Module>Tags (Id / OwnerId / Name + TagLinkContractField)。一覧レイアウトは要らない
  2. タグを付けるモジュールに TagField Tags (TagModuleName = <Module>Tags)。同名の TagField が結び付きなしで既にあれば、それに入れる
  3. DDL (3 列の表を DB ごとに組む): テーブル、(owner_id, name) の一意インデックス、name のインデックス、レコードへの外部キー (ON DELETE CASCADE。SQLite は列に書く。タグ付け行の削除は既定では TagField の DeleteTogether が行うので、外部キーは DeleteTogether を off にして DB に任せるときの保険)。owner_id の型は対象の Id 列に合わせる (DB にあればその型、無ければ対象の Id が手入力なら文字列型、そうでなければ整数型。文字列の型は長さが分からないので既定の長さで作り、結果に「対象の Id 列と同じ長さにする」と出す)。既定の照合順序が大文字小文字を区別しない SQL Server / MySQL では、name の列に区別する照合順序を付ける (Latin1_General_CS_AS_KS_WS / utf8mb4_bin。完全一致を DB でも成り立たせる。候補の Like も区別する)

冪等: <Module>Tags が既にあれば作らない (ただし TagLinkContractField が無ければ結び付けずにエラーで止める)。結び付き済みの TagField は触らない。DDL は自動実行されない (結果画面の実行ボタン、または --ddl-out のファイルを実行する)。 画面への配置はデザイナで行う (TagField を詳細・一覧・検索のレイアウトに置く)。

デザイナー設定プロパティ

プロパティ デザイナ表示名 型 説明
TagModuleName タグ付けモジュール string タグ付けモジュール (TagLinkContractField を置いたモジュール)。セットアップが入れる。これだけで結び付きが決まる
DisplayName 表示名 string ラベル・編集履歴・意味検索の文章に使う
Placeholder プレースホルダ string タグが無いときの入力欄のプレースホルダ
DeleteTogether 親テーブルと一緒に削除 bool レコードの削除でタグ付け行も一緒に消す (既定 on)。off は DDL の外部キーに任せる (0.23 までの動き)
IsSimpleSearchParameter 簡易検索パラメータ bool 検索欄に一致の選択を出さない
SearchMatchDefaultValue 検索の一致の既定 enum All = すべて含む (既定) / Any = いずれかを含む。画面で切り替えられる
OnDataChanged データ変更イベント string タグが変わったときのスクリプト

検索条件 (結び付き) は設定ではなく TagModuleName と契約から決まるので、デザイナにも JSON にも出ません。候補の件数 (10 件) も設定にしません。検索欄のスクリプトイベント (OnSearchDataChanged) は要望があれば足します。

設計チェック

コード 内容
TagFieldDesign:1 TagModuleName のモジュールに TagLinkContractField が無い (空・不在は本体のチェック)
TagFieldDesign:2 DeleteTogether なのにタグ付けモジュールの CanDelete が false (レコードの削除が失敗する)
TagLinkContractFieldDesign:1 タグ付けモジュールの OwnerId が IdField でない、または Name が TextField でない

スクリプト API

メンバ 説明
Tags 付いているタグ (List<string>、付けた順、読み取り専用)
AddTag(string tag) タグを足す。同じタグは重ねない。区切りを含めば分けて足す
RemoveTag(string tag) タグを外す
SetTags(List<string> tags) タグを置き換える (外したタグを外し、無いタグを足す)
HasTag(string tag) そのタグが付いているか (完全一致)
SearchTags 検索条件で選んでいるタグ (get / set)
SearchMatch 検索の一致 (TagSearchMatch.All / TagSearchMatch.Any、get / set)

タグの変更は、ほかのフィールドと同じくレコードを保存したときに書かれます。

スクリプトの ModuleSearcher で読んだレコードのタグを読む (Tags / HasTag) ときは、Select で TagField を指定します (指定しないとタグ付け行は読まれません)。足し外し (AddTag / RemoveTag / SetTags) は、読んでいなければその前に 1 回読んでから行うので、Select が無くても二重になりません:

// 条件に合う人全員に、タグを足して保存する
void AddTag_OnClick()
{
    var ps = new ModuleSearcher<顧客企業社員>();
    ps.AddEqual(e => e.企業Id.Value, 企業Id.Value);
    ps.Select(e => e.Tags);
    var changed = new List<Module>();
    foreach (var p in ps.Execute())
    {
        if (p.Tags.HasTag("展示会2026")) continue;
        p.Tags.AddTag("展示会2026");
        changed.Add(p);
    }
    if (changed.Count > 0) this.Submit(changed);
}

配置の注意

  • 一括ダウンロード / 一括更新 (BulkFileTransfer) はタグを扱いません (タグはレコードの列ではないため)
  • 意味検索 (SemanticSearchField) の SourceFields に入れると、タグ名を並べた行が文章に入ります (保存時・再索引とも)
  • 編集履歴 (EditHistoryField) を置いたモジュールでは、タグの追加 / 削除が版の差分に出ます。「この版に置き換える」でタグも戻ります

CSS カスタマイズ

チップは .tag-chip、入力欄全体は .tag-editor、閲覧表示は .tag-view です。色や角丸を変えたいときは app.css で上書きしてください。

.tag-chip { background: #eef3ff; border-color: #c7d7ff; }