レコードにタグを付ける・外す・候補から選ぶ・タグで探すためのフィールドです。画面ではチップ (× で外せる) で表示します。
- 付いているタグがチップで並ぶ。並びは付けた順 (左が古い)
- タグの確定は 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、データソース (対象と同じ)。
生成内容:
- タグ付けモジュール
<Module>Tags(Id/OwnerId/Name+TagLinkContractField)。一覧レイアウトは要らない - タグを付けるモジュールに TagField
Tags(TagModuleName=<Module>Tags)。同名の TagField が結び付きなしで既にあれば、それに入れる - 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 でない |
| メンバ | 説明 |
|---|---|
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) を置いたモジュールでは、タグの追加 / 削除が版の差分に出ます。「この版に置き換える」でタグも戻ります
チップは .tag-chip、入力欄全体は .tag-editor、閲覧表示は .tag-view です。色や角丸を変えたいときは app.css で上書きしてください。
.tag-chip { background: #eef3ff; border-color: #c7d7ff; }