はじめに
本連載では、長崎県電子県庁システムの1つである「ポータル・スケジューラー」をCurlで開発するにあたり苦労した点、工夫した点などを書いていきたいと思います。読者の皆さんがCurlで開発するときの参考になれば幸いです。
長崎県庁との関係性
長崎県は平成14年度に新たなIT調達方式「ながさきITモデル」を導入しました。このモデルは情報システムのコスト削減と地元IT企業の振興を目的としたものであることから、弊社にも参加機会が与えられ、平成15年から電子県庁システムの構築に関わらせていただています。
なお、長崎県では電子県庁システムの一部をオープンソースとして公開しています。以下のWebサイトよりダウンロードすることができます。
長崎県電子県庁システムはCurlで開発されている
およそ6000名の職員を抱える長崎県では、高い操作性とサーバ負荷の軽減を実現することのできるCurl言語を使って電子県庁システムの構築を行っています。現在、約30システムが稼動中です。
ポータル
ポータルには、主に以下の機能があります。
- 長崎県庁内で運用している各システムへのリンク
- RSSリーダー
- 自分のスケジュールの1週間カレンダー
- お知らせ
- TODOリスト
- メモ
- MYリンク集
- アプリケーション・フォルダのランチャー
- ファイル集
スケジューラー
スケジューラーには、主に以下の機能があります。
- 4種類のカレンダ(月間、2週間、1週間、1日)
- グループ表示(1週間、1日のみ)
- 予定のCSVインポート/エクスポート
- 予定のエクセル出力
- メッセンジャー
- 秘書機能
それでは実装の部分について、少しずつ触れていきましょう。
フレームワーク・部品の作成
ポータル・スケジューラーを作成するにあたり、はじめにフレームワーク・部品の作成を行いました。作成した部品をいくつか紹介していきます。Curlは標準コントロールを継承することで簡単に機能拡張を行うことができます。
拡張したのはどれも小さな機能ですが、さまざまなアプリケーション開発で応用できる部品となっていると思います。
数値だけしか入力できないTextField
入力内容によっては、その値を制御したい場合があります。TextFieldクラスのreplace-selection-with-stringメソッドをオーバーライドすることによって、入力できる文字を簡単に制御できます。
{define-class public MyNumericField {inherits TextField} {constructor public {default ...} {construct-super ...} } {method public {replace-selection-with-string p_value: StringInterface}: void ||入力されたものが数値でない場合は空文字にする {if not {"0123456789.-".find-string p_value} then set p_value = ""; } {super.replace-selection-with-string p_value} } }
ボーダー色を変更したTextField
TextFieldのUIクラスをカスタマイズすることによりTextFieldのボーダーの色を変更したり、ボーダー無しにすることが簡単にできます。入力した値がエラーの時、修正を促すといったときの表現方法として応用できると思います。
{define-class public MyTextField {inherits TextField} {constructor public {default ...} {construct-super ui-object = {MyTextFieldUI}, ...} } } {define-class public MyTextFieldUI {inherits StandardTextFieldUI} {constructor public {default control: #TextField = null, ...} {construct-super control = control, ...} ||ボーダー色を赤に設定する set self.border-width = 1pt, self.border-color = "red" } {method protected {overdraw-control renderer2d: Renderer2d}: void} }
イメージを使用したCommandButton
ボタンのUP状態、マウスオーバー状態、マウスダウン状態のイメージをそれぞれ準備すれば、イメージでボタンを作成できます。こういった画像を用意することで、標準のものを使用するより見栄えをよくすることができます。使用できるイメージ形式は、GIF、PNG、BMP、JPG、TIFFです。
{define-class public MyImageButton {inherits CommandButton} {constructor public {default ...} {construct-super style = CommandButtonStyle.label-only, cursor = {Cursor.get-hand-cursor}, ...} set self.reactive-label = {ReactiveLabel label = {image source = {url "normal.gif"}}, label-rollover = {image source = {url "rollover.gif"}}, label-pressed = {image source = {url "pressed.gif"}} } } }
カラー選択DropdownList
DropdownListのアイテムをグラフィックで表現できます。RectangleGraphicクラスで四角形を作成して色を選択するDropdownListを作成しました。このパーツを利用することで、例えば文字色を変更するGUIを提供できるようになります。
{define-class public MyColorDropdownList {inherits DropdownList} {constructor public {default ...} {construct-super ...} {self.append {RectangleGraphic fill-color = "#000000"}} {self.append {RectangleGraphic fill-color = "#800000"}} {self.append {RectangleGraphic fill-color = "#008000"}} {self.append {RectangleGraphic fill-color = "#808000"}} {self.append {RectangleGraphic fill-color = "#000080"}} {self.append {RectangleGraphic fill-color = "#800080"}} {self.append {RectangleGraphic fill-color = "#008080"}} {self.append {RectangleGraphic fill-color = "#808080"}} {self.append {RectangleGraphic fill-color = "#c0c0c0"}} {self.append {RectangleGraphic fill-color = "#ff0000"}} {self.append {RectangleGraphic fill-color = "#00ff00"}} {self.append {RectangleGraphic fill-color = "#ffff00"}} {self.append {RectangleGraphic fill-color = "#0000ff"}} {self.append {RectangleGraphic fill-color = "#ff00ff"}} {self.append {RectangleGraphic fill-color = "#00ffff"}} {self.append {RectangleGraphic fill-color = "#ffffff"}} } }
DropdownListのアイテムはVisualクラス(画面に表示できるコントロールの基底クラス)です。そのため、画面に表示できるものすべて、例えばドロップダウンリストにCommandButtonなどを含めることができ、とても汎用的に使用することができます。
{define-class public MyDropdownList {inherits DropdownList} {constructor public {default ...} {construct-super ...} {self.append {HBox spacing=5pt, {CheckButton label="チェックボタン1"}, {CommandButton label="コマンドボタン1"} } } {self.append {HBox spacing=5pt, {CheckButton label="チェックボタン2"}, {CommandButton label="コマンドボタン2"} } } {self.append {HBox spacing=5pt, {CheckButton label="チェックボタン3"}, {CommandButton label="コマンドボタン3"} } } {self.append {HBox spacing=5pt, {CheckButton label="チェックボタン4"}, {CommandButton label="コマンドボタン4"} } } {self.append {HBox spacing=5pt, {CheckButton label="チェックボタン5"}, {CommandButton label="コマンドボタン5"} } } } }
表示できない部分を省略して表示するラベル
今回のアプリケーションでは、文字列が指定した幅で表示できない場合に表示できない部分を省略するラベルを作成しました。ラベルの幅によって動的に表示が変わります。文字を描画したときの幅を取得できるAPIを使って実現しています。
{define-class public MyContentsLabel {inherits Fill} field private _label: String {constructor public {default label: String = "", ...} {construct-super ...} set self._label = label } {method public {draw r2d: Renderer2d}: void let v_bounds: GRect = {self.layout.get-bounds} {with-render-properties font = {Font self.font-family, self.font-size, weight = self.font-weight, style = self.font-style }, translation = {Distance2d -v_bounds.lextent, -v_bounds.ascent }, translation-y = {r2d.get-font-ascent} on r2d do {r2d.render-string 0pt, 0pt, {self.my-minimize-string self._label, v_bounds.width } } } } ||文字列が指定した幅に表示できない場合は後ろに省略記号をつける {method private {my-minimize-string p_value: String, p_maxwidth: Distance }: String let v_ret: String = p_value, v_size: int = p_value.size, v_buf: StringBuf = {StringBuf} {if v_size <= 1 or {self.my-get-text-width p_value} <= p_maxwidth then {return v_ret} } {for c: char in p_value do {if {self.my-get-text-width {v_buf.to-String} & c & "…" } < p_maxwidth then {v_buf.append c} else set v_ret = {if v_buf.empty? then {p_value.substr 0, 1} & "…" else {v_buf.to-String} & "…" } {break} } } {return v_ret} } ||文字列を描画した時の幅を取得する {method private {my-get-text-width p_value: String}: FloatDistance {return {{self.get-display-context}.get-string-advance-width {Font self.font-family, self.font-size, weight = self.font-weight, style = self.font-style }, p_value } } } }
サーバーサイドとの連携
この「ポータル・スケジューラー」システムでは、サーバーサイドをPHPで実装しています。CurlからPHPに値を渡すときはPOSTで行い、PHPからの結果はXMLで受け取ります。CurlはSAX2パーサーをサポートしています。
{define-class public final MyHttp field private _box: VBox field private _frm: HttpForm {constructor public {default} set self._box = {VBox}, self._frm = {HttpForm {url ""}, method = HttpRequestMethod.post, default-character-encoding = {get-character-encoding-by-name "euc-jp"}, self._box } } {method public {execute p_url: Url, ...}: (bool, String) let v_sts?: bool = true, v_value: StringBuf = {StringBuf} {self.createform-proc p_url, ...} set self._frm.encoding = HttpFormData.urlencoded-mime-type {try {with-open-streams in: HttpTextInputStream = {self._frm.submit-open} do let v_flg?: bool = false {while not in.end-of-stream? do {if v_flg? then {v_value.concat "\n"} } {v_value.concat {{in.read-line}.to-String} } set v_flg? = true } } catch e: Exception do set v_sts? = false } {return v_sts?, {v_value.to-String} } } ||HttpFormにサーバー側に渡すためのTextFieldを作成して渡したい値をセット {method private {createform-proc p_url: Url, ...}: void let v_cnt: int = 0, v_name: String = "", v_value: String = "" {self._frm.reset} {self._box.clear} set self._frm.form-action = p_url {for value in ... do {if v_cnt rem 2 == 0 then set v_name = value else set v_value = value {self._box.add {TextField name = v_name, value = v_value } } } {inc v_cnt} } } }
ローカルにあるファイルの内容もサーバに送ることができます。PHP側は、HTMLの<input>タグで送信されたファイルを受け取るときと同じ要領で、ファイルの内容を受け取っています。
{define-class public final MyHttp field private _box: VBox field private _frm: HttpForm {constructor public {default} set self._box = {VBox}, self._frm = {HttpForm {url ""}, method = HttpRequestMethod.post, default-character-encoding = {get-character-encoding-by-name "euc-jp"}, self._box } } {method public {execute-with-file p_url: Url, p_files: #UrlArray, ...}: (bool, String) let v_sts?: bool = true, v_value: StringBuf = {StringBuf}, v_fileno: int = 0 {self.createform-proc p_url, ...} set self._frm.encoding = HttpFormData.multipart-mime-type {if-non-null p_files then {for value: Url in p_files do {self._frm.add-file {String v_fileno}, value} {inc v_fileno} } } {try {with-open-streams in: HttpTextInputStream = {self._frm.submit-open} do let v_flg?: bool = false {while not in.end-of-stream? do {if v_flg? then {v_value.concat "\n"} } {v_value.concat {{in.read-line}.to-String} } set v_flg? = true } } catch e: Exception do set v_sts? = false } {return v_sts?, {v_value.to-String} } } ||HttpFormにサーバー側に渡すためのTextFieldを作成して渡したい値をセット {method private {createform-proc p_url: Url, ...}: void let v_cnt: int = 0, v_name: String = "", v_value: String = "" {self._frm.reset} {self._box.clear} set self._frm.form-action = p_url {for value in ... do {if v_cnt rem 2 == 0 then set v_name = value else set v_value = value {self._box.add {TextField name = v_name, value = v_value } } } {inc v_cnt} } } }
まとめ
Curlは、Java、C++の良いところを組み合わせた言語といわれるだけあって、とてもよくできた言語だと思います。簡単に標準のコントロールを拡張することができ、自由度も非常に高いです。
ヘルプも充実しており、実際にコードを入力してその場で実行結果をみることができる点も魅力的です。もし興味があれば、一度、Curlを触ってみてはいかがでしょうか。