model の内部構造と反映条件
model は実行時にレコードモデルから組み立てる動的オブジェクトです。項目値の読み書きだけでなく、行表示の設定や ReadOnly などを受け取ります。実行条件によって対象レコードの有無が変わり、代入した値にも権限と保存処理の段階があります。
公式マニュアルの model にはプロパティ、使用例、アクセス権による制限が載っています。このページでは、同じ model が実行条件によって何を指すか、代入した値がどの段階まで反映されるか、公式の表だけでは判定できない境界を扱います。
値の作られ方
実行時に ServerScriptUtilities.Values() がレコードから名前と値の組を作り、ServerScriptModel の ExpandoObject に格納します。Title や Status などの標準項目、ClassA・NumA・DateA・DescriptionA・CheckA・AttachmentsA といった拡張項目、Comments が対象です。課題と結果では、Status など一部の標準項目の取り出し方が分かれます(Values())。
受け皿の Model は辞書として値を入れる ExpandoObject です。項目ごとの専用 JavaScript クラスを作っているわけではありません。
public readonly ExpandoObject Model = new ExpandoObject();
data?.ForEach(datam => ((IDictionary<string, object>)Model)[datam.Name] = datam.Value);ServerScriptModel.cs の宣言、コンストラクタ の抜粋です。
値の読み出しには項目の CanRead 判定が入り、読めない項目は null として渡されます。サイト設定の ColumnHash に無い名前も null です。ReadOnly だけはこの判定を通さずに入ります(ReadNameValue())。model に名前が見えても、必ず値を取得できるとは限りません。
| 項目 | 渡される値の特徴 | 根拠 |
|---|---|---|
NumA など | 空欄は Nullable 項目なら null、それ以外は 0 | 数値の取得 |
DateA など | 通常は日付値。計算式用の呼び出しではクライアント時刻の文字列 | 日付の取得 |
AttachmentsA など・Comments | JSON 文字列 | 添付ファイルとコメント |
Status・Manager・Owner | 課題・結果それぞれのモデルから取得する。Manager(管理者)・Owner(担当者)はユーザー ID | 課題と結果 |
if (model.Status === 900) {
context.Log(model.Title);
}実行条件と対象レコード
「行表示の前」は一覧の対象レコードを itemModel: this として渡します。一方、「画面表示の前」の実行メソッドは 2 つあり、BaseModel の実装は itemModel: null、課題・結果の親クラス BaseItemModel の override は itemModel: this を渡します(BaseModel、BaseItemModel)。一覧の画面表示前は、SiteSettings.GetServerScriptModelRow() が itemModel なしで呼ばれ、BaseModel を継承した ItemModel の実装、つまり itemModel: null の側を通ります(GetServerScriptModelRow())。同じ条件名だけを見て、model.Status に値があると決めることはできません。
Execute() は itemModel が null なら空の BaseItemModel を作ります。その後 Values() で model の値を組み立てます(Execute())。したがって「画面表示の前」に model 自体が存在しても、その model が一覧の各行のレコードとは限りません。
itemModel = itemModel ?? new BaseItemModel();空の BaseItemModel は IssueModel・ResultModel ではないので、Status・Manager などの名前そのものが model に作られません(undefined になる)。行の呼び出し元は this を渡します(行)。
| 呼び出し元 | 渡される itemModel | model で扱う対象 |
|---|---|---|
| 一覧の画面表示前 | null | 空の基底モデル。Status などの名前も無い |
| 一覧の行表示前 | 行から作った IssueModel / ResultModel | 現在描画中のレコード |
| エディタの画面表示前 | 編集対象の IssueModel / ResultModel | 編集中のレコード |
| サイト設定読み込み時 | null | 実レコードを伴わない |
一覧の呼び出し順は、画面表示前が IssueUtilities.Index()、行表示前が HtmlGrids.cs にあります。エディタの呼び出しでは編集対象を渡します(Editor()。自動ポストバックで項目を描き直す EditorFields() も同じ)。
一覧のレコード値で分岐するなら
一覧の各行の Status や ClassA を使う処理は「行表示の前」に置きます。「画面表示の前」は画面共通の処理に使い、レコード値が必要なら呼び出し元を確認します。
項目値を書き換えたとき
model は変更されたプロパティ名を記録します。実行後、その名前からサイト設定の項目を探し、編集可能と判定された項目だけを実際のレコードモデルへ反映します(変更検知、変更名の取得、反映対象の絞り込み)。
private void DataPropertyChanged(object sender, PropertyChangedEventArgs e)
{
ChangeItemNames.Add(e.PropertyName);
}これは変更名を記録する箇所です。記録した名前を編集可能な Column に絞ってから値を戻します。
model.ClassA = '確認済み';model に任意の名前を追加すること自体はできますが、その名前が保存対象になるわけではありません。ExtendedRowCss・ExtendedRowData は通常の項目値とは別に読み出されます。
図を読み込み中…
拡張項目は BaseItemModel.SetValue() へ、課題・結果の標準項目は専用の setter へ戻されます。model.ReadOnly は項目の変更名とは別に反映されます(SetValues())。通常の代入はメモリ上のレコードモデルへの反映であり、直ちに DB 更新を意味しません。UpdateOnExit が真なら課題・結果の処理が Update() を呼びます(課題、結果)。
同じ代入でも保存に進む条件が違う
| 条件 | 代入後の処理 | DB への反映 |
|---|---|---|
| 作成前 | Create() がサーバースクリプトを実行した後に CreateStatements() を組み立てる | 通常の作成処理に含まれる |
| 更新前 | Update() がサーバースクリプトを実行した後に更新処理を進める | 通常の更新処理に含まれる |
| 行表示の前 | 一覧の行を描くためにレコードモデルへ反映 | 代入だけでは DB 更新しない |
UpdateOnExit = true | 課題・結果の SetValues() が Update() を呼ぶ | 追加の更新処理が走る |
作成前・更新前の実行位置は IssueModel.Create() と IssueModel.Update() で確認できます。どちらもスクリプトを保存処理より先に実行するので、その後の通常の保存処理が変更済みモデルを使います。
// 条件: 更新前
if (model.Status === 900) {
model.ClassA = '完了';
}この例は更新処理の途中でレコードモデルの ClassA を変えます。ClassA が更新可能な項目なら、後続の更新処理にその値が渡ります。行表示の前に同じコードを書いた場合は、一覧の描画に使うモデルの変更にとどまります。
同じ値を代入しても反映先は項目名で決まる
拡張項目は SetExtendedColumnValues() が model.SetValue() に渡し、標準項目は SetIssueModelValues() / SetResultModelValues() が項目ごとに変換して戻します。例えば Status は整数化、Manager と Owner はユーザー ID から SiteInfo.User() を引き直し、日付項目は日付用の変換を通ります(課題の setter)。JavaScript 側で任意の型を入れればその型のまま保存される、という構造ではありません。
値が見えない・変わらないときの確認順
| 症状 | ソースで確認する箇所 |
|---|---|
一覧の画面表示前で model.Status が期待した行の値にならない | itemModel: null から空の基底モデルを作る経路。行表示前へ処理を移す |
項目名はあるのに値が null | Values() の CanRead 判定 |
| 代入後の画面や保存値が変わらない | 変更名が ColumnHash にあり、CanEdit を通るか |
| 表示時に代入したが DB が更新されない | レコードモデルへの反映と Update() 呼び出しの違い |
| 行全体が編集できなくなった | model.ReadOnly と columns.項目名.ReadOnly の取り違え |
レコード全体の ReadOnly と項目の ReadOnly
model.ReadOnly はレコードモデル全体の ReadOnly に戻ります。一覧で項目を編集できる状態で描くか(一覧の編集モード)は、行全体の条件(Locked・ReadOnly)を見たあとで、項目の column.GetReadOnly() を見て決めます(一覧の編集分岐)。特定項目だけを読取専用にするなら columns.ClassA.ReadOnly を使います。
行全体に効く値
| 書く値 | 一覧での反映先 | 用途 |
|---|---|---|
model.ExtendedRowCss | 行の <tr> の class | 行全体に CSS を当てる |
model.ExtendedRowData | 行の <tr> の data-extension | 行にデータを渡す |
どちらもスクリプト終了後に ServerScriptModelRow に取り出され、一覧の行を組み立てる処理で使われます(SetRow()、HtmlGrids.cs)。一覧の各レコードで「行表示の前」のスクリプトが実行されるため、行ごとに条件を変えられます。
// 条件: 行表示の前
if (model.Status === 900) {
model.ExtendedRowCss = 'completed-row';
}.grid tr.completed-row td {
background-color: #e8f5e9;
}ExtendedRowCss は CSS のクラス名を渡します。特定の項目だけを変える場合は、columns の ExtendedCellCss を使います。