3.5. 用途を追加する¶
項目
標準で提供されているデータ用途、制約用途ではメタデータとして不足している場合、用途そのものを追加できます。
例として、辞書項目を画面へ表示する際の位置を用途として追加してみます。
用途の項目として
- x座標 (変数名:x)
- y座標 (変数名:y)
- 水平方向への配置位置 (変数名:align)
を持つものとします。
また、水平方向への配置位置はエイリアスが参照している辞書項目とは別の値を持てるものとします。
3.5.1. 用途の実装¶
用途を増やすには、クライアント側とサーバ側の両方の実装が必要です。
- クライアント側
- Vue.js のコンポーネント
- サーバ側
- 用途の項目を表現するクラス
- インポート・エクスポート時の設定、処理を行うクラス
- 設定ファイル
- 影響範囲検出のためのクラス
それぞれの実装で共通となる ID が必要です。この ID はシステムで一意とならなければなりません。 未知の用途が追加されることを考慮し、FQDN やモジュールIDなど競合しない文字列を検討してください。
この例では簡単のため sample_usage を用途のIDとします。
3.5.1.1. クライアント側の実装¶
クライアント側は、Vue.js のコンポーネントとして実装します。 「Vue.js のコンポーネント」の詳細は Vue.js の Web サイトを参照してください。
用途のコンポーネントとして必要なのは以下の項目です。
- id
- 上記「用途のID」を指定します。この文字列でサーバ側とクライアント側を紐づけます。
- aliasEditable
- エイリアスが参照元の辞書項目とは別の値を持てる項目のキーを指定します。
- inject
- 親コンポーネントから注入される関数を定義します。
- template
- 画面に表示する HTML のテンプレートを定義します。
- 辞書項目画面のツリー形式、表形式、差分表示の3つに対応するように定義します。
- props
- dictionaryItem: { type: Object, default: function() { return {} }
- 辞書項目がこのプロパティ名で引き渡されます。
- anotherItem: { type: Object, default: function() { return {} }
- 辞書項目がこのプロパティ名で引き渡されます。
- このプロパティは、差分表示画面で使用されます。
- readonly: { type: Boolean, default: false }
- このプロパティは、表敬式画面などで使用されます。
- computed
- 入力データ
- computed のゲッター、セッターを定義します。
- セッターでは、このコンポーネントを呼び出している親コンポーネントにデータの変更を伝えます。
これらを踏まえて実装します。
src/main/public/im_repository/sample.js
// // 表示形式用途のコンポーネントを定義します。 // const SampleUsage = { // 「用途のID」を指定します。この文字列でサーバ側とクライアント側を紐づけます。 id: 'sample_usage', // エイリアスが参照元の辞書項目とは別の値を持てる項目のキーを指定します。 aliasEditable: [ 'align' ], // 親コンポーネントから注入される関数を定義します。 inject: ['isEditable', 'getUsageData', 'getUsageDataValue', 'getAnotherUsageDataValue', 'hasDifference'], // 画面に表示する HTML のテンプレートを定義します。 template: '<usage usage-id="sample_usage" title="表示形式">' + '<usage-table>' + '<usage-table-row usage-id="sample_usage" title="x座標" property-key="x">' + '<input v-if="isEditable()" name="x" type="text" v-model="x">' + // 編集不可の場合は label で表示する '<label v-else>{{ x }}</label>' + '</usage-table-row>' + '<usage-table-row usage-id="sample_usage" title="y座標" property-key="y">' + '<input v-if="isEditable()" name="y" type="text" v-model="y">' + '<label v-else>{{ y }}</label>' + '</usage-table-row>' + '<usage-table-row usage-id="sample_usage" title="水平方向への配置位置" property-key="align">' + '<input v-if="isEditable()" name="align" type="text" v-model="align">' + '<label v-else>{{ align }}</label>' + '</usage-table-row>' + '</usage-table>' + '</usage>', computed: { x: { get() { return _.get(this.getUsageData(this.usageId), 'x', null) }, set(value) { this.$emit('merge', { usageId: this.usageId, usageData: { x: value } }) } }, y: { get() { return _.get(this.getUsageData(this.usageId), 'y', null) }, set(value) { this.$emit('merge', { usageId: this.usageId, usageData: { y: value } }) } }, align: { get() { return _.get(this.getUsageData(this.usageId), 'align', null) }, set(value) { this.$emit('merge', { usageId: this.usageId, usageData: { align: value } }) } } }, props: { // 読み込み専用かどうかのフラグ readonly: { type: Boolean, default: false } }, data() { return { usageId: SampleUsage.id } } } // // コンポーネントを追加します。 // if (window.imRepository && imRepository.usageComponents) { window.imRepository.usageComponents.push(SampleUsage) } else if (window.imRepository) { window.imRepository.usageComponents = [ SampleUsage ] } else { window.imRepository = { usageComponents: [ SampleUsage ] } }
3.5.1.1.1. 親コンポーネントから渡される情報¶
親コンポーネントから props へ渡されるものは次のものです。 上記例では readonly しか使用していません。
名前 | 型 | 説明 |
---|---|---|
dictionaryItem | Object | 辞書項目またはエイリアス |
anotherItem | Object | 差分表示時に使用される辞書項目またはエイリアス |
readonly | Boolean | 表形式や差分表示など読み込み専用を示すフラグ |
親コンポーネントから provide される関数は次のものです。
名前 | 引数 | 返り値 | 説明 |
---|---|---|---|
isEditable() | この項目が編集可能かどうかを返す関数です。 | ||
Boolean | 編集可能な場合 true を、編集不可能な場合 false を返します。 | ||
hasDifference(one, another) | 指定した2つのオブジェクトに差分があるかどうかを返す関数です。 | ||
{Object} one | 差があるかどうかを確認するオブジェクト | ||
{Object} another | 差があるかどうかを確認するもう一つのオブジェクト | ||
Boolean | 差がある場合 true を、差がない場合 false を返します。 | ||
getUsageData(usageId, defaults) | 辞書項目から、指定した用途IDの用途のデータを返します。 | ||
{String} usageId | 用途ID | ||
{Object} [defaults] | 用途が取得できなかった場合の値。オプショナル | ||
Object | 用途のデータを返します。該当するものがなければ null | ||
getUsageDataValue(usageId, key) | 辞書項目から、指定した用途IDの用途のデータから指定したキーに 該当する値を返します。 | ||
{String} usageId | 用途ID | ||
{String} key | 用途のデータのキー | ||
Object | 用途のデータの値を返します。 該当するものがなければ null を返します。 | ||
getAnotherUsageDataValue(usageId, key) | 差分表示の際に他方の辞書項目から、指定した用途IDの用途の データから指定したキーに該当する値を返します。 | ||
{String} usageId | 用途ID | ||
{String} key | 用途のデータのキー | ||
Object | 用途のデータの値を返します。 該当するものがなければ null を返します。 |
また、この他に親コンポーネントから provide されるオブジェクトも存在します。内部で使用しています。
名前 | 型 | 説明 |
---|---|---|
dictionaryItem | Object | 辞書項目またはエイリアス |
anotherItem | Object | 差分表示時に使用される辞書項目またはエイリアス |
readonly | Boolean | 表形式や差分表示など読み込み専用を示すフラグ |
eventHub | Object | 削除ボタンをクリックしたときのイベントをやりとりするオブジェクト |
3.5.1.1.2. 親コンポーネントで定義されているコンポーネント¶
3.5.1.1.2.1. usage コンポーネント¶
このコンポーネントは、標準の用途と同じ見た目を提供するものです。 必ず使わなければならないものではありませんが、見た目の調整などが軽減されるので利用をお勧めします。
div タグとして展開されます。また、内部に form タグを内包しています。
usage コンポーネントでは以下のプロパティを要求します。これらの値を指定してください。
名前 | 型 | 説明 |
---|---|---|
id | String | div の id。div の内部に生成される form の id にも使用されます。form の id は、この id に _form をつけたものです。 |
title | String | この用途の見出し名を指定します。 |
3.5.1.1.2.2. usage-table コンポーネント¶
このコンポーネントは、標準の用途と同じ見た目を提供するものです。 必ず使わなければならないものではありませんが、見た目の調整などが軽減されるので利用をお勧めします。 table タグとして展開されます。また、内部に tbody タグを内包しています。
table タグには imui-form クラスが指定されています。 別なクラスを指定したい場合は、 <usage-table class="some_class"> のように class 属性を指定してください。
3.5.1.1.2.3. usage-table-row コンポーネント¶
このコンポーネントは、標準の用途と同じ見た目を提供するものです。 必ず使わなければならないものではありませんが、見た目の調整などが軽減されるので利用をお勧めします。 tr タグとして展開されます。また、内部に差分の有無を示すセル、行のヘッダを示すセル、用途のプロパティを表示するセルの3つのセルを内包しています。
usage-table-row コンポーネントでは以下のプロパティを要求します。これらの値を指定してください。
名前 | 型 | 説明 |
---|---|---|
usage-id | String | 用途の id。 |
title | String | この用途のプロパティの名前を指定します。 |
property-key | String | この用途のプロパティのキーを指定します。 |
3.5.1.2. サーバ側の実装¶
サーバ側の実装で必要なのは以下の4つです。
- 用途の項目を表現するクラスの実装
- インポート・エクスポート時の設定、処理を行うクラスの実装
- 設定ファイルの追加
- 影響範囲検出のためのクラス
3.5.1.2.1. 用途の項目を表現するクラスの実装¶
このクラスの実装で大事なことは、用途の項目をどのようなデータ構造として扱うのか?を決めることです。 jp.co.intra_mart.foundation.repository.metadata.dictionary.usage.Usage を実装したクラスとして実装します。
クライアント側とは JSON 形式でデータのやり取りをします。 上記のクライアン側の例では
return { x: '', y: '', align: '' }
となっているので、サーバ側では Map<String, Object> としてデータを扱います。
また、align は参照先の辞書項目とは別の値を持てるとしたので、AliasEditable アノテーションを付けます。 もしこのアノテーションをつけ忘れると、画面で入力した値が、参照先の辞書項目の値で書き換えられた上で保存されます。
src/main/java/sample/usage/SampleUsage.java
public class SampleUsage implements Usage { /** * この用途のIDです。 */ public static final String USAGE_ID = "sample_usage"; private static final long serialVersionUID = 8444395879883374534L; // 用途の項目を格納する Map です。 private final Map<String, Object> usageData = new HashMap<>(); // x座標 private String x; // y座標 private String y; // 水平方向への配置位置 @AliasEditable private String align; /** * 用途の表示名です。 * 辞書項目の右側、用途の一覧にチェックボックスとともに表示されます。 */ @Override public String getName() { return "表示位置"; } /** * 用途のIDを返します。 */ @Override public UsageId getUsageId() { return UsageId.of(USAGE_ID); } /** * 用途の項目データを返します。 */ @Override public Object getUsageData() { usageData.put("x", this.x); usageData.put("y", this.y); usageData.put("align", this.align); return usageData; } /** * 用途の項目データを設置します。 * このメソッドは画面からと、永続化層の双方からセットされます。 */ @Override public void setUsageData(final Object data) { if (data != null && data instanceof Map<?, ?>) { @SuppressWarnings("unchecked") final Map<String, Object> map = (Map<String, Object>) data; this.x = Optional.ofNullable(map.get("x")).map(String.class::cast).orElse(StringUtil.EMPTY_STRING); this.y = Optional.ofNullable(map.get("y")).map(String.class::cast).orElse(StringUtil.EMPTY_STRING); this.align = Optional.ofNullable(map.get("align")).map(String.class::cast).orElse(StringUtil.EMPTY_STRING); } } /** * クライアント側の JavaScript 実装のパスを返します。 */ @Override public String[] getScriptPathList() { return new String[] { "im_repository/sample.js" }; } /** * x座標を返します。 * @return x座標 */ public String getX() { return x; } /** * y座標を返します。 * @return y座標 */ public String getY() { return y; } /** * 水平位置を返します。 * @return 水平位置 */ public String getAlign() { return align; } }
3.5.1.2.2. インポート・エクスポート時の設定、処理を行うクラスの実装¶
インポート・エクスポートで利用されるクラスです。用途の各フィールドに対してインポート・エクスポートで使用するキーを定義します。 jp.co.intra_mart.system.repository.metadata.dictionary.import_export.StandardUsageSerializer を継承したクラスとして実装します。
/** x座標のインポート・エクスポートに使用するキーです。 */ private static final String SAMPLE_USAGE_X = "sample_usage_x"; /** y座標のインポート・エクスポートに使用するキーです。 */ private static final String SAMPLE_USAGE_Y = "sample_usage_y"; /** 水平方向への配置位置のインポート・エクスポートに使用するキーです。 */ private static final String SAMPLE_USAGE_ALIGN = "sample_usage_align";
このキーは後述する インポート・エクスポートで使用する設定ファイルの追加 でも利用し、システムで一意とならなければなりません。 未知の用途、制約が追加されることを考慮し、FQDN やモジュールIDなど競合しない文字列を検討してください。 またシステムで利用している設定ファイルのキーにも競合しないように注意してください。
システムで利用している設定ファイルの配置場所は「WEB-INF/conf/im-repository-dictionary-config-type/im-repository-dictionary-config-type.xml」です。
getKeySettingsメソッドでインポート・エクスポートで使用するキーと用途の各フィールドの対応を設定することにより、エクスポート時の出力、インポート時の取込に対応させることができます。
public KeySetting[] getKeySettings() { return new KeySetting[] { KeySetting.of(SAMPLE_USAGE_X, "x"), KeySetting.of(SAMPLE_USAGE_Y, "y"), KeySetting.of(SAMPLE_USAGE_ALIGN, "align")}; }
validateメソッドはインポート時のインポート確認画面に出力されるエラーメッセージ、ワーニングメッセージを設定できます。 エラーメッセージが含まれる場合はインポートを継続できないので、内容に応じて設定してください。
src/main/java/sample/usage/SampleUsageSerializer.java
public class SampleUsageSerializer extends StandardUsageSerializer { /** x座標のインポート・エクスポートに使用するキーです。 */ private static final String SAMPLE_USAGE_X = "sample_usage_x"; /** y座標のインポート・エクスポートに使用するキーです。 */ private static final String SAMPLE_USAGE_Y = "sample_usage_y"; /** 水平方向への配置位置のインポート・エクスポートに使用するキーです。 */ private static final String SAMPLE_USAGE_ALIGN = "sample_usage_align"; /** * XMLの設定値とフィールド名の組み合わせを返します。 */ @Override public List<KeySetting> getKeySettings() { final List<KeySetting> list = new ArrayList<>(); list.add(KeySetting.of(SAMPLE_USAGE_X, "x")); list.add(KeySetting.of(SAMPLE_USAGE_Y, "y")); list.add(KeySetting.of(SAMPLE_USAGE_ALIGN, "align")); return list; } /** * 用途タイプを返却します。 * 用途のclassを指定してください。 */ @SuppressWarnings("unchecked") @Override public Class<Usage> getSupportType() { return (Class<Usage>) (Object) SampleUsage.class; } /** * インポート時のバリデーションを行います。 */ @Override public void validate(final Class<?> element, final ImportDataModel importData) { // 入力チェックを行う対象 final String key = SAMPLE_USAGE_X; if (!isEditable(element, key)) { // 編集不可の場合は値が設定されていないためバリデーションを行いません。 return; } // インポートデータから値を取得 final String x = getImportDataValue(importData.get(key)); // バリデーション内容 if (x != null && x.isEmpty()) { // エラーメッセージの追加例 setErrorMessage("必須入力項目です。"); } else { try { // 数値化 Integer.valueOf(x); } catch (final NumberFormatException e) { // ワーニングメッセージの追加例 setWarningMessage("数値ではありません。"); } } } }
3.5.1.2.3. 設定ファイルの追加¶
3.5.1.2.3.1. 実装したクラスをシステムに読み込ませるための設定ファイルの追加¶
実装したクラスをシステムに読み込ませるための設定ファイルを追加します。 設定ファイルは java.util.ServiceLoader で読み込まれます。
src/main/resources/META-INF/services/jp.co.intra_mart.foundation.repository.metadata.dictionary.usage.Usage
sample.usage.SampleUsagesrc/main/resources/META-INF/services/jp.co.intra_mart.system.repository.metadata.dictionary.import_export.usage.UsageSerializer
sample.usage.SampleUsageSerializer
3.5.1.2.3.2. インポート・エクスポートで使用する設定ファイルの追加¶
下記の例は sample_usage と sample_usage の各項目の入出力設定です。
<excelConfig kind="dictionary_usage"> は用途の使用・不使用の欄を表します。 今回のサンプルの用途向けに記述を追加します。
<excelConfig kind="sample_usage"> は今回のサンプルの用途の各項目向けの欄を表します。 今回のサンプルの用途の各項目の記述を追加します。
src/main/conf/im-repository-dictionary-config-type/sample-usage.xml
<?xml version="1.0" encoding="UTF-8"?> <im-repository-dictionary-config-type xmlns="http://www.intra-mart.jp/repository/im-repository-dictionary-config-type" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.intra-mart.jp/repository/im-repository-dictionary-config-type im-repository-dictionary-config-type.xsd "> <sheetConfig kind="dictionary"> <excelConfig kind="dictionary_usage"> <columnData> <key>sample_usage</key> <index>20200</index> <validationAddress>inputlist!$B$1:$B$3</validationAddress> <editDisable>Alias</editDisable> <line no="1"> <name></name> <color>TAN</color> <borderTop>THIN</borderTop> <borderBottom>THIN</borderBottom> <borderLeft></borderLeft> <borderRight></borderRight> </line> <line no="2"> <name>サンプル用途</name> <color>TAN</color> <borderTop>THIN</borderTop> <borderBottom>THIN</borderBottom> <borderLeft>THIN</borderLeft> <borderRight>THIN</borderRight> </line> </columnData> </excelConfig> <excelConfig kind="sample_usage"> <columnData> <key>sample_usage_x</key> <index>50000</index> <validationAddress></validationAddress> <editDisable>Alias</editDisable> <line no="1"> <name>表示位置</name> <color>GREY_25_PERCENT</color> <borderTop>THIN</borderTop> <borderBottom>THIN</borderBottom> <borderLeft></borderLeft> <borderRight></borderRight> </line> <line no="2"> <name>x座標</name> <color>GREY_25_PERCENT</color> <borderTop>THIN</borderTop> <borderBottom>THIN</borderBottom> <borderLeft>THIN</borderLeft> <borderRight>THIN</borderRight> </line> </columnData> <columnData> <key>sample_usage_y</key> <index>50100</index> <validationAddress></validationAddress> <editDisable>Alias</editDisable> <line no="1"> <name></name> <color>GREY_25_PERCENT</color> <borderTop>THIN</borderTop> <borderBottom>THIN</borderBottom> <borderLeft></borderLeft> <borderRight></borderRight> </line> <line no="2"> <name>y座標</name> <color>GREY_25_PERCENT</color> <borderTop>THIN</borderTop> <borderBottom>THIN</borderBottom> <borderLeft>THIN</borderLeft> <borderRight>THIN</borderRight> </line> </columnData> <columnData> <key>sample_usage_align</key> <index>50200</index> <validationAddress></validationAddress> <editDisable></editDisable> <line no="1"> <name></name> <color>GREY_25_PERCENT</color> <borderTop>THIN</borderTop> <borderBottom>THIN</borderBottom> <borderLeft></borderLeft> <borderRight>THIN</borderRight> </line> <line no="2"> <name>水平位置</name> <color>GREY_25_PERCENT</color> <borderTop>THIN</borderTop> <borderBottom>THIN</borderBottom> <borderLeft>THIN</borderLeft> <borderRight>THIN</borderRight> </line> </columnData> </excelConfig> </sheetConfig > </im-repository-dictionary-config-type>
3.5.1.2.3.2.1. タグの説明¶
- sheetConfigタグ
Excel ファイルのシートを表現します。 kind属性で出力するシート名を指定します。 指定できる値は category または dictionary のいずれかです。 今回の例ではdictionaryシートに出力したいので、dictionary を指定します。
<sheetConfig kind="dictionary">
- excelConfigタグ
kind 属性を指定することで、列のグループを表現します。 kind属性 dictionary_usage は用途の使用・不使用を表す列のグループです。 今回の例では、追加したsample_usageを出力するための設定を追加します。
<excelConfig kind="dictionary_usage">
kind属性に用途IDを指定すると、その用途IDが示す用途を表す列のグループとして扱われます。 今回の例では、追加した用途を表す列のグループになり、用途の項目を columnData として追加していきます。
<excelConfig kind="sample_usage">
- columnDataタグ
- Excel に対する出力設定を行うタグの親タグです。 属性情報はありません。
- keyタグ
追加した用途のID、用途の項目のキーを設定します。
<!-- サンプル用途の使用・不使用の列を追加します --> <excelConfig kind="dictionary_usage"> <columnData> <!-- 用途IDを指定します --> <key>sample_usage</key> ...
用途の各項目のキーは、インポート・エクスポート時の設定、処理を行うクラスの実装 で定義したインポート・エクスポートで使用するキーを指定します。
<!-- サンプル用途の各項目を列を追加します --> <excelConfig kind="sample_usage"> <columnData> <key>sample_usage_x</key> ... <columnData> <key>sample_usage_y</key> ... <columnData> <key>sample_usage_align</key> ...
- indexタグ
出力位置を示す値です。 excelConfig タグ内での位置を示す値です。
<index>20200</index>
- validationAddressタグ
Excel での入力規則を示す値です。 Excel 上で利用する値を設定してください。
<validationAddress>inputlist!$B$1:$B$3</validationAddress>
入力規則が必要ない場合は値を空にしてください。
<validationAddress></validationAddress>
- editDisableタグ
Excel での編集不可を示す値です。 編集不可の場合はAliasを指定してください。編集可能な場合は、空のタグを指定するか、タグ自体を書かないでください。
<editDisable>Alias</editDisable>
- lineタグ
Excel でのヘッダ行の出力位置を指定します。ヘッダ行は2行で構成されています。 1行目に出力したい場合は 1 を、2行目に出力したい場合は 2 を指定してください。
<line no="1">
- nameタグ
カラムの名称を指定してください。
<name>表示位置</name>
多言語化が必要な場合は、多言語対応 を参考にメッセージプロパティを定義した上でメッセージコードを指定してください。
<name>I18N.MESSAGE.EXAMPLE</name>
- colorタグ
セルの背景色を設定できます。 指定できる値は org.apache.poi_v3_9.ss.usermodel.IndexedColors の値です。
<color>TAN</color>
背景色が必要ない場合は値を空にしてください。
<color></color>
- borderTopタグ、borderBottomタグ、borderLeftタグ、borderRightタグ
セルの罫線を設定できます。 指定できる値は org.apache.poi_v3_9.ss.usermodel.CellStyle の値です。
<borderTop>THIN</borderTop> <borderBottom>THIN</borderBottom> <borderLeft>THIN</borderLeft> <borderRight>THIN</borderRight>
罫線が必要ない場合は値を空にしてください。
<borderTop></borderTop> <borderBottom></borderBottom> <borderLeft></borderLeft> <borderRight></borderRight>
これらの実装を行い、モジュールのビルドや war ファイルのデプロイを行うと、次のような画面が現れ、用途が追加されていることがわかります。