OracleのSteve Muenchさんが彼のブログに、APEX 22.2から使用できる新しい日付ピッカーをカスタマイズして、範囲を指定をする方法を紹介されています。
Designer Collab for Date Ranges
https://diveintoapex.com/2023/02/07/designer-collab-for-date-ranges/
記事からダウンロードできるサンプル・アプリケーションに含まれる静的アプリケーション・ファイル、dateRangePicker.js、dateRangePicker.cssを取り出し、自分のアプリケーションに取り込むことで簡単に再利用できるようになっています。
色々な技が使われていて再利用が容易になっているのですが、実装を理解するにはスキルが必要です。もう少し実装を分かりやすくするため、動的アクションによる実装に変更してみました。dateRangePicker.cssはそのまま流用し、日付ピッカーのdayFormatterについてもほぼ同じ実装を流用します。
実装した日付範囲ピッカーは以下のように動作します。日付範囲ピッカーは表示形式をInlineにした日付ピッカーで、開始日と終了日はそれぞれ異なるページ・アイテムに設定しています。
以下より実装について説明します。
ページ・アイテムとして開始日を保存するP1_DATE_START、終了日を保存するP1_DATE_ENDを作成します。上のGIF動画では、これらのページ・アイテムの値が画面に表示されていますが、一般的には非表示にすると思います。
日付範囲ピッカーですが、ページ・アイテムP1_DATE_RANGEとして作成しています。
タイプは日付ピッカーです。設定の表示形式としてInlineを選択します。外観と動作のShow Clear Buttonにチェックを入れます。表示形式がInlineの場合、ページ・アイテムの値をクリアするためにこのボタンが必要です。
詳細のCSSクラスにdate-range-pickerを設定します。これはdateRangePicker.cssで定義されているCSSクラスを適用するために必要です。
タイミングのイベントは変更、選択タイプはアイテム、アイテムとしてP1_DATE_RANGEを指定します。日付範囲ピッカー上で日付を選択(変更)したときに、動的アクションが実行されます。
TRUEアクションはJavaScriptコードの実行、設定のコードとして以下を記述します。クリックしたときの状態に応じて、選択された日付を開始日または終了日として設定しています。最後に日付範囲ピッカーをリフレッシュし、設定された開始日と終了日の間に色を付けます。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 影響を受ける要素として、開始日と終了日のページ・アイテムを設定する。 | |
* 開始日のページ・アイテムを先に設定する。 | |
*/ | |
let dateItemStart = this.affectedElements[0]; | |
let dateItemEnd = this.affectedElements[1]; | |
let dateItemClicked = this.triggeringElement; | |
/* | |
* 値が設定されていないときは、クリアがクリックされている。 | |
* 開始日と終了日をクリアして、リフレッシュする。 | |
*/ | |
if (!dateItemClicked.value) { | |
dateItemStart.value = ""; | |
dateItemEnd.value = ""; | |
dateItemClicked.refresh(); | |
return; | |
} | |
if (!dateItemStart.value) { | |
// 開始日が未設定なので、選択された値は開始日として扱う。 | |
dateItemStart.value = dateItemClicked.value; | |
if (!dateItemEnd.value) { | |
/* | |
* 終了日も未設定なので、同じ日付を終了日に設定する。 | |
* 開始日と終了日が同じ日であることを禁止する場合は、この処理を変更する。 | |
*/ | |
dateItemEnd.value = dateItemClicked.value; | |
} | |
// 開始日が未設定で終了日が設定されていることは無いはず。なので無視する。 | |
} | |
else | |
{ | |
// 開始日はすでに設定されている。 | |
const dateClicked = apex.date.parse(dateItemClicked.value, DATE_FORMAT); | |
const dateStart = apex.date.parse(dateItemStart.value, DATE_FORMAT); | |
if (!dateItemEnd.value) { | |
// 終了日が設定されていない。 | |
if (apex.date.isBefore(dateClicked, dateStart)) { | |
// 選択した日付が開始日より前であれば、開始日と終了日を入れ替えて期間を設定する。 | |
dateItemEnd.value = dateItemStart.value; | |
dateItemStart.value = dateItemClicked.value; | |
} | |
else | |
{ | |
/* | |
* 終了日を設定する。 | |
*/ | |
dateItemEnd.value = dateItemClicked.value; | |
} | |
} | |
else | |
{ | |
// 開始日と終了日の両方が設定済み。 | |
const dateEnd = apex.date.parse(dateItemEnd.value, DATE_FORMAT); | |
if (apex.date.isBefore(dateClicked, dateStart)) { | |
// 開始日より前の日付を選択したときは開始日を変更する。 | |
dateItemStart.value = dateItemClicked.value; | |
} else if (apex.date.isAfter(dateClicked, dateEnd)) { | |
// 終了日より後の日付を選択したときは終了日を変更する。 | |
dateItemEnd.value = dateItemClicked.value; | |
} else if (apex.date.isBetween(dateClicked, dateStart, dateEnd)) { | |
// 開始日と終了日の間の日付を選択したときは、終了日を変更する。 | |
dateItemEnd.value = dateItemClicked.value; | |
// 開始日を後ろに移動するには、一旦クリアする。 | |
} | |
} | |
/* | |
* このページ・アイテムにはつねに開始日を設定したのちにリフレッシュを行う。 | |
* 日付の範囲を正しく表示するために必要。 | |
*/ | |
dateItemClicked.value = dateItemStart.value; | |
dateItemClicked.refresh(); | |
} |
影響を受ける要素の選択タイプにアイテムを選び、アイテムとしてP1_DATE_START、P1_DATE_ENDの2つのページ・アイテムを設定します。開始日を保持するページ・アイテムを先頭に配置します。
開始日から終了日の間に色をつける処理は、日付範囲ピッカーに実装するdayFormatterが行います。
ページ・プロパティのJavaScriptのファンクションおよびグローバル変数の宣言に以下を記述します。
const DATE_FORMAT = "yyyy/mm/dd";
ページ・ロード時に実行でdayFormatterを定義します。開始日にCSSクラスdateRangeStart、終了日にdateRangeEnd、開始日と終了日の間の日付にはdateRangeMiddleを設定しています。開始日と終了日が同じ場合はCSSクラスとしてdateRangeSingleDayを設定しています。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
apex.item("P1_DATE_RANGE").dayFormatter = (iso8860DateString) => { | |
// Parse the day being formatted using ISO8860 | |
const currentDate = apex.date.parse(iso8860DateString,"YYYY-MM-DD"); | |
const startDateValue = apex.items.P1_DATE_START.value; | |
// Parse start date using start page item format | |
const startDate = startDateValue ? | |
apex.date.parse(startDateValue,DATE_FORMAT) : null; | |
const endDateValue = apex.items.P1_DATE_END.value; | |
// Parse end date using end page item format | |
const endDate = endDateValue ? | |
apex.date.parse(endDateValue,DATE_FORMAT) : null; | |
let dateRangeClass = ""; | |
// 開始日の設定は必須。 | |
if (startDateValue) { | |
// 開始日が処理対象である。 | |
if (apex.date.isSame(currentDate,startDate)) { | |
// 開始日と終了日が同じであれば、スタイルdateRangeSingleDayを適用する。 | |
if (endDateValue && apex.date.isSame(currentDate,endDate)) { | |
dateRangeClass = "dateRangeSingleDay"; | |
} | |
else { | |
dateRangeClass = "dateRangeStart"; | |
} | |
} | |
// 処理対象が開始日以外。 | |
else if (endDateValue) { | |
// 処理対象が終了日であれば、スタイルdateRangeEndを適用する。 | |
if (apex.date.isSame(currentDate,endDate)) { | |
dateRangeClass = "dateRangeEnd" | |
} else if (apex.date.isBetween(currentDate,startDate,endDate)) { | |
// 処理対象が開始日と終了日の間であればスタイルdateRangeMiddleを適用する。 | |
dateRangeClass = "dateRangeMiddle"; | |
} | |
} | |
// それ以外はスタイルを適用しない。 | |
}; | |
return { | |
disabled: false, | |
class: dateRangeClass | |
}; | |
}; |
CSSのファイルURLに以下を記述します。CSSファイルはこちらから、または英語の元記事よりサンプル・アプリケーションをダウンロードし、dateRangePicker.cssを取り出して静的アプリケーション・ファイルとして保存しておきます。
#APP_FILES#dateRangePicker#MIN#.css
以上で実装は完了です。
今回作成したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/date-range-picker.zip
Oracle APEXのアプリケーション作成の参考になれば幸いです。
完