CodeZine(コードジン)

特集ページ一覧

Curlで構築する長崎県電子県庁システム(ポータル・スケジューラー)
部品の拡張とPHP連携

第1回

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加

 およそ6000名の職員を抱える長崎県では、高い操作性とサーバ負荷の軽減を実現することのできるCurl言語を使って電子県庁システムの構築を行っています。現在、約30システムが稼動中です。本連載では、長崎県電子県庁システムの1つである「ポータル・スケジューラー」をCurlで開発するにあたり苦労した点、工夫した点などを解説していきたいと思います。

はじめに

 本連載では、長崎県電子県庁システムの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メソッドをオーバーライドすることによって、入力できる文字を簡単に制御できます。

数値だけしか入力できないTextField
{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のボーダーの色を変更したり、ボーダー無しにすることが簡単にできます。入力した値がエラーの時、修正を促すといったときの表現方法として応用できると思います。

標準のTextField
標準のTextField
ボーダーを赤色にしたTextField
ボーダーを赤色にしたTextField
ボーダー色を赤にした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です。

標準のボタン
標準のボタン
イメージを使用したボタン
イメージを使用したボタン
イメージを使用したCommandButton
{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を提供できるようになります。

カラー選択DropdownList
{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などを含めることができ、とても汎用的に使用することができます。

DropdownListのアイテムにコマンドボタン、チェックボックスを設定
{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を触ってみてはいかがでしょうか。

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

  • 株式会社ドゥアイネット 前田慎治(マエダシンジ)

    株式会社ドゥアイネットに勤務するプログラマーです。制御系から事務系まで 様々な開発を経験し、現在はCurlやOpenLaszloを使ってRIAの開発を担当してい ます。 OpenLaszloで開発した「スマートスケジューラー」 http://www.dinss.jp/

All contents copyright © 2005-2021 Shoeisha Co., Ltd. All rights reserved. ver.1.5