2022年12月31日土曜日

Cozy Web/HelloResource

Cozy Webの目的の一つはモデル駆動開発とWeb開発を接続することにあります。

モデリングによって定義したモデルが、そのままWebアプリケーションのアプリケーション・ロジックとして動作し、Web画面をデザインするだけでWebアプリケーションが作成できるのが理想形です。

現時点ではその理想形にはまだまだ距離がありますが、部分的には実現できているところもあります。今回はエンティティをWebのリソースとしてアクセスする処理をモデルベースで実現する方法について説明します。

HelloResource

モデル駆動開発を指向した、モデルを使用したWebアプリケーションとしてHelloResourceを作成します。

準備

cozyを起動するディレクトリのwebappsディレクトリに、アプリケーションのホームディレクトリとなるHelloResourceを作成します。

ビュー

HelloResourceでは、Web画面を記述するビュー(*.html, *.jadeなど)は定義していません。リソースの表示はデフォルトのビュー機能によって自動的にレイアウトされます。

モデル

WEB-INF/modelsでアプリケーションで使用するモデルを定義します。以下のモデル定義をmodel.orgとしてWEB-INF/modelsに格納します。

* entity  
** product
*** features  
table=product
*** attributes  
| Name  | Type   | Multiplicity |
|-------+--------+--------------|
| id    | token  |            1 |
| name  | string |            1 |
| price | int    |            1 |

エンティティproductを以下の通りに定義しています。

  • feature項で格納するテーブル名をproductと定義
  • 属性としてid, name, priceの3つを定義

モデルはCozy Webのスクリプト言語であるCozy Scriptの基盤となっているKaleidoxの提供するモデル駆動機能をベースとしています。

Kaleidoxの提供するモデル駆動機能は以下で説明しています。

モデルの定義、データベースとの接続、状態機械の定義、状態機械によるイベント駆動といった機能を提供しています。

これらの機能をCozyではWebアプリケーションから使用することができるようになります。

実行

それではHelloResourceを実行してみましょう。

一覧

まず一覧取得です。

モデルとして定義したリソースのproductの一覧取得は以下になります。

WebアプリケーションのURL http://localhost:8080/web/HelloResource の直下にリソース名productを指定しています。

$ curl http://localhost:8080/web/HelloResource/product

この結果、以下のHTML文書が返されます。(読みやすいようにxmllintで整形しています。)

<?xml version="1.0"?>
<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <meta name="keywords" content="TBD"/>
    <meta name="description" content="TBD"/>
    <meta name="robots" content="noindex,nofollow"/>
    <meta name="author" content="TBD"/>
    <title>product</title>
  </head>
  <body>
    <table class="">
      <caption class="">product</caption>
      <thead class="">
        <tr class="">
          <th scope="col" class="">Id</th>
          <th scope="col" class="">Name</th>
          <th scope="col" class="">Price</th>
        </tr>
      </thead>
      <tbody class="">
        <tr data-href="product/1.html">
          <td class="">1</td>
          <td class="">Apple</td>
          <td class="">300</td>
        </tr>
        <tr data-href="product/2.html">
          <td class="">2</td>
          <td class="">Orange</td>
          <td class="">350</td>
        </tr>
        <tr data-href="product/3.html">
          <td class="">3</td>
          <td class="">Peach</td>
          <td class="">400</td>
        </tr>
      </tbody>
    </table>
  </body>
</html>

tableタグで3つのエンティティに対応する情報が表示されています。

詳細

$ curl http://localhost:8080/web/HelloResource/product/2

この結果、以下のHTML文書が返されます。(読みやすいようにxmllintで整形しています。)

<?xml version="1.0"?>
<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <meta name="keywords" content="TBD"/>
    <meta name="description" content="TBD"/>
    <meta name="robots" content="noindex,nofollow"/>
    <meta name="author" content="TBD"/>
  </head>
  <body>
    <table class="">
      <tbody>
        <tr class="">
          <th scope="row" class="">Id</th>
          <td class="">2</td>
        </tr>
        <tr class="">
          <th scope="row" class="">Name</th>
          <td class="">Orange</td>
        </tr>
        <tr class="">
          <th scope="row" class="">Price</th>
          <td class="">350</td>
        </tr>
      </tbody>
    </table>
  </body>
</html>

一覧表示のidが2の部分は以下になります。

        <tr data-href="product/2.html">
          <td class="">2</td>
          <td class="">Orange</td>
          <td class="">350</td>
        </tr>

このエンティティが詳細情報として表示されているのが分かります。

まとめ

HelloResourceでは、必要最小限の構成要素としてモデル定義のみを作成しました。HTMLなどのビューは作成していません。

モデル定義を記述したmodel.orgを作成し、WEB-INF/modelsに配置するのみで、エンティティの内容が一覧画面、詳細画面として表示できることが分かりました。ビューの定義がない場合は、Cozy Webのデフォルト画面を使ってエンティティ情報が表示されました。

次回はビューを定義して、デザイン化された画面でエンティティ情報の表示を行ってみる予定です。

諸元

Cozy
0.0.10

2022年11月30日水曜日

Cozy Web/HelloScript

前回はCozy Scriptの組込み関数を使用して、システムが提供するオペレーションをフォーム画面から呼出して使用する方法について説明しました。

今回はフォーム画面から入力したパラメタを引数にしてCozy Scriptを実行する方法について説明します。

HelloScript

フォームから使用できるアプリケーション・ロジックをCozy Scriptのスクリプトとして記述することができます。

Cozy scriptを用いたWebアプリケーションHelloScriptを作成します。

準備

cozyを起動するディレクトリのwebappsディレクトリに、アプリケーションのホームディレクトリとなるHelloScriptを作成します。

Formページ

フォームを使った入力画面として以下のページをindex.jadeとしてホームディレクトリに配置します。

-@val form: ViewForm
html
  head
    title HelloScript
  body
    form(method="POST" action={form.action})
      input(hidden="true" name="$scenario" value={form.scenario})
      input(name="arg1" value={form.arg1})
      input(name="arg2" value={form.arg2})
      input(name="arg3" value={form.arg3})
      button(type="submit" id="submitbutton" name="$submit" value="ok") Submit
      button(type="submit" id="cancelbutton" name="$submit" value="cancel") Cancel

ViewFormオブジェクト

以下の宣言でフォームの設定に必要なViewFormを参照可能にします。

-@val form: ViewForm

Form/action

actionにViewFormオブジェクトのaction属性を設定します。

    form(method="POST" action={form.action})

今回の場合は空文字が設定されます。入力と同じページにフォームのPOSTが行われるということですね。

POST処理に必要な情報は後述する「$scenario」に設定されています。

Input/Hidden

Hidden属性のInputで入力する「$scenario」プロパティにViewFormオブジェクトのscenario属性を設定します。

      input(hidden="true" name="$scenario" value={form.scenario})

今回の場合は「{&quot;name&quot;:&quot;invoke-operation&quot;,&quot;state&quot;:&quot;input&quot;,&quot;data&quot;:{}}」が設定されています。URLエンコーディングを解除した値は「{"name":"invoke-operation","state":"input","data":{}}」です。

Cozy Webがフォーム処理を行うために必要な情報が設定されています。

Inputデータ

Hidden属性のないInputでデータ入力を行います。

今回は以下の3行が対象です。

      input(name="arg1" value={form.arg1})
      input(name="arg2" value={form.arg2})
      input(name="arg3" value={form.arg3})

nameにarg1,arg2,arg3を指定しているので、arg1プロパティとarg2プロパティ, arg3プロパティの3つのプロパティの入力ということになります。値のデフォルト値としてViewFormオブジェクトのarg1属性とarg2属性の値を設定しています。今回のケースでは空文字が設定されます。

Input/Ok

OK用のサブミットボタンとして以下のButtonを設定しました。

      button(type="submit" id="submitbutton" name="$submit" value="ok") Submit

name属性に「$submit」、value属性に「ok」を指定しています。

Input/Cancel

キャンセル用のサブミットボタンとして以下のButtonを設定しました。

      button(type="submit" id="cancelbutton" name="$submit" value="cancel") Cancel

name属性に「$submit」、value属性に「cancel」を指定しています。

コントローラ

WEB-INF/controllersにindex.jade用のコントローラである以下のindex.jsonを配置します。

{
  "action": "script-scenario",
  "script0": "(+ 1 2 3)",
  "script": "(+ arg1 (* arg2 arg3))",
  "method": "POST",
  "successView": "index_complete",
  "errorView": "index_error",
  "parameters": [{
    "name": "arg1",
    "datatype": "int"
  },{
    "name": "arg2",
    "datatype": "int"
  },{
    "name": "arg3",
    "datatype": "int"
  }]
}

action

コントローラのアクションとしてscript-scenarioを指定しています。

  "action": "script-scenario",

script-scenarioは、フォームで入力したパラメタ入力を使ってスクリプトを呼び出し、その結果をビューに渡すモデルとして生成する処理を行うシナリオです。

フォーム入力にまつわるWebブラウザとサーバ間のインタラクションをシナリオに従って実現します。

script

実行するスクリプトをCozy Scriptで記述します。

  "script": "(+ arg1 (* arg2 arg3))",

method

メソッドはPOSTを指定しています。

  "method": "POST",

FormのメソッドがPOSTのものを受け付けます。

successView

successViewにはindex_completeを指定しています。

  "successView": "index_complete",

コントローラの処理が成功するとこのページに遷移します。

errorView

errorViewにはindex_errorを指定しています。

  "errorView": "index_error",

コントローラの処理が失敗するとこのページに遷移します。

parameters

フォームから入力されるパラメタとして、パラメタ名とデータ型を指定しています。

  "parameters": [{
    "name": "arg1",
    "datatype": "int"
  },{
    "name": "arg2",
    "datatype": "int"
  },{
    "name": "arg3",
    "datatype": "int"
  }]

パラメタは、パラメタ名arg1でデータ型int, パラメタ名arg2でデータ型int, パラメタ名arg3でデータ型intの3つです。

成功ページ

成功ページとして以下のindex_complete.htmlを用意します。コントローラのsuccessViewで指定したページです。

<html>
    <head>
	<title>Script Success</title>
    </head>
    <body>
	<h1>Script Success</h1>
	<c:model/>
    </body>
</html>

このページ内の以下のタグはコントローラの実行結果のモデルの内容を表形式で表示するものです。

	<c:model/>

今回の場合は、スクリプトの実行結果が出力されます。

エラーページ

成功ページとして以下のindex_error.htmlを用意します。コントローラのerrorViewで指定したページです。

<html>
    <head>
	<title>Script Error</title>
    </head>
    <body>
	<h1>Script Error</h1>
	<c:error/>
    </body>
</html>

このページ内の以下のタグはコントローラの実行時にエラーが発生した場合、そのエラーを表形式で表示するものです。

	<c:error/>

今回の場合は、スクリプトの実行結果がエラーとなる場合に出力されます。

実行

Formページ

curlコマンドによってローカルホストの8080ポート上の/web/HelloScript を取得します。

$ curl http://localhost:8080/web/HelloScript/

以下のHTML文書が返されます。

<!DOCTYPE html>
<html>
  <head>
    <title>HelloScript</title>
  </head>
  <body>
    <form action="" method="POST">
      <input value="{&quot;name&quot;:&quot;execute-script&quot;,&quot;state&quot;:&quot;input&quot;,&quot;data&quot;:{}}" name="$scenario" hidden="true" />
      <input value="" name="arg1" />
      <input value="" name="arg2" />
      <input value="" name="arg3" />
      <button value="ok" name="$submit" id="submitbutton" type="submit">Submit</button>
      <button value="cancel" name="$submit" id="cancelbutton" type="submit">Cancel</button>
    </form>
  </body>
</html>

OK

OKボタンの押下は以下のcurlに相当します。

$ curl http://localhost:8080/web/HelloScript/ -X POST \
--data-urlencode '$submit=ok' \
--data-urlencode 'arg1=3' \
--data-urlencode 'arg2=8' \
--data-urlencode 'arg3=5' \
--data-urlencode '$scenario={"name":"invoke-operation","state":"input","data":{}}'

OKボタン押下と同等の上記curlの結果、以下のHTMLが出力されました。

<!DOCTYPE html>
<html><head>
	<title>Script Success</title>
    </head><body>
	<h1>Script Success</h1>
	43
</body></html>

c:modelタグの場所に、スクリプト「(+ 3 (* 8 5))」の計算結果である43が出力されています。

まとめ

今回はフォーム画面から入力したパラメタを引数にしてCozy Scriptを実行する方法について説明しました。

次回はCozy Scriptに組み込まれたモデル駆動の機能を使用してリソースに対するアクセスを実現する方法について説明する予定です。

諸元

Cozy
0.0.9

2022年10月31日月曜日

Cozy Web/HelloOperation

フォーム処理ではサーバーサイドでアプリケーション・ロジックを実行する必要があります。

Cozyではアプリケーション・ロジックを記述する方式としてKaleidoxの関数を使用することが可能です。

今回はKaleidoxの組込み関数「+」をCozy Webから使用する方法について見ていきます。

HelloOperation

フォームから使用できるアプリケーション・ロジックはオペレーションとして提供されています。

Kaleidoxの組込み関数はオペレーションとして登録されているので、そのまま使用することができます。

フォームを用いたWebアプリケーションHelloOperationを作成します。

HelloOperationでは、Kaleidoxの「+」関数をCozyのオペレーションとして実行することでフォームで入力した数値の加算を行います。

準備

cozyを起動するディレクトリのwebappsディレクトリに、アプリケーションのホームディレクトリとなるHelloOperationを作成します。

Formページ

フォームを使った入力画面として以下のページをindex.jadeとしてホームディレクトリに配置します。

-@val form: ViewForm
html
  head
    title HelloForm
  body
    form(method="POST" action={form.action})
      input(hidden="true" name="$scenario" value={form.scenario})
      input(name="arg1" value={form.arg1})
      input(name="arg2" value={form.arg2})
      button(type="submit" id="submitbutton" name="$submit" value="ok") Submit
      button(type="submit" id="cancelbutton" name="$submit" value="cancel") Cancel

ViewFormオブジェクト

以下の宣言でフォームの設定に必要なViewFormを参照可能にします。

-@val form: ViewForm

Form/action

actionにViewFormオブジェクトのaction属性を設定します。

    form(method="POST" action={form.action})

今回の場合は空文字が設定されます。入力と同じページにフォームのPOSTが行われるということですね。

POST処理に必要な情報は後述する「$scenario」に設定されています。

Input/Hidden

Hidden属性のInputで入力する「$scenario」プロパティにViewFormオブジェクトのscenario属性を設定します。

      input(hidden="true" name="$scenario" value={form.scenario})

今回の場合は「{&quot;name&quot;:&quot;invoke-operation&quot;,&quot;state&quot;:&quot;input&quot;,&quot;data&quot;:{}}」が設定されています。URLエンコーディングを解除した値は「{"name":"invoke-operation","state":"input","data":{}}」です。

Cozy Webがフォーム処理を行うために必要な情報が設定されています。

Inputデータ

Hidden属性のないInputでデータ入力を行います。

今回は以下の2行が対象です。

      input(name="arg1" value={form.arg1})
      input(name="arg2" value={form.arg2})

nameにarg1とarg2を指定しているので、arg1プロパティとarg2プロパティの2つのプロパティの入力ということになります。値のデフォルト値としてViewFormオブジェクトのarg1属性とarg2属性の値を設定しています。今回のケースでは空文字が設定されます。

Input/Ok

OK用のサブミットボタンとして以下のButtonを設定しました。

      button(type="submit" id="submitbutton" name="$submit" value="ok") Submit

name属性に「$submit」、value属性に「ok」を指定しています。

Input/Cancel

キャンセル用のサブミットボタンとして以下のButtonを設定しました。

      button(type="submit" id="cancelbutton" name="$submit" value="cancel") Cancel

name属性に「$submit」、value属性に「cancel」を指定しています。

コントローラ

WEB-INF/controllersにindex.jade用のコントローラである以下のindex.jsonを配置します。

{
  "action": "invoke-operation-scenario",
  "operation": "+",
  "method": "POST",
  "successView": "index_complete",
  "errorView": "index_error",
  "parameters": [{
    "arg1": "int",
    "arg2": "int"
  }]
}

action

コントローラのアクションとしてinvoke-operation-scenarioを指定しています。

  "action": "invoke-operation-scenario",

invoke-operation-scenarioは、フォームでパラメタ入力し、このパラメタを使ってオペレーションを呼び出し、その結果をビューに渡すモデルとして生成する処理を行うシナリオです。

フォーム入力にまつわるWebブラウザとサーバ間のインタラクションをシナリオに従って実現します。

operation

実行するオペレーションとして+を指定しています。

  "operation": "+",

+オペレーションはKaleidoxが定義している関数で、数値の加算をおこないます。

CozyではKaleidoxの関数をinvoke-operation-scenarioで呼び出すことができるオペレーションとして使用できるようになっています。

method

メソッドはPOSTを指定しています。

  "method": "POST",

FormのメソッドがPOSTのものを受け付けます。

successView

successViewにはindex_completeを指定しています。

  "successView": "index_complete",

コントローラの処理が成功するとこのページに遷移します。

errorView

errorViewにはindex_errorを指定しています。

  "errorView": "index_error",

コントローラの処理が失敗するとこのページに遷移します。

parameters

フォームから入力されるパラメタとして、パラメタ名とデータ型を指定しています。

  "parameters": [{
    "arg1": "int",
    "arg2": "int"
  }]

パラメタは、パラメタ名arg1でデータ型intとパラメタ名arg2でデータ型intの2つです。

成功ページ

成功ページとして以下のindex_complete.htmlを用意します。コントローラのsuccessViewで指定したページです。

<html>
    <head>
	<title>Operation Success</title>
    </head>
    <body>
	<h1>Operation Success</h1>
	<c:model/>
    </body>
</html>

このページ内の以下のタグはコントローラの実行結果のモデルの内容を表形式で表示するものです。

	<c:model/>

今回の場合は、+オペレーションの結果が出力されます。

エラーページ

成功ページとして以下のindex_error.htmlを用意します。コントローラのerrorViewで指定したページです。

<html>
    <head>
	<title>Operation Error</title>
    </head>
    <body>
	<h1>Operation Error</h1>
	<c:error/>
    </body>
</html>

このページ内の以下のタグはコントローラの実行時にエラーが発生した場合、そのエラーを表形式で表示するものです。

	<c:error/>

今回の場合は、loopbackオペレーションがエラーとなる場合に出力されます。

実行

Formページ

curlコマンドによってローカルホストの8080ポート上の/web/HelloOperation を取得します。

$ curl http://localhost:8080/web/HelloOperation/

以下のHTML文書が返されます。

<!DOCTYPE html>
<html>
  <head>
    <title>HelloForm</title>
  </head>
  <body>
    <form action="" method="POST">
      <input value="{&quot;name&quot;:&quot;invoke-operation&quot;,&quot;state&quot;:&quot;input&quot;,&quot;data&quot;:{}}" name="$scenario" hidden="true" />
      <input value="" name="arg1" />
      <input value="" name="arg2" />
      <button value="ok" name="$submit" id="submitbutton" type="submit">Submit</button>
      <button value="cancel" name="$submit" id="cancelbutton" type="submit">Cancel</button>
    </form>
  </body>
</html>

OK

OKボタンの押下は以下のcurlに相当します。

$ curl http://localhost:8080/web/HelloOperation/ -X POST \
--data-urlencode '$submit=ok' \
--data-urlencode 'arg1=3' \
--data-urlencode 'arg2=8' \
--data-urlencode '$scenario={"name":"invoke-operation","state":"input","data":{}}'

OKボタン押下と同等の上記curlの結果、以下のHTMLが出力されました。

<!DOCTYPE html>
<html><head>
	<title>Operation Success</title>
    </head><body>
	<h1>Operation Success</h1>
	11
    
</body></html>

c:modelタグの場所に、「+ 3 8」の計算結果である11が出力されています。

まとめ

今回はCozy WebでHTMLフォームの処理にKaleidoxの組込み関数を使用する方法について説明しました。

Cozy WebではHTMLフォームを使った処理はコントローラに宣言的な定義をするだけで、Webとサーバー間のインタラクションを伴う状態遷移の管理を行ってくれます。さらにオペレーションとして登録された各種処理も実行してくれるので、オペレーションを実装すれば後はWeb画面のデザインをするだけでHTMLフォーム処理を実現することができるわけです。

今回はKaleidoxの組込み関数を使用しましたが、次回はKaleidoxのスクリプト機能を使用してHTMLフォームによる処理を実現する方法を説明します。

諸元

Cozy
0.0.8

2022年9月30日金曜日

Cozy Web/HelloForm

Webアプリケーション開発では、フォーム処理の開発が一つのポイントになります。やりたいことそのものは単純でもHTTPプロトコルを使ってWebブラウザとサーバー間でやり取りをするシーケンスの中で実現するのは案外大変です。

Cozy Webではこの煩雑な処理を簡単に実現する機能を提供しています。

今回はCozy Webでのフォーム入力の処理方法について説明します。

HelloForm

フォームを用いたWebアプリケーションHelloFormを作成します。

準備

cozyを起動するディレクトリのwebappsディレクトリに、アプリケーションのホームディレクトリとなるHelloFormを作成します。

Formページ

フォームを使った入力画面として以下のページをindex.jadeとしてホームディレクトリに配置します。

-@val form: ViewForm
html
  head
    title HelloForm
  body
    form(method="POST" action={form.action})
      input(hidden="true" name="$scenario" value={form.scenario})
      input(name="name" value={form.name})
      button(type="submit" id="submitbutton" name="$submit" value="ok") Submit
      button(type="submit" id="cancelbutton" name="$submit" value="cancel") Cancel

ViewFormオブジェクト

以下の宣言でフォームの設定に必要なViewFormを参照可能にします。

-@val form: ViewForm

Form/action

actionにViewFormオブジェクトのaction属性を設定します。

    form(method="POST" action={form.action})

今回の場合は空文字が設定されます。入力と同じページにフォームのPOSTが行われるということですね。

POST処理に必要な情報は後述する「$scenario」に設定されています。

Input/Hidden

Hidden属性のInputで入力する「$scenario」プロパティにViewFormオブジェクトのscenario属性を設定します。

      input(hidden="true" name="$scenario" value={form.scenario})

今回の場合は「{&quot;name&quot;:&quot;invoke-operation&quot;,&quot;state&quot;:&quot;input&quot;,&quot;data&quot;:{}}」が設定されています。URLエンコーディングを解除した値は「{"name":"invoke-operation","state":"input","data":{}}」です。

Cozy Webがフォーム処理を行うために必要な情報が設定されています。

Inputデータ

Hidden属性のないInputでデータ入力を行います。

今回は以下の1行が対象です。

      input(name="name" value={form.name})

nameに「name」指定しているので、nameプロパティの入力ということになります。値のデフォルト値としてViewFormオブジェクトのname属性の値を設定しています。今回のケースでは空文字が設定されます。

Input/Ok

OK用のサブミットボタンとして以下のButtonを設定しました。

      button(type="submit" id="submitbutton" name="$submit" value="ok") Submit

name属性に「$submit」、value属性に「ok」を指定しています。

Input/Cancel

キャンセル用のサブミットボタンとして以下のButtonを設定しました。

      button(type="submit" id="cancelbutton" name="$submit" value="cancel") Cancel

name属性に「$submit」、value属性に「cancel」を指定しています。

SSPの場合

フォームのような構造を記述するページの場合、今回の例ではJadeの方が簡潔に記述できるので本ページではJadeを使っていますが、JSP(JavaServer Page)のようなHTMLライクな文法を持つSSP(Scala Server Pages)を使って記述することも可能です。

SSPの場合は以下になります。

<%@ val form: ViewForm %>
<html>
    <head>
	<title>HelloForm</title>
    </head>
    <body>
	<form method="POST" action="${form.action}">
	    <input hidden="true" name="$scenario" value="${form.scenario}"/>
	    <input name="name" value="${form.name}"/>
	    <button type="submit" id="submitbutton" name="$submit" value="ok">Submit</button>
	    <button type="submit" id="cancelbutton" name="$submit" value="cancel">Cancel</button>
	</form>
    </body>
</html>

フォーム記述に使用するViewFormオブジェクトを変数formに束縛する宣言は以下になります。

<%@ val form: ViewForm %>

Formタグに設定する情報はJade版と同じものです。

コントローラ

WEB-INF/controllersにindex.jade用のコントローラである以下のindex.jsonを配置します。

{
  "action": "invoke-operation-scenario",
  "operation": "loopback",
  "method": "POST",
  "successView": "index_complete",
  "errorView": "index_error",
  "parameters": [{
    "name": "token"
  }]
}

action

コントローラのアクションとしてinvoke-operation-scenarioを指定しています。

  "action": "invoke-operation-scenario",

invoke-operation-scenarioは、フォームでパラメタ入力し、このパラメタを使ってオペレーションを呼び出し、その結果をビューに渡すモデルとして生成する処理を行うシナリオです。

フォーム入力にまつわるWebブラウザとサーバ間のインタラクションをシナリオに従って実現します。

operation

実行するオペレーションとしてloopbackを指定しています。

  "operation": "loopback",

loopbackは入力したデータをそのまま表形式のモデルとして受け渡すオペレーションです。

method

メソッドはPOSTを指定しています。

  "method": "POST",

FormのメソッドがPOSTのものを受け付けます。

successView

successViewにはindex_completeを指定しています。

  "successView": "index_complete",

コントローラの処理が成功するとこのページに遷移します。

errorView

errorViewにはindex_errorを指定しています。

  "errorView": "index_error",

コントローラの処理が失敗するとこのページに遷移します。

parameters

フォームから入力されるパラメタとして、パラメタ名とデータ型を指定しています。

  "parameters": [{
    "name": "token"
  }]

パラメタは、パラメタ名nameでデータ型はトークン文字列の一つだけです。

成功ページ

成功ページとして以下のindex_complete.htmlを用意します。コントローラのsuccessViewで指定したページです。

<html>
    <head>
	<title>Form Success</title>
    </head>
    <body>
	<h1>Form Success</h1>
	<c:model/>
    </body>
</html>

このページ内の以下のタグはコントローラの実行結果のモデルの内容を表形式で表示するものです。

	<c:model/>

今回の場合は、loopbackオペレーションの結果が出力されます。

エラーページ

成功ページとして以下のindex_error.htmlを用意します。コントローラのerrorViewで指定したページです。

<html>
    <head>
	<title>Form Error</title>
    </head>
    <body>
	<h1>Form Error</h1>
	<c:error/>
    </body>
</html>

このページ内の以下のタグはコントローラの実行時にエラーが発生した場合、そのエラーを表形式で表示するものです。

	<c:error/>

今回の場合は、loopbackオペレーションがエラーとなる場合に出力されます。

実行

Formページ

curlコマンドによってローカルホストの8080ポート上の/web/HelloForm を取得します。

$ curl http://localhost:8080/web/HelloForm/

以下のHTML文書が返されます。

<!DOCTYPE html>
<html>
  <head>
    <title>HelloForm</title>
  </head>
  <body>
    <form action="" method="POST">
      <input value="{&quot;name&quot;:&quot;invoke-operation&quot;,&quot;state&quot;:&quot;input&quot;,&quot;data&quot;:{}}" name="$scenario" hidden="true" />
      <input value="" name="name" />
      <button value="ok" name="$submit" id="submitbutton" type="submit">Submit</button>
      <button value="cancel" name="$submit" id="cancelbutton" type="submit">Cancel</button>
    </form>
  </body>
</html>

Web画面は以下のとおりです。

OK

$ curl http://localhost:8080/web/HelloForm/ -X POST \
--data-urlencode '$submit=ok' \
--data-urlencode 'name=abc' \
--data-urlencode '$scenario={"name":"invoke-operation","state":"input","data":{}}'
<!DOCTYPE html>
<html><head>
	<title>Form Success</title>
    </head><body>
	<h1>Form Success</h1>
	<table class="">
      <tbody><tr class=""><th scope="row" class="">$submit</th><td class="">ok</td></tr><tr class=""><th scope="row" class="">Name</th><td class="">abc</td></tr><tr class=""><th scope="row" class="">$scenario</th><td class="">{&quot;name&quot;:&quot;invoke-operation&quot;,&quot;state&quot;:&quot;input&quot;,&quot;data&quot;:{}}</td></tr></tbody>
    </table>
    
</body></html>

Web画面は以下のとおりです。

エラー

コントローラで指定したloopbackオペレーションでは、データのいずれかに「#500」のような「#」記号の後ろに数値を入れた文字列を入力すると、この数値をエラー番号としたエラーが発生するようになっています。

この機能を利用してエラーページの動作を確認してみましょう。

Webページの入力から「#500」を入れてSubmitボタンを押したときのHTTPの送信は以下のcurlで実現できます。

「name=#500」となっているところがキモです。

$ curl -v http://localhost:8080/web/HelloForm/ -X POST \
--data-urlencode '$submit=ok' \
--data-urlencode 'name=#500' \
--data-urlencode '$scenario={"name":"invoke-operation","state":"input","data":{}}'

この結果以下のHTMLが返ってきました。

<!DOCTYPE html>
<html><head>
	<title>Form Error</title>
    </head><body>
	<h1>Form Error</h1>
	<table class="">
      <tbody><tr class=""><th scope="row" class="">Code</th><td class="">500</td></tr><tr class=""><th scope="row" class="">Message</th><td class="" /></tr><tr class=""><th scope="row" class="">Top URI</th><td class="" /></tr><tr class=""><th scope="row" class="">Back URI</th><td class="" /></tr><tr class=""><th scope="row" class="">Exception Message</th><td class="" /></tr><tr class=""><th scope="row" class="">Exception Stack</th><td class="" /></tr><tr class=""><th scope="row" class="">Call Tree</th><td class="" /></tr></tbody>
    </table>
    
</body></html>

Web画面は以下のとおりです。

キャンセル

invoke-operattionシナリオでは、cancelがサブミットされるとキャンセル動作が行われます。

$ curl -v http://localhost:8080/web/HelloForm/ -X POST \
--data-urlencode '$submit=cancel' \
--data-urlencode 'name=#500' \
--data-urlencode '$scenario={"name":"invoke-operation","state":"input","data":{}}'

キャンセル動作を行うと、元のページにリダイレクトされ、新規にデータ入力の状態になります。

* Connected to localhost (::1) port 8080 (#0)
> POST /web/HelloForm HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.76.1
> Accept: */*
> Content-Length: 128
> Content-Type: application/x-www-form-urlencoded
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 302 Found
< Date: Thu, 29 Sep 2022 01:29:31 GMT
< Content-Type: text/html;charset=utf-8
< Cache-Control: private,no-store,no-cache,must-revalidate
< Location: http://localhost:8080/web/HelloForm
< Content-Length: 0
< Server: Jetty(9.4.38.v20210224)
< 
* Connection #0 to host localhost left intact

curlの-Lスイッチで、リダイレクトをフォローしてみます。

$ curl -L http://localhost:8080/web/HelloForm/ -X POST \
--data-urlencode '$submit=cancel' \
--data-urlencode 'name=#500' \
--data-urlencode '$scenario={"name":"invoke-operation","state":"input","data":{}}'

そうすると以下のように、無事元のフォームページが返ってきました。

<!DOCTYPE html>
<html>
  <head>
    <title>HelloForm</title>
  </head>
  <body>
    <form action="" method="POST">
      <input value="{&quot;name&quot;:&quot;invoke-operation&quot;,&quot;state&quot;:&quot;input&quot;,&quot;data&quot;:{}}" name="$scenario" hidden="true" />
      <input value="" name="name" />
      <button value="ok" name="$submit" id="submitbutton" type="submit">Submit</button>
      <button value="cancel" name="$submit" id="cancelbutton" type="submit">Cancel</button>
    </form>
  </body>
</html>

Web画面は以下のとおりです。

まとめ

今回はCozy WebでHTMLフォームを取り扱う方法について説明しました。

フォームを扱う場合、Webとサーバ間のインタラクションに伴う状態遷移の管理を行う処理を作るのがなかなか大変です。Cozy Webでは、コントローラに宣言的な定義をするだけで、この処理を簡単に行ってくれることが分かりました。

諸元

Cozy
0.0.7

2022年8月31日水曜日

Cozy Web/Webライブラリ

前回「Cozy Web/レイアウト」ではBootstrapベースのレイアウトを持った共通テンプレートをWebアプリケーション全体で共通化して利用する方法について説明しました。

この共通テンプレートを、複数のWebアプリケーションで共有できると便利です。

このことを実現するため、Cozy Webでは複数のWebアプリケーションから共有できるWebライブラリを作成する機能を提供しています。

今回はこのWebライブラリについてみていきます。

LibHello

まず、Webライブラリとして使用するLibHelloプロジェクトを作成します。

プロジェクトの構成は、ここまで紹介してきた通常のWebアプリケーションと同じものになります。

準備

cozyを起動するディレクトリのwebappsディレクトリに、WebライブラリのホームとなるディレクトリLibHelloを作成します。このディレクトリLibHelloをWebライブラリのホームディレクトリとなります。

共通テンプレート

Webページで共有されるテンプレートとして以下のものをWEB-INF/layouts/default.jadeに作成します。これは「Cozy Web/レイアウト」で作成したものと同じものです。このテンプレートページでBootstrapに必要な設定を行っています。

-@val model: ViewModel
!!! 5
html(lang="ja")
  head
    meta(charset="utf-8")
    meta(name="viewport" content="width=device-width, initial-scale=1")
    link(href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous")
    =model.pageTitle
  body
    script(src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous")
    header
      =model.header
      =model.navigation
    div(class="container-fluid pb-3 flex-grow-1 d-flex flex-column flex-sm-row overflow-auto")
      div(class="row flex-grow-sm-1 flex-grow-0")
        aside(class="col-sm-3 flex-grow-sm-1 flex-shrink-1 flex-grow-0 sticky-top pb-sm-0 pb-3")
          div(class="bg-light border p-1 h-100 sticky-top")
            =model.sidebar
        main(class="col overflow-auto h-100")
          =model.content
    footer	
      =model.footer

部品ページ

Webページの部品はWEB-INF/partialsに格納します。いずれも「Cozy Web/レイアウト」で作成したものと同じものです。

ヘッダー

ヘッダーとして以下のheader.jadeをWEB-INF/partialsに格納しました。「Cozy Web/レイアウト」で作成したものと同じものです。Bootstrapのクラスを設定しています。

nav(class="navbar navbar-light bg-primary")
  div(class="container-fluid")
    span(class="navbar-brand mb-0 h1") Cozy Web

ナビゲーション・バー

ナビゲーション・バーとして以下のnavigation.jadeをWEB-INF/partialsに格納しました。「Cozy Web/レイアウト」で作成したものと同じものです。Bootstrapの機能を用いてナビゲーション・バーを実現しています。

nav(class="navbar navbar-expand-lg navbar-light bg-light")
  div(class="container-fluid")
    a(class="navbar-brand" href="#") Navbar
    button(class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation")
      span(class="navbar-toggler-icon")
    div(class="collapse navbar-collapse" id="navbarNav")
      ul(class="navbar-nav")
        li(class="nav-item")
          a(class="nav-link active" aria-current="page" href="#") Home
        li(class="nav-item")
          a(class="nav-link" href="#") Features
        li(class="nav-item")
          a(class="nav-link" href="#") Pricing
        li(class="nav-item")
          a(class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true") Disabled

フッター

フッターとして以下のfooter.jadeをWEB-INF/partialsに格納しました。「Cozy Web/レイアウト」で作成したものと同じものです。Bootstrapのクラスを設定しています。

nav(class="navbar navbar-light bg-secondary")
  span Cozy Web

サイド・バー

サイド・バーとして以下のsidebar.jadeをWEB-INF/partialsに格納しました。「Cozy Web/レイアウト」で作成したものと同じものです。Bootstrapの機能を用いてサイド・バーを実現しています。

ul(class="nav nav-pills flex-sm-column flex-row mb-auto justify-content-between text-truncate")
  li(class="nav-item")
    a(href="#" class="nav-link px-2 text-truncate")
      i(class="bi bi-house fs-5")
      span(class="d-none d-sm-inline") Home
  li
    a(href="#" class="nav-link px-2 text-truncate")
      i(class="bi bi-speedometer fs-5")
      span(class="d-none d-sm-inline") Dashboard
  li
    a(href="#" class="nav-link px-2 text-truncate")
      i(class="bi bi-card-text fs-5")
      span(class="d-none d-sm-inline") Orders
  li
    a(href="#" class="nav-link px-2 text-truncate")
      i(class="bi bi-bricks fs-5")
      span(class="d-none d-sm-inline") Products
  li
    a(href="#" class="nav-link px-2 text-truncate")
      i(class="bi bi-people fs-5")
      span(class="d-none d-sm-inline") Customers

構成

ここまでは、前回「Cozy Web/レイアウト」のLayoutBootstrapと一点を除いて同一の構成です。異なっているのは、index.htmlがないこと。Webライブラリなので必要ないためです。

HellLib

WebライブラリLibHelloを作成したので、このWebライブラリを使ったWebアプリケーションHelloLibを作成します。

準備

cozyを起動するディレクトリのwebappsディレクトリに、WebライブラリのホームとなるディレクトリHelloLibを作成します。このディレクトリHelloLibをWebライブラリのホームディレクトリとなります。

project.conf

使用するWebライブラリの定義はwepapp.confで行います。

extend: LibHello

extendプロパティによって、親となるWebライブラリであるLibHelloを指定しています。この設定を行うことでWebライブラリの持っている機能をすべて引き継ぐことができます。

このwebapp.confをWEB-INFに格納します。

これで設定は完了です。

Webページ

共通ページデザインは、LibHelloのものがそのまま引き継がれるので、HelloLibではWebページの本体のみ作成します。

表示したい本文を記述したページをindex.htmlとして用意します。

これは前回「Cozy Web/レイアウト」で作成したWebアプリケーション「LayoutBootstrap」と基本的に同じ内容です。(タイトルだけ変更しています)タグのクラスにBootstrapのクラスを設定しています。WebライブラリLibHelloでBootstrap 5を活用したレイアウトを設定しているので、その効果があるはずです。

<html>
    <head>
	<title>HelloLib</title>
    </head>
    <body>
	<h1>HelloLib</h1>
	<table class="table">
	    <thead>
		<tr>
		    <th scope="col">#</th>
		    <th scope="col">First</th>
		    <th scope="col">Last</th>
		    <th scope="col">Handle</th>
		</tr>
	    </thead>
	    <tbody>
		<tr>
		    <th scope="row">1</th>
		    <td>Mark</td>
		    <td>Otto</td>
		    <td>@mdo</td>
		</tr>
		<tr>
		    <th scope="row">2</th>
		    <td>Jacob</td>
		    <td>Thornton</td>
		    <td>@fat</td>
		</tr>
		<tr>
		    <th scope="row">3</th>
		    <td colspan="2">Larry the Bird</td>
		    <td>@twitter</td>
		</tr>
	    </tbody>
	</table>
    </body>
</html>

実行

curlコマンドによってローカルホストの8080ポート上の/web/HelloLib を取得します。

$ curl http://localhost:8080/web/HelloLib/

取得結果は以下になります。無事、登録したHTML文書にBootstrapの各種設定を行ったHTML文書を取得することができました。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <meta content="width=device-width, initial-scale=1" name="viewport" />
    <link crossorigin="anonymous" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" />
    <title>HelloLib</title>
  </head>
  <body>
    <script crossorigin="anonymous" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
    <header>
      <nav class="navbar navbar-light bg-primary">
        <div class="container-fluid">
          <span class="navbar-brand mb-0 h1">Cozy Web</span>
        </div>
      </nav>
      <nav class="navbar navbar-expand-lg navbar-light bg-light">
        <div class="container-fluid">
          <a class="navbar-brand" href="#">Navbar</a>
          <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
          </button>
          <div class="collapse navbar-collapse" id="navbarNav">
            <ul class="navbar-nav">
              <li class="nav-item">
                <a class="nav-link active" aria-current="page" href="#">Home</a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="#">Features</a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="#">Pricing</a>
              </li>
              <li class="nav-item">
                <a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
              </li>
            </ul>
          </div>
        </div>
      </nav>
    </header>
    <div class="container-fluid pb-3 flex-grow-1 d-flex flex-column flex-sm-row overflow-auto">
      <div class="row flex-grow-sm-1 flex-grow-0">
        <aside class="col-sm-3 flex-grow-sm-1 flex-shrink-1 flex-grow-0 sticky-top pb-sm-0 pb-3">
          <div class="bg-light border p-1 h-100 sticky-top">
            <ul class="nav nav-pills flex-sm-column flex-row mb-auto justify-content-between text-truncate">
              <li class="nav-item">
                <a href="#" class="nav-link px-2 text-truncate">
                  <i class="bi bi-house fs-5"></i>
                  <span class="d-none d-sm-inline">Home</span>
                </a>
              </li>
              <li>
                <a href="#" class="nav-link px-2 text-truncate">
                  <i class="bi bi-speedometer fs-5"></i>
                  <span class="d-none d-sm-inline">Dashboard</span>
                </a>
              </li>
              <li>
                <a href="#" class="nav-link px-2 text-truncate">
                  <i class="bi bi-card-text fs-5"></i>
                  <span class="d-none d-sm-inline">Orders</span>
                </a>
              </li>
              <li>
                <a href="#" class="nav-link px-2 text-truncate">
                  <i class="bi bi-bricks fs-5"></i>
                  <span class="d-none d-sm-inline">Products</span>
                </a>
              </li>
              <li>
                <a href="#" class="nav-link px-2 text-truncate">
                  <i class="bi bi-people fs-5"></i>
                  <span class="d-none d-sm-inline">Customers</span>
                </a>
              </li>
            </ul>
          </div>
        </aside>
        <main class="col overflow-auto h-100">
          
          	    <h1>HelloLib</h1>
          	    <table class="table">
          		<thead>
          		    <tr>
          			<th scope="col">#</th>
          			<th scope="col">First</th>
          			<th scope="col">Last</th>
          			<th scope="col">Handle</th>
          		    </tr>
          		</thead>
          		<tbody>
          		    <tr>
          			<th scope="row">1</th>
          			<td>Mark</td>
          			<td>Otto</td>
          			<td>@mdo</td>
          		    </tr>
          		    <tr>
          			<th scope="row">2</th>
          			<td>Jacob</td>
          			<td>Thornton</td>
          			<td>@fat</td>
          		    </tr>
          		    <tr>
          			<th scope="row">3</th>
          			<td colspan="2">Larry the Bird</td>
          			<td>@twitter</td>
          		    </tr>
          		</tbody>
          	</table>
              
        </main>
      </div>
    </div>
    <footer>
      <nav class="navbar navbar-light bg-secondary">
        <span>Cozy Web</span>
      </nav>
    </footer>
  </body>
</html>

このページは以下のように表示されます。

Bootstrapの機能を活かしたレイアウト、カラーコーディネーションになっています。

小さな画面で表示すると、以下のようにメニューが折り畳まれて表示されます。Bootstrapのレシポンシブデザインが効いていることが分かります。

いずれも前回のLayoutBootstrapとタイトルを除いては同じ動きです。

効果

WebアプリケーションHelloLibで用意したファイルはindex.htmlのみです。

HelloLibはWebライブラリLibHelloを引き継いでいるので、LibHelloが提供しているBootstrapの機能を活かした共有レイアウトをそのまま利用できています。

まとめ

今回はWebアプリケーションの共通テンプレートをWebライブラリという形の部品として作成し、Webアプリケーションで使用する方法について説明しました。

Bootstrapベースで、ヘッダやフッタのレイアウトを共通テンプレートとしたWebライブラリHelloLibを作成しました。

このWebライブラリを使用することで、WebアプリケーションHelloLibはHTML文書index.htmlを作成するだけで、HelloLibが提供しているBootstrapベースの共通レイアウトを使用したリッチな画面を得ることができました。

Cozy WebのWebライブラリ機能を使用することで、Webアプリケーションの開発効率を飛躍的に向上させることができます。

諸元

Cozy
0.0.6

2022年7月31日日曜日

Cozy Web/レイアウト

前回「Cozy Web/Bootstrap」ではBootstrapのテンプレートを使用するためにレイアウト機能を使用しました。

今回は、このCozy Webのレイアウト機能について少し詳しく説明します。

HelloLayout

まず、レイアウト機能を使ったHelloLayoutアプリケーションを作成します。

準備

cozyを起動するディレクトリのwebappsディレクトリに、アプリケーションのホームとなるディレクトリHelloLayoutを作成します。このディレクトリHelloLayoutをアプリケーションのホームディレクトリとなります。

共通テンプレート

Webページで共有されるテンプレートとして以下のものをWEB-INF/layouts/default.jadeに作成します。

-@val model: ViewModel
!!! 5
html(lang="ja")
  head
    =model.pageTitle
  body
    =model.header
    =model.navigation
    =model.sidebar
    =model.content
    =model.footer

headに以下の設定があります。

    =model.pageTitle

ページのタイトルをhead要素の配下に追加する指定になります。

またbodyに以下の設定があります。

    =model.header
    =model.navigation
    =model.sidebar
    =model.content
    =model.footer

それぞれ以下の部品をbody要素の配下に追加する指定です。

model.header
ページのヘッダー
model.navigation
ナビゲーション・バー
model.sidebar
サイド・バー
model.content
ページの本文
model.footer
ページのフッター

ヘッダー、フッター、ナビゲーション・バー、サイド・バーで構成される固定レイアウト内に本文を埋め込む構成になっています。

部品ページ

Webページの部品はWEB-INF/partialsに格納します。

partialsに格納する部品は、header.htmlやheader.jadeのようにファイル名本体を部品名とします。サフィックスは部品の記述を行う言語になります。

ヘッダー

ヘッダーとして以下のheader.jadeをWEB-INF/partialsに格納しました。

div Header

ナビゲーション・バー

ナビゲーション・バーとして以下のnavigation.jadeをWEB-INF/partialsに格納しました。

div Navigation

フッター

フッターとして以下のfooter.jadeをWEB-INF/partialsに格納しました。

div Footer

サイド・バー

フッターとして以下のsidebar.jadeをWEB-INF/partialsに格納しました。

div Sidebar

Webページ

WebアプリケーションのWebページとしてindex.htmlを用意します。

後述のBootstrap版との比較のために、Bootstrapのクラスを用いた同じWebページを用いています。この版ではBootstrapの設定は行っていないので、画面表示に影響しません。

<!doctype html>
<html>
    <head>
	<title>HelloLayout</title>
    </head>
    <body>
	<h1>HelloLayout</h1>
	<table class="table">
	    <thead>
		<tr>
		    <th scope="col">#</th>
		    <th scope="col">First</th>
		    <th scope="col">Last</th>
		    <th scope="col">Handle</th>
		</tr>
	    </thead>
	    <tbody>
		<tr>
		    <th scope="row">1</th>
		    <td>Mark</td>
		    <td>Otto</td>
		    <td>@mdo</td>
		</tr>
		<tr>
		    <th scope="row">2</th>
		    <td>Jacob</td>
		    <td>Thornton</td>
		    <td>@fat</td>
		</tr>
		<tr>
		    <th scope="row">3</th>
		    <td colspan="2">Larry the Bird</td>
		    <td>@twitter</td>
		</tr>
	    </tbody>
	</table>
    </body>
</html>

実行

curlコマンドによってローカルホストの8080ポート上の/web/HelloLayout/を取得します。

/web はCozy上のWebアプリケーションのホームです。その配下のHelloLayoutが、先程作成したディレクトリHelloLayoutに対応するもので、ディレクトリ名がアプリケーション名になっています。

$ curl http://localhost:8080/web/HelloLayout/

取得結果は以下になります。無事登録したHTML文書を取得することができました。

HTML文書には期待通りLayoutの各種設定が自動的に行われています。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <title>HelloLayout</title>
  </head>
  <body>
    <div>Header</div>
    <div>Navigation</div>
    <div>Sidebar</div>
    
    	<h1>HelloLayout</h1>
    	<table class="table">
    	    <thead>
    		<tr>
    		    <th scope="col">#</th>
    		    <th scope="col">First</th>
    		    <th scope="col">Last</th>
    		    <th scope="col">Handle</th>
    		</tr>
    	    </thead>
    	    <tbody>
    		<tr>
    		    <th scope="row">1</th>
    		    <td>Mark</td>
    		    <td>Otto</td>
    		    <td>@mdo</td>
    		</tr>
    		<tr>
    		    <th scope="row">2</th>
    		    <td>Jacob</td>
    		    <td>Thornton</td>
    		    <td>@fat</td>
    		</tr>
    		<tr>
    		    <th scope="row">3</th>
    		    <td colspan="2">Larry the Bird</td>
    		    <td>@twitter</td>
    		</tr>
    	    </tbody>
    	</table>
        
    <div>Footer</div>
  </body>
</html>

このページは以下のように表示されます。

LayoutBootstrap

レイアウト機能を使うと、定形のWebページ内に定義した部品を差し込める事が分かりました。

このレイアウト機能を使って、Bootstrapベースの定形ページを作ってみます。

準備

cozyを起動するディレクトリのwebappsディレクトリに、アプリケーションのホームとなるディレクトリLayoutBootstrapを作成します。このディレクトリLayoutBootstrapをアプリケーションのホームディレクトリとなります。

共通テンプレート

Webページで共有されるテンプレートとして以下のものをWEB-INF/layouts/default.jadeに作成します。このテンプレートページでBootstrapに必要な設定を行っています。

-@val model: ViewModel
!!! 5
html(lang="ja")
  head
    meta(charset="utf-8")
    meta(name="viewport" content="width=device-width, initial-scale=1")
    link(href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous")
    =model.pageTitle
  body
    script(src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous")
    header
      =model.header
      =model.navigation
    div(class="container-fluid pb-3 flex-grow-1 d-flex flex-column flex-sm-row overflow-auto")
      div(class="row flex-grow-sm-1 flex-grow-0")
        aside(class="col-sm-3 flex-grow-sm-1 flex-shrink-1 flex-grow-0 sticky-top pb-sm-0 pb-3")
          div(class="bg-light border p-1 h-100 sticky-top")
            =model.sidebar
        main(class="col overflow-auto h-100")
          =model.content
    footer	
      =model.footer

部品ページ

Webページの部品はWEB-INF/partialsに格納します。

ヘッダー

ヘッダーとして以下のheader.jadeをWEB-INF/partialsに格納しました。

Bootstrapのクラスを設定しています。

nav(class="navbar navbar-light bg-primary")
  div(class="container-fluid")
    span(class="navbar-brand mb-0 h1") Cozy Web

ナビゲーション・バー

ナビゲーション・バーとして以下のnavigation.jadeをWEB-INF/partialsに格納しました。

Bootstrapの機能を用いてナビゲーション・バーを実現しています。

nav(class="navbar navbar-expand-lg navbar-light bg-light")
  div(class="container-fluid")
    a(class="navbar-brand" href="#") Navbar
    button(class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation")
      span(class="navbar-toggler-icon")
    div(class="collapse navbar-collapse" id="navbarNav")
      ul(class="navbar-nav")
        li(class="nav-item")
          a(class="nav-link active" aria-current="page" href="#") Home
        li(class="nav-item")
          a(class="nav-link" href="#") Features
        li(class="nav-item")
          a(class="nav-link" href="#") Pricing
        li(class="nav-item")
          a(class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true") Disabled

フッター

フッターとして以下のfooter.jadeをWEB-INF/partialsに格納しました。

Bootstrapのクラスを設定しています。

nav(class="navbar navbar-light bg-secondary")
  span Cozy Web

サイド・バー

サイド・バーとして以下のsidebar.jadeをWEB-INF/partialsに格納しました。

Bootstrapの機能を用いてサイド・バーを実現しています。

ul(class="nav nav-pills flex-sm-column flex-row mb-auto justify-content-between text-truncate")
  li(class="nav-item")
    a(href="#" class="nav-link px-2 text-truncate")
      i(class="bi bi-house fs-5")
      span(class="d-none d-sm-inline") Home
  li
    a(href="#" class="nav-link px-2 text-truncate")
      i(class="bi bi-speedometer fs-5")
      span(class="d-none d-sm-inline") Dashboard
  li
    a(href="#" class="nav-link px-2 text-truncate")
      i(class="bi bi-card-text fs-5")
      span(class="d-none d-sm-inline") Orders
  li
    a(href="#" class="nav-link px-2 text-truncate")
      i(class="bi bi-bricks fs-5")
      span(class="d-none d-sm-inline") Products
  li
    a(href="#" class="nav-link px-2 text-truncate")
      i(class="bi bi-people fs-5")
      span(class="d-none d-sm-inline") Customers

Webページ

表示したい本文を記述したページをindex.htmlとして用意します。

これは前出のHelloLayout版と基本的に同じ内容です。(タイトルだけ変更しています)今度はBootstrapのクラス指定が活きてくるはずです。

<!doctype html>
<html>
    <head>
	<title>LayoutBootstrap</title>
    </head>
    <body>
	<h1>LayoutBootstrap</h1>
	<table class="table">
	    <thead>
		<tr>
		    <th scope="col">#</th>
		    <th scope="col">First</th>
		    <th scope="col">Last</th>
		    <th scope="col">Handle</th>
		</tr>
	    </thead>
	    <tbody>
		<tr>
		    <th scope="row">1</th>
		    <td>Mark</td>
		    <td>Otto</td>
		    <td>@mdo</td>
		</tr>
		<tr>
		    <th scope="row">2</th>
		    <td>Jacob</td>
		    <td>Thornton</td>
		    <td>@fat</td>
		</tr>
		<tr>
		    <th scope="row">3</th>
		    <td colspan="2">Larry the Bird</td>
		    <td>@twitter</td>
		</tr>
	    </tbody>
	</table>
    </body>
</html>

実行

curlコマンドによってローカルホストの8080ポート上の/web/LayoutBootstrap を取得します。

$ curl http://localhost:8080/web/LayoutBootstrap/

取得結果は以下になります。無事、登録したHTML文書にBootstrapの各種設定を行ったHTML文書を取得することができました。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <meta content="width=device-width, initial-scale=1" name="viewport" />
    <link crossorigin="anonymous" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" />
    <title>LayoutBootstrap</title>
  </head>
  <body>
    <script crossorigin="anonymous" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
    <header>
      <nav class="navbar navbar-light bg-primary">
        <div class="container-fluid">
          <span class="navbar-brand mb-0 h1">Cozy Web</span>
        </div>
      </nav>
      <nav class="navbar navbar-expand-lg navbar-light bg-light">
        <div class="container-fluid">
          <a class="navbar-brand" href="#">Navbar</a>
          <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
          </button>
          <div class="collapse navbar-collapse" id="navbarNav">
            <ul class="navbar-nav">
              <li class="nav-item">
                <a class="nav-link active" aria-current="page" href="#">Home</a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="#">Features</a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="#">Pricing</a>
              </li>
              <li class="nav-item">
                <a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
              </li>
            </ul>
          </div>
        </div>
      </nav>
    </header>
    <div class="container-fluid pb-3 flex-grow-1 d-flex flex-column flex-sm-row overflow-auto">
      <div class="row flex-grow-sm-1 flex-grow-0">
        <aside class="col-sm-3 flex-grow-sm-1 flex-shrink-1 flex-grow-0 sticky-top pb-sm-0 pb-3">
          <div class="bg-light border p-1 h-100 sticky-top">
            <ul class="nav nav-pills flex-sm-column flex-row mb-auto justify-content-between text-truncate">
              <li class="nav-item">
                <a href="#" class="nav-link px-2 text-truncate">
                  <i class="bi bi-house fs-5"></i>
                  <span class="d-none d-sm-inline">Home</span>
                </a>
              </li>
              <li>
                <a href="#" class="nav-link px-2 text-truncate">
                  <i class="bi bi-speedometer fs-5"></i>
                  <span class="d-none d-sm-inline">Dashboard</span>
                </a>
              </li>
              <li>
                <a href="#" class="nav-link px-2 text-truncate">
                  <i class="bi bi-card-text fs-5"></i>
                  <span class="d-none d-sm-inline">Orders</span>
                </a>
              </li>
              <li>
                <a href="#" class="nav-link px-2 text-truncate">
                  <i class="bi bi-bricks fs-5"></i>
                  <span class="d-none d-sm-inline">Products</span>
                </a>
              </li>
              <li>
                <a href="#" class="nav-link px-2 text-truncate">
                  <i class="bi bi-people fs-5"></i>
                  <span class="d-none d-sm-inline">Customers</span>
                </a>
              </li>
            </ul>
          </div>
        </aside>
        <main class="col overflow-auto h-100">
          
          	    <h1>LayoutBootstrap</h1>
          	    <table class="table">
          		<thead>
          		    <tr>
          			<th scope="col">#</th>
          			<th scope="col">First</th>
          			<th scope="col">Last</th>
          			<th scope="col">Handle</th>
          		    </tr>
          		</thead>
          		<tbody>
          		    <tr>
          			<th scope="row">1</th>
          			<td>Mark</td>
          			<td>Otto</td>
          			<td>@mdo</td>
          		    </tr>
          		    <tr>
          			<th scope="row">2</th>
          			<td>Jacob</td>
          			<td>Thornton</td>
          			<td>@fat</td>
          		    </tr>
          		    <tr>
          			<th scope="row">3</th>
          			<td colspan="2">Larry the Bird</td>
          			<td>@twitter</td>
          		    </tr>
          		</tbody>
          	</table>
              
        </main>
      </div>
    </div>
    <footer>
      <nav class="navbar navbar-light bg-secondary">
        <span>Cozy Web</span>
      </nav>
    </footer>
  </body>
</html>

このページは以下のように表示されます。

Bootstrapの機能を活かしたレイアウト、カラーコーディネーションになっています。

小さな画面で表示すると、以下のようにメニューが折り畳まれて表示されます。Bootstrapのレシポンシブデザインが効いていることが分かります。

まとめ

Cozy Webのレイアウト機能について説明しました。

BootstrapのようなWebフレームワークの定形テンプレートを容易に作成することができます。

次回は、定形テンプレートをさらに部品化する方法について説明します。

諸元

Cozy
0.0.6

2022年6月30日木曜日

Cozy Web/Bootstrap

BootstrapはレスポンシブWebデザインを可能にするWebフレームワークです。

Bootstrapを使う場合、Webの全ページにBootstrapの設定を行う必要がありますが、これを手作業で行うと大変です。

Cozy Webでは、共通のページレイアウトを設定して、これを必要な全ページに自動的に適用する機能を提供しています。この機能を用いてBootstrapのWebページを作成してみます。

HelloBootstrap

Bootstrapを用いたWebアプリケーションHelloBootstrapを作成します。

準備

cozyを起動するディレクトリのwebappsディレクトリに、アプリケーションのホームとなるディレクトリHelloBootstrapを作成します。このディレクトリHelloBootstrapをアプリケーションのホームディレクトリとなります。

共通テンプレート

Webページで共有されるテンプレートとして以下のものをWEB-INF/layouts/default.jadeに作成します。

Bootstrapの本体はCDN上に公開されているものを使用する設定になっています。

-@val model: ViewModel
!!! 5
html(lang="ja")
  head
    meta(charset="utf-8")
    meta(name="viewport" content="width=device-width, initial-scale=1")
    link(href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous")
    =model.pageTitle
  body
    script(src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous")
    =model.content

ポイントは以下の2つの設定です。

    =model.pageTitle
    =model.content

前者はページのタイトルをhead要素内に設定します。

後者はページの内容をbody要素内に展開します。

なお、ここではJadeを用いてページの定義をしていますが、HTMLやSSPを用いてもかまいません。

共通テンプレートはタグ混じりの本文文書がなく、タグによる骨格だけの文書になるので、Jadeでは構造を簡潔に見通しよく記述できるためJadeを採用しました。

Webページ

WebアプリケーションのWebページとしてindex.htmlを用意します。

<html>
    <head>
	<title>HelloBootstrap</title>
    </head>
    <body>
	<h1>HelloBootstrap</h1>
	<table class="table">
	    <thead>
		<tr>
		    <th scope="col">#</th>
		    <th scope="col">First</th>
		    <th scope="col">Last</th>
		    <th scope="col">Handle</th>
		</tr>
	    </thead>
	    <tbody>
		<tr>
		    <th scope="row">1</th>
		    <td>Mark</td>
		    <td>Otto</td>
		    <td>@mdo</td>
		</tr>
		<tr>
		    <th scope="row">2</th>
		    <td>Jacob</td>
		    <td>Thornton</td>
		    <td>@fat</td>
		</tr>
		<tr>
		    <th scope="row">3</th>
		    <td colspan="2">Larry the Bird</td>
		    <td>@twitter</td>
		</tr>
	    </tbody>
	</table>
    </body>
</html>

このページにはBootstrapの設定はありませんが、table要素のclass属性にBootstrapの定義するクラスであるtableを使用しています。

実行

curlコマンドによってローカルホストの8080ポート上の/web/HelloBootstrap/を取得します。

/web はCozy上のWebアプリケーションのホームです。その配下のHelloBootstrapが、先程作成したディレクトリHelloBootstrapに対応するもので、ディレクトリ名がアプリケーション名になっています。

$ curl http://localhost:8080/web/HelloBootstrap/

取得結果は以下になります。無事登録したHTML文書を取得することができました。

HTML文書には期待通りBootstrapの各種設定が自動的に行われています。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <meta content="width=device-width, initial-scale=1" name="viewport" />
    <link crossorigin="anonymous" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" />
  </head>
  <body>
    <script crossorigin="anonymous" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
    
    	<h1>HelloBootstrap</h1>
    	<table class="table">
    	    <thead>
    		<tr>
    		    <th scope="col">#</th>
    		    <th scope="col">First</th>
    		    <th scope="col">Last</th>
    		    <th scope="col">Handle</th>
    		</tr>
    	    </thead>
    	    <tbody>
    		<tr>
    		    <th scope="row">1</th>
    		    <td>Mark</td>
    		    <td>Otto</td>
    		    <td>@mdo</td>
    		</tr>
    		<tr>
    		    <th scope="row">2</th>
    		    <td>Jacob</td>
    		    <td>Thornton</td>
    		    <td>@fat</td>
    		</tr>
    		<tr>
    		    <th scope="row">3</th>
    		    <td colspan="2">Larry the Bird</td>
    		    <td>@twitter</td>
    		</tr>
    	    </tbody>
    	</table>
        
  </body>
</html>

このページは以下のように表示されます。

次の例

次の例として以下のHTML文書をrich-table.htmlとして用意します。

このHTML文書では、table要素のclass属性にBootstrapの定義するクラスであるtable, table-striped, table-hoverを使用しています。

またtr要素のclass属性にBootstrapの定義するクラスであるtable-primaryを使用しています。

<html>
    <head>
	<title>Rich Table</title>
    </head>
    <body>
	<h1>Rich Table</h1>
	<table class="table table-striped table-hover">
	    <thead>
		<tr class="table-primary">
		    <th scope="col">#</th>
		    <th scope="col">First</th>
		    <th scope="col">Last</th>
		    <th scope="col">Handle</th>
		</tr>
	    </thead>
	    <tbody>
		<tr>
		    <th scope="row">1</th>
		    <td>Mark</td>
		    <td>Otto</td>
		    <td>@mdo</td>
		</tr>
		<tr>
		    <th scope="row">2</th>
		    <td>Jacob</td>
		    <td>Thornton</td>
		    <td>@fat</td>
		</tr>
		<tr>
		    <th scope="row">3</th>
		    <td colspan="2">Larry the Bird</td>
		    <td>@twitter</td>
		</tr>
	    </tbody>
	</table>
    </body>
</html>

実行

curlコマンドによってローカルホストの8080ポート上の/web/HelloBootstrap/rich-table を取得します。

$ curl http://localhost:8080/web/HelloBootstrap/rich-table

取得結果は以下になります。無事、登録したHTML文書にBootstrapの各種設定を行ったHTML文書を取得することができました。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <meta content="width=device-width, initial-scale=1" name="viewport" />
    <link crossorigin="anonymous" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" />
    <title>Rich Table</title>
  </head>
  <body>
    <script crossorigin="anonymous" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
    
    	<h1>Rich Table</h1>
    	<table class="table table-striped table-hover">
    	    <thead>
    		<tr class="table-primary">
    		    <th scope="col">#</th>
    		    <th scope="col">First</th>
    		    <th scope="col">Last</th>
    		    <th scope="col">Handle</th>
    		</tr>
    	    </thead>
    	    <tbody>
    		<tr>
    		    <th scope="row">1</th>
    		    <td>Mark</td>
    		    <td>Otto</td>
    		    <td>@mdo</td>
    		</tr>
    		<tr>
    		    <th scope="row">2</th>
    		    <td>Jacob</td>
    		    <td>Thornton</td>
    		    <td>@fat</td>
    		</tr>
    		<tr>
    		    <th scope="row">3</th>
    		    <td colspan="2">Larry the Bird</td>
    		    <td>@twitter</td>
    		</tr>
    	    </tbody>
    	</table>
        
  </body>
</html>

このページは以下のように表示されます。

まとめ

今回はCozy Webの共通テンプレート機能を用いて、Bootstrapを用いたWebアプリケーションを作成しました。

特にコントローラの設定も不要で、単に共通テンプレートを所定の場所に置くだけで、Webアプリケーションのページに共通の設定を行うことができました。

次回は共通テンプレートを使って、ページに共通構造を導入してみます。

諸元

Cozy
0.0.6

2022年5月31日火曜日

Cozy Web/Controller

Webアプリケーションでは、リクエストに対する動的な振る舞いを実現する必要があります。このためのメカニズムとしてCozy Webでは、Webパイプラインというメカニズムを用意しています。

Webパイプラインでは複数のモジュールをパイプライン上に配置しますが、振る舞いを記述するためのモジュールとしてパイプライン上で動作するコントローラを使用します。

今回はコントローラを用いてページの振る舞いを記述する基本的な方法について説明します。

HelloController

コントローラを使用するWebアプリケーションHelloControllerを作成します。

準備

cozyを起動するディレクトリのwebappsディレクトリに、アプリケーションのホームとなるディレクトリHelloControllerを作成します。このディレクトリHelloControllerをアプリケーションのホームディレクトリとなります。

Webページ

コントローラによって生じる振る舞いを表示するためのWebページとして以下のindex.htmlを用意します。

<html>
    <head>
	<title>HelloController</title>
    </head>
    <body>
	<p><c:value name="a"/></p>
    </body>
</html>

拡張タグvalue「<c:value name="a"/>」によって、プロパティ「a」の内容を表示します。

このindex.htmlをHelloControlerディレクトリに配備します。

Controller

次はコントローラの設定です。

まずコントローラに設定するアクションとして、以下のプロパティaに値を設定するアクションを作成します。

{
  "action": "property",
  "properties": [{
    "name" : "a",
    "value" : 5
  }]
}

このアクションをコントローラに登録するために、ホームディレクトリ配下のWEB-INF/controllersディレクトリにindex.jsonの名前で保存します。

index.jsonの名前はindex.html表示前に起動されるコントローラに登録されるアクションであることを示しています。

これでアプリケーションの作成は完了です。

実行

curlコマンドによってローカルホストの8080ポート上の /web/HelloTag/index.html を取得します。

/web はCozy上のWebアプリケーションのホームです。その配下のHelloTagが、先程作成したディレクトリHelloTagに対応するもので、ディレクトリ名がアプリケーション名になっています。

curl -v http://localhost:8080/web/HelloTag/index.html

取得結果は以下になります。無事登録したHTMLファイルを取得することができました。

-vスイッチが指定されているので、HTTPのプロトコルヘッダーも表示されています。

取得結果は以下になります。無事登録したHTMLファイルを取得することができました。

-vスイッチが指定されているので、HTTPのプロトコルヘッダーも表示されています。

*   Trying ::1:8080...
* Connected to localhost (::1) port 8080 (#0)
> GET /web/HelloController/index.html HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.76.1
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Sun, 29 May 2022 02:41:41 GMT
< Content-Type: text/html;charset=utf-8
< Transfer-Encoding: chunked
< Server: Jetty(9.4.38.v20210224)
< 
<!DOCTYPE html>
<html><head>
	<title>HelloController</title>
    </head><body>
	<p>5</p>
    
</body></html>
* Connection #0 to host localhost left intact

無事、プロパティaの値として設定した「5」がWebページ上に表示されました。

Webページの種類

HTML文書index.htmlはコントローラで設定したプロパティを表示します。

HTML文書以外のフォーマットでも、同一のコントローラに対して動作します。

このindex.htmlと同等の各種フォーマットは以下のとおりです。

Jade

Jade文書では、serviceにバインドされたViewService経由でプロパティを取得します。

-@val service: ViewService
html
  head
    title HelloController
  body
    p =service.get("a")

アプリケーションに配備する場合にはindex.jadeで保存します。

Jade&タグ

Jade文書でも拡張タグを用いることができます。この場合はViewServiceを直接使用する必要はありません。

html
  head
    title HelloController
  body
    p<
      c:value(name="a")

アプリケーションに配備する場合にはindex.jadeで保存します。

SSP

SSP文書でも、serviceにバインドされたViewService経由でプロパティを取得します。

<%@ val service: ViewService %>
<html>
    <head>
	<title>HelloController</title>
    </head>
    <body>
	<p><%= service.get("a") %></p>
    </body>
</html>

アプリケーションに配備する場合にはindex.sspで保存します。

SSP&タグ

SSP文書でも拡張タグを用いることができます。この場合はViewServiceを直接使用する必要はありません。

<html>
    <head>
	<title>HelloController</title>
    </head>
    <body>
	<p><c:value name="a"/></p>
    </body>
</html>

アプリケーションに配備する場合にはindex.sspで保存します。

HTML文書+拡張タグに加えて、Jade文書、Jade文書+拡張タグ、SSP文書、SSP文書+拡張タグのいずれも、同一のコントローラ用のアクションindex.jspで同じ表示をします。ページ表示に必要なデータ生成部と、表示用のビュー部の独立性が保たれていることが分かります。

まとめ

今回はCozy WebでWebアプリケーションの振る舞いを制御するコントローラの基本的な使い方について説明しました。

JSON文書でコントローラに設定するアクションを記述し、WEB-INF/controllerに配備することでコントローラの設定を行うことができます。

コントローラで生成したデータはHTML文書などのビュー上に配置した拡張タグで表示することができます。

今回のコントローラはプロパティ設定のアクションなので動作もシンプルですが、Form処理などの複雑な処理もアクション設定のみで使用できるようになります。

次回はBootstrapを使ったWebアプリケーションをCozy Webで実現する方法を取り上げる予定です。

諸元

Cozy
0.0.6

2022年4月30日土曜日

Cozy Web/Tag

前回紹介したとおりCozy WebではHTMLページを動的に生成するための機能として、テンプレートエンジンScalateによるSspやJade(Pug)を提供しています。

SspやJadeでは、基本的にプログラミング言語の断片をHTMLに埋め込む形になります。プログラミング機能をフルに使えるので、リッチな動的コンテンツを作成できるというメリットがある反面おまじない的なコードが必要になったり、アプリケーションロジックがプレゼンテーション層であるWebページに混入しがちになるといった問題がでてきます。

この問題に対してCozy Webではタグ機能を用意しました。タグ機能はCozy Webが提供する様々な機能をビュー層で簡単に使えるようにするための機能を提供します。拡張タグをHTML内に埋め込むことで、HTMLページの動的機能を簡単に実現することができます。

SSP

まずSSPによる動的ページについて確認しておきましょう。

以下はSSPによる動的ページです。

<%@ var context: ViewContext %>
<html>
    <head>
	<title>HelloTag</title>
    </head>
    <body>
	<p>現在の時刻は${context.datetime}です。</p>
    </body>
</html>

まずページ内で使用する変数として、以下の行でViewContextを格納する変数contextを定義しています。

<%@ var context: ViewContext %>

そして、以下の行でViewContextオブジェクトのdateTimeメソッドを呼び出し、その結果をpタグ内の文章に埋め込んでいます。dateTimeメソッドは現在時刻をロケール、タイムゾーンに対応した文字列で返します。

	<p>現在の時刻は${context.dateTime}です。</p>

HelloTag

Cozy Webのタグを使用したWebアプリケーションHelloTagを作成します。

アプリケーション

以下のHTMLはCozy Webタグを使用して前出のSSPと同じ動作をします。

<html>
    <head>
	<title>HelloTag</title>
    </head>
    <body>
	<p>現在の時刻は<c:datetime/>です。</p>
    </body>
</html>

以下の行で現在時刻を埋め込みたい場所にタグc:datetime配置しています。Cozy WebではHTML内に配置されたc:datetimeを現在時刻に変換します。

	<p>現在の時刻は<c:datetime/>です。</p>

SSPなどのようなプログラミング成分はなく、動的なコンテンツ部分を宣言的に定義することができます。

配備

cozyを起動するディレクトリにwebappsディレクトリに、アプリケーションのホームとなるディレクトリHelloTagを作成します。

このHelloTagディレクトリに前述のindex.htmlを作成すればOKです。

起動

Cozyをwebコマンドで起動するとWebプラットフォームとして起動します。

$ cozy web

実行

curlコマンドによってローカルホストの8080ポート上の /web/HelloTag/index.html を取得します。

/web はCozy上のWebアプリケーションのホームです。その配下のHelloTagが、先程作成したディレクトリHelloTagに対応するもので、ディレクトリ名がアプリケーション名になっています。

curl -v http://localhost:8080/web/HelloTag/index.html

取得結果は以下になります。無事登録したHTMLファイルを取得することができました。

-vスイッチが指定されているので、HTTPのプロトコルヘッダーも表示されています。

> GET /web/HelloTag/index.html HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.76.1
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Sat, 30 Mar 2022 18:30:00 GMT
< Content-Type: text/html;charset=utf-8
< Transfer-Encoding: chunked
< Server: Jetty(9.4.38.v20210224)
< 
<!DOCTYPE html>
<html>
    <head>
	<title>Hello Tag</title>
    </head>
    <body>
	<p>現在の時刻は2022年4月30日18時30分00秒です。</p>
    </body>
</html>

まとめ

今回はCozy WebのTag機能を説明しました

Tag機能を使えば、通常のHTMLにタグを埋め込むだけで動的コンテンツのWebページを作成することができます。

次回は動的コンテンツを実現する要の機能であるコントローラー機能を説明します。

諸元

Cozy
0.0.5

2022年3月31日木曜日

Cozy Web/HTMLフォーマット

Cozy Webでは通常のHTMLに加えて、以下の4つのフォーマットをサポートしています。

Mustache
Mustache派生の独自マークアップ
Scaml
HamlのScala版
Jade
Pug派生の独自マークアップ
Ssp
VelocityやJSPなどのHTML埋め込みフォーマットのScala版

これらのフォーマットはテンプレートエンジンScalateを使用して実現しており、フォーマットの仕様はScalateの提供するものになります。

今回はこの中でSspとJadeの2つのフォーマットを使ったWebページを試してみます。

HelloSsp

Ssp(Scala Server Pages)はVeloicyやJSPなどと同系統のHTMLにマークアップするフォーマットです。Scalate独自のフォーマットで、埋め込みプログラミング言語にScalaを使用しています。

HTMLをベースに必要箇所だけマークアップする方式なので習得が容易です。またお勧めはできませんが、プログラミング言語によるロジックをマークアップで記述することが可能です。

アプリケーション

以下のindex.sspを用意します。

基本的に通常のHTMLで、"${"と"}"で囲まれた「new java.util.Date()」がScala言語の埋め込み部分です。

<html>
    <head>
	<title>Hello SSP!</title>
    </head>
    <body>
	<h1>Hello SSP!</h1>
	<p>今日は ${new java.util.Date()} です。</p>
    </body>
</html>

配備

cozyを起動するディレクトリにwebappsディレクトリに、アプリケーションのホームとなるディレクトリHelloSspを作成します。

このHelloSspディレクトリに前述のindex.sspを作成すればOKです。

起動

Cozyをwebコマンドで起動するとWebプラットフォームとして起動します。

$ cozy web

実行

curlコマンドによってローカルホストの8080ポート上の /web/HelloSsp/index.html を取得します。

/web はCozy上のWebアプリケーションのホームです。その配下のHelloSspが、先程作成したディレクトリHelloSspに対応するもので、ディレクトリ名がアプリケーション名になっています。

curl -v http://localhost:8080/web/HelloSsp/index.html

取得結果は以下になります。無事登録したHTMLファイルを取得することができました。

-vスイッチが指定されているので、HTTPのプロトコルヘッダーも表示されています。

取得結果は以下になります。無事登録したHTMLファイルを取得することができました。

-vスイッチが指定されているので、HTTPのプロトコルヘッダーも表示されています。

> GET /web/HelloSsp/index.html HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.76.1
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Mon, 28 Mar 2022 22:02:47 GMT
< Content-Type: text/html;charset=utf-8
< Transfer-Encoding: chunked
< Server: Jetty(9.4.38.v20210224)
< 
<!DOCTYPE html>
<html>
    <head>
	<title>Hello SSP!</title>
    </head>
    <body>
	<h1>Hello SSP!</h1>
	<p>今日は 2022年3月29日 です。</p>
    </body>
</html>

HelloJade

SspはHTMLをベースにマークアップを入れる方式なので習得も容易ですが、HTML言語から引き継いでいる特性としてマークアップがやや煩雑です。

文章の中にマークアップする場合にはHTML言語が適していますが、HTML文書の構造などを記述する場合には冗長な表現になりがちです。

Jadeはタグの木構造を簡潔に記述することに適したフォーマットです。Jadeは現在ではPug( https://pugjs.org/ )と変名されていますが、ここではScalateの用語法を踏襲してJadeを使用します。Jadeはタグの木構造を簡潔に記述することに適したフォーマットです。Jadeは現在ではPug( https://pugjs.org/ )と変名されていますが、ここではScalateの用語法を踏襲してJadeを使用します。

アプリケーション

以下のindex.jadeを用意します。

タグの木構造をインデントで記述します。HTML文書を簡潔に記述することができます。

html(lang="ja")
  head
    title Hello Jade!
  body
    h1 Hello Jade!
    p
      | 今日は
      = new java.util.Date()
      | です。
    :markdown
      Jadeを使うとMarkdownで文章を書く事ができます。

配備

cozyを起動するディレクトリにwebappsディレクトリに、アプリケーションのホームとなるディレクトリHelloJadeを作成します。

このHelloJadeディレクトリに前述のindex.jadeを作成すればOKです。

起動

Cozyをwebコマンドで起動するとWebプラットフォームとして起動します。

$ cozy web

実行

curlコマンドによってローカルホストの8080ポート上の /web/HelloJade/index.html を取得します。

/web はCozy上のWebアプリケーションのホームです。その配下のHelloJadeが、先程作成したディレクトリHelloJadeに対応するもので、ディレクトリ名がアプリケーション名になっています。

curl -v http://localhost:8080/web/HelloJade/index.html

取得結果は以下になります。無事登録したHTMLファイルを取得することができました。

-vスイッチが指定されているので、HTTPのプロトコルヘッダーも表示されています。

取得結果は以下になります。無事登録したHTMLファイルを取得することができました。

-vスイッチが指定されているので、HTTPのプロトコルヘッダーも表示されています。

*   Trying ::1:8080...
* Connected to localhost (::1) port 8080 (#0)
> GET /web/HelloJade/index.html HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.76.1
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Mon, 28 Mar 2022 22:04:28 GMT
< Content-Type: text/html;charset=utf-8
< Transfer-Encoding: chunked
< Server: Jetty(9.4.38.v20210224)
< 
<!DOCTYPE html>
<html lang="ja">
  <head>
    <title>Hello Jade!</title>
  </head>
  <body>
    <h1>Hello Jade!</h1>
    <p>
      今日は
      2022年3月29日
      です。
    </p>
    <p>Jadeを使うとMarkdownで文章を書く事ができます。</p>
  </body>
  </div>
</html>

まとめ

Cozy WebではHTML文書記述に以下の5つのフォーマットを使用することができます。

  • HTML
  • Mustache
  • Scaml
  • Jade
  • Ssp

前回HTML文書を使用する方法を紹介しました。

HTML文書をHTMLフォーマットで作成したものは、そのまま配備するだけでWebで表示することができます。

ただ、動的な要素を持っているページの記述の場合には動的生成が可能な文書フォーマットを使用する必要があります。Cozy WebではScalateがサポートしている4つのフォーマットが使用可能になっています。

今回は、その中のJadeとSspの2つについて使用方法を説明しました。MustacheやScamlも同様にそれぞれのフォーマットでページを記述するだけなので、簡単に使用できると思います。

次回はArcadiaが提供するタグ機能を紹介する予定です。

諸元

Cozy
0.0.4

2022年2月28日月曜日

Cozy Web

Modegramming Styleブログではモデリングとプログラミングの一体化を指向したModegrammingというコンセプトを提唱しており、その実現のための技術体系としてSimpleModelingを整備しています。

SimpleModelingを実現するためのプラットフォームとして以下の4つのプロダクトを集約したCozyを立ち上げたことを前回ご紹介しました。

SmartDox
文書処理系
SimpleModeler
モデルコンパイラ
Kaleidox
アクション言語
Arcadia
Webフレームワーク

CozyはGitHubで開発を進めています。

  • https://github.com/asami/cozy

Cozy Web

モデル駆動開発をWebアプリケーションまで広げるためには、モデル駆動対応したWebプラットフォームが必要です。このためのWebフレームワークとして開発を進めてきたのがArcadiaです。

CozyはこのArcadiaをベースに動作させることでモデル駆動の基盤のWebプラットフォームとして動作します。

CozyをWebプラットフォームとして動作させる形態をCozy Webと呼ぶことにします。

HelloWorld

Cozy Webの最小構成アプリケーションとしてHelloWorldを作成し、動作させてみます。

アプリケーション

最小構成のアプリケーションなので、index.htmlのみを配備します。以下のindex.htmlを用意します。

<html>
<head>
<title>Hello World!</title>
</head>
<body>
Hello World!
</body>
</html>

配備

cozyを起動するディレクトリにwebappsディレクトリを作成します。

ここにアプリケーションのホームとなるディレクトリHelloWorldを作成します。

このHelloWorldディレクトリに前述のindex.htmlを作成すればOKです。

起動

Cozyをwebコマンドで起動するとWebプラットフォームとして起動します。

$ cozy web

実行

curlコマンドによってローカルホストの8080ポート上の /web/HelloWorld/index.html を取得します。

/web はCozy上のWebアプリケーションのホームです。その配下のHelloWorldが、先程作成したディレクトリHelloWorldに対応するもので、ディレクトリ名がアプリケーション名になっています。

curl -v http://localhost:8080/web/HelloWorld/index.html

取得結果は以下になります。無事登録したHTMLファイルを取得することができました。

-vスイッチが指定されているので、HTTPのプロトコルヘッダーも表示されています。

取得結果は以下になります。無事登録したHTMLファイルを取得することができました。

-vスイッチが指定されているので、HTTPのプロトコルヘッダーも表示されています。

> GET /web/HelloWorld/index.html HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.76.1
> Accept: */*
> 
< HTTP/1.1 200 OK
< Date: Sun, 27 Feb 2022 04:12:53 GMT
< Content-Type: text/html;charset=utf-8
< Cache-Control: public,max-age=86400
< Expires: Mon, 28 Feb 2022 04:12:59 GMT
< Content-Length: 337
< Server: Jetty(9.4.38.v20210224)
< 
<html>
<head>
<title>Hello World!</title>
</head>
<body>
Hello World!
</body>
</html>

まとめ

今回はモデル駆動開発向けのWebプラットフォームCozy Webを紹介しました。

ごく簡単なWebアプリケーションをHelloWorldとして配備して実行することが確認できました。

次回以降Cozy Webの機能を紹介していく予定です。

諸元

Cozy
0.0.3

2022年1月31日月曜日

Cozy

Modegramming Styleブログではモデリングとプログラミングの一体化を指向したModegrammingというコンセプトを提唱しており、その実現のための技術体系としてSimpleModelingを整備しています。

プロダクト

ちょうど一年前の記事「SimpleModeling」では、このSimpleModelingの技術体系の構成要素である以下の4つのプロダクトの開発について説明しました。ちょうど一年前の記事「SimpleModeling」では、このSimpleModelingの技術体系の構成要素である以下の4つのプロダクトの開発について説明しました。

SmartDox
文書処理系
SimpleModeler
モデルコンパイラ
Kaleidox
アクション言語
Arcadia
Webフレームワーク

この中で、この一年は特にアクション言語であるKaleidoxの開発を進めてきました。

開発方法論

7月からオブジェクト指向開発方法論の講座を始めました。

UML(Unified Modeling Language)/UP(Unified Process)の基本を押さえつつ、オブジェクト指向開発方法論の現在地を整理する目的です。

その上で、最終的には上記のプロダクトを活用したモデル駆動開発と連携できればと考えています。

Cozy

Kaleidoxの開発も順調に進んできたので、Kaleidox, SmartDox, SimpleModeler, Arcadiaを統合した、モデル駆動開発のワークベンチとして新たなプロダクトCozyを立ち上げました。

モデルコンパイラにアクション言語、文書処理系、Webフレームワークを統合し連携することでより有効な活用を行うことができるでしょう。

状態機械図

Kaleidox, SmartDox, SimpleModeler, Arcadiaを統合することにより、KaleidoxからSimpleModelerのモデル生成機能などを直接使用できるようになりました。

今回はSimpleModelerの持つ状態機械図生成機能を使ってKaleidox上の状態機械モデルの状態機械図を生成してみます。

状態機械

まず「Kaleidox/状態機械:ヒストリー」で使用した状態機械の状態機械図を生成します。

以下のモデルでは状態機械purchaseを定義しています。

* event
event=[{
    name="confirm"
  },{
    name="reject"
  },{
    name="delivered"
  },{
    name="cancel"
  },{
    name="suspend"
  },{
    name="resume"
}]
* statemachine
statemachine={
  name="purchase"
  state=[{
    name=INIT
    transition=[{
      to=running
    }]
  },{
    name=canceled
    transition=[{
      to=FINAL
    }]
  },{
    name=suspended
    transition=[{
      guard=resume
      to=HISTORY
    }]
  }]
  statemachine=[{
    name="running"
    state=[{
      name=applying
      transition=[{
        to=confirming
      }]
    },{
      name=confirming
      transition=[{
        guard=confirm
        to=confirmed
      },{
        guard=reject
        to=rejected
      }]
    },{
      name=confirmed
      transition=[{
        to=delivering
      }]
    },{
      name=rejected
      transition=[{
        to=FINAL
      }]
    },{
     name=delivering
      transition=[{
        guard=delivered
        to=delivered
      }]
    },{
      name=delivered
      transition=[{
        to=FINAL
      }]
    }]
    transition=[{
      guard=cancel
      to=canceled
    },{
      guard=suspend
      to=suspended
    }]
  }]
}

statemachine-diagram関数で上記の状態機械purchaseから状態機械図を生成します。

cozy> statemachine-diagram 'purchase
Image

画像をviewコマンドで表示します。Kaleidox上で定義した状態機械の状態機械図として表示することができました。

cozy> :view

エンティティ

次は「Kaleidox状態機械/エンティティの状態遷移」で使用した状態機械を持つエンティティです。

エンティティsalesorderが状態機械statusを持っています。

* event
event=[{
    name="confirm"
  },{
    name="reject"
  },{
    name="delivered"
  },{
    name="cancel"
  },{
    name="suspend"
  },{
    name="resume"
}]
* entity
** salesorder
*** features
table=salesorder
*** attributes
| Name | Type  | Multiplicity |
|------+-------+--------------|
| id   | token |            1 |
| price| int   |            1 |
*** statemachines
**** status
state=[{
  name=INIT
  transition=[{
    to=running
  }]
},{
  name=canceled
  transition=[{
    to=FINAL
  }]
},{
  name=suspended
  transition=[{
    guard=resume
    to=HISTORY
  }]
}]
statemachine=[{
  name="running"
  state=[{
    name=applying
    transition=[{
      to=confirming
    }]
  },{
    name=confirming
    transition=[{
      guard=confirm
      to=confirmed
    },{
      guard=reject
      to=rejected
    }]
  },{
    name=confirmed
    transition=[{
      to=delivering
    }]
  },{
    name=rejected
    transition=[{
      to=FINAL
    }]
  },{
   name=delivering
    transition=[{
      guard=delivered
      to=delivered
    }]
  },{
    name=delivered
    transition=[{
      to=FINAL
    }]
  }]
  transition=[{
    guard=cancel
    to=canceled
  },{
    guard=suspend
    to=suspended
  }]
}]

statemachine-diagram関数で上記のエンティティpurchaseが持っている状態機械statusの状態機械図を生成します。

cozy> statemachine-diagram 'salesorder
Image

画像をviewコマンドで表示します。こちらもKaleidox上で定義した状態機械の状態機械図として表示することができました。

状態機械モデルとしては前出のpurchaseと同じなので、同じ状態機械図になっています。

cozy> :view

まとめ

Kaleidoxの開発もいい感じに進み、Cozyの枠組みの中でSimpleModelerと連携して状態機械図を生成することができるようになりました。

今年はCozyの各種機能の連携を強化し、効果的なモデル駆動開発の環境づくりに取り組んでいきたいと思います。

諸元

Cozy
0.0.2