SI屋的(?)JavaScript(jQuery)活用方法
SI屋的なWebシステムの開発において、どこまでJavaScriptを使うのかについては、いくつかパターンがあると思うわけですが。
1. 機能要件では無いし、お金にならないのでいっさい使わないパターン(´д`;)
2. jQueryとdata属性なんかを使えば、宣言的にそれなりに装飾されたものをコストをかけずに作れるので、そのパターン(・ω・)
3. フルJavaScript…っというか、MVVMライブラリを使うだけで無く、GWTみたいなものを使わないとやる気が起きないパターン(・∀・;)
っで、未だに1のパターンも多かったりする一方で、jQueryの登場以降、使い方を考慮することで、SI屋的なシステムでもコストをかけずにUIをリッチにすることも可能になってきたので、その方法について書きたいと思います(`・ω・´)
そもそも
SI屋的なシステム開発における統制の観点から言うと、JavaScriptの記述は他のロジック記述に比べて個人差が出るので*1、担当者個々に処理を書かせたくないという話があるかと思います(´д`;)
また、各html(jsp、aspx)ページ内にJavaScriptの記述が混在すると、メンテナンス的に好ましくないという懸念もあるかと思います。
今回提唱する方法は、上記懸念に対する一定の回答を示しながら、JavaScriptを活用してUIをそれなりにリッチにする方法だと思います(・ω・)
その方法とは、簡単に言えば「data属性の活用と宣言的な記述」です。
なお、以降はjQuery前提での話となります。
data属性
data属性とはなんぞや?、っという人にはググって貰うとして(・∀・;)
要は、data属性を用いて、html内には処理(JavaScript)に必要なパラメータのみを記述することで、htmlからJavaScriptの完全分離をしようというのが今回提唱する案の肝になります。
簡単な例として言えば、
<button type="button" onclick="location.href='/home'">初期値に戻す</button>
の様に処理も含めて記述するのでは無く、
<button type="button" class="link" data-link="/home">初期値に戻す</button>
のように、宣言的な記述にして、処理(JavaScript)をhtmlから分離しよううという話です(・ω・)
宣言的記述
動作を書くのでは無く、それが何をするものであり、必要なパラメータは○○であるということをに記述することで、処理(JavaScript)とhtmlを分離することが出来ます。
その場合、処理(JavaScript)の方はどう書くのかという話になりますが、まず、基本の考え方としては以下の様になります。
- 特定のコンテキスト(id、class、タグの親子関係等)に対して適用する
- プラグイン/ライブラリというレベルでの共通化とは別に、アプリケーション固有のコンテキストに適用する層を用意する
…っと言ってもよく分からない(´д`;)と思うので、前述の
$(document).ready(function() { $(".link").live("click", function() { window.location = $(this).data("link"); return false; }); });
これをどう捕らえるかと言えば、[class=link]という「コンテキスト」に対して処理を適用する、っという風に読んでください。
そして、必要なパラメータ(この例で言えばリンク先の情報:data-link)も宣言的に記述されているので、それを参照して使用する、っという形になります。
このパターンを使用すれば(適用コンテキストの設計は必要ですが)、htmlとJavaScriptの完全分離が出来るようになります(`・ω・´)
また、この様な処理については、jQueryや各種プラグイン、及び他のライブラリとは別に、アプリケーション固有のコンテキスト適用層として分離することで、汎用性と柔軟性のバランスを取ることができると考えます。
html上では下記の様にインクルードするイメージで。
... <!-- 共通層 --> <script src="/js/jquery/jquery-1.5.2.min.js" type="text/javascript"></script> <script src="/js/jquery/jquery-ui-1.8.13.min.js" type="text/javascript"></script> ...その他jQueryのプラグイン、ここまでは共通部品 <!-- アプリケーション固有層 --> <script src="/js/app/common.js" type="text/javascript"></script> <script src="/js/app/link.js" type="text/javascript"></script> <script src="/js/app/partial.js" type="text/javascript"></script> ...アプリケーション固有のコンテキスト適用層、共通部品をどう使うか?、っとい層 ...
サーバアプリケーションのアーキテクチャを例にして見れば、汎用FWが提供する機能とは別に、そのアプリケーション固有のレイヤスーパータイプを用意するような感覚に近い感じでしょうか(・ω・)?
処理パターン
コンテキスト適用層は処理毎に分離して、画面毎に必要なものをインクルードする形でメンテナンスしやすくすることが出来ます。
っということで、以下、処理パターン毎に分離する時の例をいくつか書いてみます。
非同期ロード(asyncload.js)
ポータル的な画面などで複数のWidget的な要素を配置して、それを非同期にロードして見せたいような場合の処理の例です。
$(document).ready(function() { $(".asyncload").each(function(index, e) { var $this = $(e); $.ajax({ type: "GET", url: $this.data("url"), context: $this, success: function(data, textStatus, xhr) { $(this).empty().append(data); }, error: function() { }, complete: function() { }, dataType: "html" }); }); });
[class=asyncload]のコンテキストに対して、data-urlから表示する先のURLを取得し、それのhtmlを非同期に取得して画面を更新する、っという処理です。
この場合の適用コンテキストの例としては、下記の様になります。
<div id="weatherWidget" class="asyncload" data-url="/widget/weather"> <img src="/images/loading.gif"/> </div>
画面の実装者は上記の様なhtmlを記述するだけでよくなり、htmlとJavaScriptの分離、もとい、個々の画面担当者とJavaScript担当者という形での作業の分離が(SI屋的に)出来るようになります(・∀・;)
パーシャルアップデート(partial.js)
もう少し複雑なパターンとして、部分更新(ASP.NETにおけるUpdatePanel的なもの)の処理例を書いてみます。
$(document).ready(function() { var partialUpdate = function(e, name) { var form = e.parents("form"); var url = e.data("url"); if (!url) { url = form.attr("action"); } var target = e.data("target"); if (!target) { target = form.data("target"); } var data = form.serializeArray(); data.push({ name: "_partial", value: name }); $.ajax({ type: "POST", url: url, data: data, context: target, success: function(data, textStatus, xhr) { $(this).empty().append(data); }, error: function() { }, complete: function() { }, dataType: "html" }); }; $("select.partial").live("change", function() { var $this = $(this); partialUpdate($this, $this.attr("name")); }); $("input[type='button'].partial").live("click", function() { var $this = $(this); partialUpdate($this, $this.attr("name")); }); $("a.partial").live("click", function() { var $this = $(this); var name = $this.attr("href") if (name.indexOf("#") == 0) { name = name.slice(1); } partialUpdate($this, name); return false; }); });
解説の前にhtmlも。
<div id="updatePanel"> <form action="/partial" method="POST" data-target="#updatePanel"> <input type="text" name="data1" value=""/> <select name="data2" class="partial"> <option value="1">データ1</option> <option value="2">データ2</option> <option value="3">データ3</option> </select> <input type="button" name="trigger" value="トリガー" class="partial"/> <a href="#link" class="partial">パーシャルリンク</a> <input type="submit" value="更新"/> </form> </div>
この例は、[class=partial]なselect、button、リンクに対する操作に対して、formのaction先にAjaxでデータをPOSTする(応答では
まとめ
data属性と宣言的な記述により、htmlとJavaScriptの完全分離が出来るようになりました(・∀・)
また、このことによりJavaScriptの実装は少数のコンテキスト適用層を作る人に任せることが出来るようになりました。
っということで、SI屋的な開発でも、コストをあまりかけず、品質面での統制も問題無く、Ajax/JavaScriptを使ったそれなりにリッチなWebシステム開発が出来るようになりますね、っという話ですが、どうでしょう(・ω・)?
おまけ
自分の出自はWindowsプログラマ〜バックエンド/サーバ側なので、JavaScript自体それほど好きというわけでも無いですが、なんだかんだ言いつつ、去年はJSのコードを結構書いていたたり、本も読んだりしたので、これも時代でしょうかね(・ω・)?
コアjQuery+プラグイン/jQuery UI 開発実践技法 (Programmer’s SELECTION)
- 作者: Bear Bibeault,Yehuda Katz,吉川邦夫
- 出版社/メーカー: 翔泳社
- 発売日: 2011/08/31
- メディア: 大型本
- 購入: 8人 クリック: 296回
- この商品を含むブログ (24件) を見る
ちなみにこれ、jQuery in Actionの邦訳ね。*2
っで、SI屋的なWebシステム開発におけるJavaScriptの課題としては、自分としては以下のようなものが残っていると思っています。
- JavaScriptコードの静的テストツール、及びCIとの組み合わせ
- jQueryはデフォとして、ユーティリティ、オブジェクトクエリ、バインディング*3、モバイル(スマフォ対応)、テストあたりは何かしらライブラリを使用することになると思うので、自分達なりのそのセットの評価とメンテ
JSPでお手軽マスターページ
穴埋め発掘ネタ。
Smart Web Layout
JSPでもASP.NETのマスターページのように、共通部分はテンプレートを使用してレイアウトしたいよね、っということで。
Javaの場合、この用途にはTilesやSitemeshがあるわけですが(・ω・)
でも、設定ファイルとか使いたくねーし、JSPだけで完結してーし( ゚д゚)、ッ…っというニーズのための、お手軽レイアウトライブラリを作ったので下記で公開。
https://github.com/usausa/Java-Smart-WebLayout
使い方
公開しているサンプルはTomcatプロジェクトになっているので、とりあえず動作確認はできると思いますが。
使用例として見るのは、個別ページ部分のindex.jspとテンプレートのmaster.jsp。
まずindex.jspについて。
<layout:master template="master"> <layout:parameter key="title" value="ホーム"/> <layout:content name="body"> ページ固有のコンテンツ </layout:content> </layout:master>
テンプレートで置換する部分を
また、個別ページからテンプレートにパラメータとして渡したい項目は
次にマスターページのmaster.jspはこんな感じで。
<!doctype html> <html lang="ja"> <head> <meta charset="utf-8"> <title><layout:value key="title"/></title> </head> <body id="body"> <h1><layout:value key="title"/></h1> <div id="main"> <layout:place name="body"/> </div> </body> </html>
個別ページのコンテンツで置換したい部分は
また、
っで、実際にindex.jspの処理が行われると、ページが合成されて出力は下記の様になりますだ(・ω・)
<!doctype html> <html lang="ja"> <head> <meta charset="utf-8"> <title>ホーム</title> </head> <body id="body"> <h1>ホーム</h1> <div id="main"> ページ固有のコンテンツ </div> </body> </html>
共通テンプレートを使ったレイアウトが出来ていますね(・∀・)
タグ一覧
タグの一覧を簡単に解説。
master
個別ページで使用。
このタグ部分がマスターページで置換される。
templateで属性マスターページとして使うJSPを指定。
content
個別ページで使用。
マスターページ内に合成されるコンテンツを記述。
name属性でマスターページにおけるプレースホルダの場所を指定。
parameter
個別ページで使用。
マスターページに引き渡すパラメータをkey属性とvalue属性で指定。
place
マスターページで使用。
個別ページで記述したcontentの内容が合成される部分のプレースホルダを指定。
value
マスターページで使用。
個別ページで指定したparameterの内容を使用。
key属性で指定した内容の出力、またはvar属性が指定された場合は変数への設定。
partial
どこでも使用。
Smart Web Layoutライブラリではweb.xml中で下記の様なパラメータを指定した場合、ビューの記述の省略が可能。
<context-param> <param-name>smart.web.layout.PREFIX</param-name> <param-value>/WEB-INF/views/</param-value> </context-param> <context-param> <param-name>smart.web.layout.SUFFIX</param-name> <param-value>.jsp</param-value> </context-param>
この設定であれば、template="master"のような記述で実際に使用されるjspは/WEB-INF/views/master.jspとなる。
partialタグは上記に対応したinclude処理のSmart Web Layout版。
っというわけで、このレイアウトタグライブラリはお手軽&ピュアJSPなのでその他のライブラリと組み合わせ自由という感じなのですが、どうでしょう(・ω・)?
Spring MVCでグループヴァリデーションしたい時
JSR-303のjavax.validation.Validatorヴァリデーションのグループ化に対応していて、例えば同じモデルに対して新規作成時と更新時に違う検証を行う事が可能ですが(`・ω・´)
しかし、javax.validation.Validアノテーションにはグループ化の指定方法がないという…。
なので、Spring MVCを使ってControllerメソッドの引数に対してヴァリデーションを行うときに、グループの指定が出来ないという結果に(´・ω・`)
本家ではjavax.validation.Validとは別のアノテーションを用意してこの問題に対応するっぽいですが、とりあえずそれまでの対応方法について(・ω・)
基本方針
javax.validation.Validとは別にグループの指定ができるアノテーションを用意します。
次に、Spring MVCで使用する際のMethodInterceptorとヴァリデーション用のコンポーネントを用意します。
アノテーション
こんな感じでjavax.validation.Validにグループを追加してものを用意します。
@Target({ METHOD, FIELD, CONSTRUCTOR, PARAMETER }) @Retention(RUNTIME) public @interface ValidGroups { Class<?>[] value(); }
ヴァリデーション用コンポーネント
javax.validation.Validatorを使ってヴァリデーションを行い、org.springframework.validation.Errorsを設定するコンポーネントとして以下のようなものを用意します。
import java.util.Set; import javax.validation.ConstraintViolation; import javax.validation.Path; import javax.validation.Validator; import javax.validation.Path.Node; import javax.validation.groups.Default; import org.springframework.validation.Errors; public class GroupValidator { private static final Class<?>[] DEFAULT_CLASSES = new Class<?>[] {Default.class}; private Validator validator; public void setValidator(final Validator validator) { this.validator = validator; } public boolean isValid(final Errors result, final Object object, final Class<?>... classes) { Set<ConstraintViolation<Object>> violations = validator.validate(object, isDefaultValidation(classes) ? DEFAULT_CLASSES : classes); for (ConstraintViolation<Object> v : violations) { Path path = v.getPropertyPath(); String propertyName = ""; if (path != null) { StringBuilder buffer = new StringBuilder(); for (Node n : path) { buffer.append(n.getName()); buffer.append("."); } propertyName = buffer.toString(); propertyName = propertyName.substring(0, propertyName.length() - 1); } String constraintName = v.getConstraintDescriptor().getAnnotation().annotationType().getSimpleName(); if (propertyName == null || "".equals(propertyName)) { result.reject(constraintName, v.getMessage()); } else { result.rejectValue(propertyName, constraintName, v.getMessage()); } } return violations.isEmpty(); } private boolean isDefaultValidation(final Class<?>[] classes) { if ((classes == null) || (classes.length == 0) || (classes[0] == null)) { return true; } return false; } }
ヴァリデーション用MethodInterceptor
Controllerのメソッドでjavax.validation.Validによるヴァリデーションと同じように書けるように、以下の様なMethodInterceptorを用意。
import java.lang.annotation.Annotation; import java.lang.reflect.Method; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.springframework.validation.Errors; public class ValidGroupsInterceptor implements MethodInterceptor { private GroupValidator groupValidator; public void setGroupValidator(final GroupValidator groupValidator) { this.groupValidator = groupValidator; } @Override public Object invoke(final MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); Object[] arguments = invocation.getArguments(); Class<?>[] parameterTypes = method.getParameterTypes(); Annotation[][] annotations = method.getParameterAnnotations(); for (int i = 0; i < arguments.length; i++) { if (annotations[i] == null) { continue; } for (Annotation annotation : annotations[i]) { if (!(annotation instanceof ValidGroups)) { continue; } if ((i < arguments.length - 1) && (Errors.class.isAssignableFrom(parameterTypes[i + 1]))) { groupValidator.isValid((Errors)arguments[i + 1], arguments[i], ((ValidGroups)annotation).value()); } } } return invocation.proceed(); } }
設定
っで、Springの設定としてはこんな感じで、Controllerのメソッドに対してValidGroupsInterceptorを適用するように設定。
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"> </bean> <bean id="groupValidator" class="smart.extension.spring.validator.GroupValidator"> <property name="validator" ref="validator" /> </bean> <bean id="validGroupsInterceptor" class="smart.extension.spring.validator.ValidGroupsInterceptor"> <property name="groupValidator" ref="groupValidator" /> </bean> <aop:config proxy-target-class="true"> <aop:advisor pointcut="execution(* sample..*Controller.*(..))" advice-ref="validGroupsInterceptor" /> </aop:config>
サンプル
使用例としては以下の様なモデルとController、jspになります。
public class MultiViewModel implements Serializable { private static final long serialVersionUID = 1L; public interface Rule1 {} public interface Rule2 {} @NotEmpty(groups = {Rule1.class}) private String data1; @NotEmpty(groups = {Rule2.class}) private String data2; public String getData1() { return data1; } public void setData1(final String data1) { this.data1 = data1; } public String getData2() { return data2; } public void setData2(final String data2) { this.data2 = data2; } }
@Controller @RequestMapping(value = "/sample/basic/multi") public class MultiController extends SampleControllerBase { @RequestMapping(method = RequestMethod.GET) public String index(final ModelMap map) { map.put("model", new MultiViewModel()); return "sample/basic/multi/index"; } @RequestMapping(method = RequestMethod.POST, params = "rule1") public String rule1(@ModelAttribute("model") @ValidGroups(MultiViewModel.Rule1.class) final MultiViewModel model, final BindingResult binding, final ModelMap map) { return "sample/basic/multi/index"; } @RequestMapping(method = RequestMethod.POST, params = "rule2") public String rule2(@ModelAttribute("model") @ValidGroups(MultiViewModel.Rule2.class) final MultiViewModel model, final BindingResult binding, final ModelMap map) { return "sample/basic/multi/index"; } }
<form:form method="post" modelAttribute="model"> <table> <tr> <th>data1</th> <td> <form:input path="data1"/> <form:errors path="data1" cssClass="error" /> </td> </tr> <tr> <th>data2</th> <td> <form:input path="data2"/> <form:errors path="data2" cssClass="error" /> </td> </tr> </table> <input type="submit" name="rule1" value="ルール1で更新"/> <input type="submit" name="rule2" value="ルール2で更新"/> </form:form>
これで、画面の[ルール1で更新]ボタンを押下するとdata1に対するチェックのみが、[ルール2で更新]ボタンを押下するとdata2に対するチェックのみが実行されることを確認できます(・∀・)
…などと、書きかけのネタを発掘したのでリサイクル投稿(・ω・;)
Spring MVCも、3.1になると色々改善されるっぽいんだけどね〜。
Spring MVCというか、JSR-303でメソッドを使用したヴァリデーション
メンバaの値が条件Aでメンバbの値が条件Bだったら、メンバcの値は条件Cの時のみ許可する、みたいな複雑な条件を行う時の話について。
メソッドで判定処理を書けば簡単に済む場合の、メソッドを使ったヴァリデーターの作成方法。
実装
アノテーションと実装はこんな感じのものを用意。
@Target({ METHOD, FIELD, TYPE, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) @Retention(RUNTIME) @Constraint(validatedBy = ValidationMethodValidator.class) @Documented public @interface ValidationMethod { String message() default "{smart.extension.spring.validator.ValidationMethod.message}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; String method(); String target() default ""; @Target({ METHOD, FIELD, TYPE, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) @Retention(RUNTIME) @Documented @interface List { ValidationMethod[] value(); } }
public class ValidationMethodValidator implements ConstraintValidator<ValidationMethod, Object> { private String method; private String target; @Override public void initialize(final ValidationMethod constraintAnnotation) { method = constraintAnnotation.method(); target = constraintAnnotation.target(); } @Override public boolean isValid(final Object value, final ConstraintValidatorContext context) { boolean isValid = false; if (value != null) { try { Method m = value.getClass().getMethod(method); isValid = (Boolean)m.invoke(value); } catch (Exception e) { throw new WrapRuntimeException(e); } } if (!isValid) { context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()).addNode(target).addConstraintViolation() .disableDefaultConstraintViolation(); } return isValid; } }
サンプル
typeが1か2だったらdataは空以外を、typeが1か2以外だったらdataは空を要求するような場合の記述はこんな感じで。
@ValidationMethod(method = "validateData", target = "data") public static class Data { private int type; private String data; ... public boolean validateData() { if ((type == 1) || (type == 2)) { return !StringUtil.isEmpty(data); } else { return StringUtil.isEmpty(data); } } }
targetは、前回のFieldCompareValidatorの話に同じでどのメンバのエラーにするかの設定だす(・ω・)
Spring MVCというか、JSR-303で値の比較を行うヴァリデーション
ユーザ登録における、[パスワード]と[パスワードの再確認]欄の一致とかに使うやつを自分で用意する方法について(・ω・)
実装
アノテーションと実装はこんな感じのものを用意。
@Target({ TYPE, ANNOTATION_TYPE }) @Retention(RUNTIME) @Constraint(validatedBy = FieldCompareValidator.class) @Documented public @interface FieldCompare { String message() default "{smart.extension.spring.validator.FieldCompare.message}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; String field(); String compareTo(); @Target({ TYPE, ANNOTATION_TYPE }) @Retention(RUNTIME) @Documented @interface List { FieldCompare[] value(); } }
public class FieldCompareValidator implements ConstraintValidator<FieldCompare, Object> { private String fieldFieldName; private String compareToFieldName; @Override public void initialize(final FieldCompare constraintAnnotation) { this.fieldFieldName = constraintAnnotation.field(); this.compareToFieldName = constraintAnnotation.compareTo(); } @Override public boolean isValid(final Object value, final ConstraintValidatorContext context) { final BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(value); final Object fieldValue = wrapper.getPropertyValue(this.fieldFieldName); final Object compareToValue = wrapper.getPropertyValue(this.compareToFieldName); boolean isValid = ((fieldValue == null) && (compareToValue == null)) || ((fieldValue != null) && (fieldValue.equals(compareToValue))); if (!isValid) { context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()).addNode(this.fieldFieldName).addConstraintViolation() .disableDefaultConstraintViolation(); } return isValid; } }
サンプル
例えば検証対象のモデルに下記の様な感じで設定。
@FieldCompare(field = "newPasswordConfirm", compareTo = "newPassword") public class PasswordViewModel implements Serializable { @NotEmpty private String newPassword; private String newPasswordConfirm; ... }
<ul> <li> <form:password path="newPassword"/> <form:errors path="newPassword" cssClass="error" /> </li> <li> <form:password path="newPasswordConfirm"/> <form:errors path="newPasswordConfirm" cssClass="error" /> </li> </ul>
ポイントは、入力欄のエラーとして扱う方のメンバをfieldで指定すること。
この場合、パスワードが一致しなかった時のエラーはnewPasswordConfirmの方に表示されます。
FieldCompareValidatorのcontext.buildConstraintViolationWithTemplate()のあたりでやっているのがこの設定です(・ω・)
これ、どっかのサイトを参考にしたものですが、出典は忘れました(・∀・;)
CentOS上でMonoを使ってRazorを動かす
最近はCentOS(RHEL)でもMonoのリポジトリを設定すれば、yumで最新版をインスコできるとか、楽に環境設定できるようになっているので、その手順メモ(・∀・)
とりあえずCentOSが用意できているところからはじめます。*1
Monoのインストール
まずはMonoのリポジトリを追加(・ω・)
> vi /etc/yum.repos.d/mono.repo
内容は下記で。
[mono] name = novell-mono baseurl=http://ftp.novell.com/pub/mono/download-stable/RHEL_5/ enabled=0 gpgcheck=0
ついでMonoのインスコですが、とりあえずこのあたりを入れておけば事足りる(・ω・)?
yum --enablerepo=mono install mono-addon-core mono-addon-data mono-addon-web mono-addon-devel mono-addon-winforms mono-addon-wcf mono-addon-libgdiplus0 mono-addon-extras mod_mono-addon
これだけでインスコ完了、簡単ですね(・∀・)
バージョンの確認しておきましょうか。
> /opt/novell/mono/bin/mono -V Mono JIT compiler version 2.10.2 (tarball Mon Apr 18 18:57:39 UTC 2011) Copyright (C) 2002-2011 Novell, Inc and Contributors. www.mono-project.com TLS: __thread SIGSEGV: altstack Notifications: epoll Architecture: x86 Disabled: none Misc: debugger softdebug LLVM: supported, not enabled. GC: Included Boehm (with typed GC and Parallel Mark)
RazorなWebアプリを動かす
まず、アプリケーション用のディレクトリを作成(・ω・)
> mkdir /var/www/sample
ついでApacheの設定ですが、mod_monoの設定自体は/etc/httpd/conf.d/mod_mono.confに設定済みになっているので、アプリケーションの設定だけを追加します。
> vi /etc/httpd/conf/httpd.conf
最後に下記の内容を追加。
Alias /sample/ "/var/www/sample" AddMonoApplications sample "/sample:/var/www/sample" MonoServerPath sample "/opt/novell/mono/bin//mod-mono-server4" MonoSetEnv sample MONO_IOMAP=all <Location "/sample"> MonoSetServerAlias sample SetHandler mono </Location>
環境設定はこんなんでOK。
次はアプリケーションですが、これはVisual Studio 2010で作成することにして。
ASP.NET MVC 3 Web アプリケーションを作成して、ビューエンジンにRazorを選択。
参照設定に、いかのうち足りないものを追加し、プロパティでローカルコピーをTrueに設定します。
- System.Web.Helpers.dll
- System.Web.Mvc.dll
- System.Web.Razor.dll
- System.Web.WebPages.Deployment.dll
- System.Web.WebPages.dll
- System.Web.WebPages.Razor.dll
あとは、プロジェクトをビルドしたものをCentOS上の/var/www/sampleにデプロイすれば動作確認できますよヽ(・∀・)ノ
ヘッダも確認してみるとこんな感じになっていて。
HTTP/1.1 200 OK Date: Tue, 03 May 2011 08:17:02 GMT Server: Apache/2.2.3 (CentOS) X-AspNetMvc-Version: 3.0 X-AspNet-Version: 4.0.30319 Content-Length: 1215 Cache-Control: private X-Powered-By: Mono Connection: close Content-Type: text/html; charset=utf-8
ApacheとMonoで動作していることが確認できました。
しかし、これだけ簡単に環境設定できるようになってくると、なにか実戦投入したくなりますね(・∀・;)
formに複数buttonを配置するときの話、IE6への対処編
こんな風に、formに複数のbuttonを配置したい時があり(・ω・)
<button type="submit" name="update">更新</button> <button type="submit" name="back">戻る</button>
この場合、どちらの処理を行うか切り分けるときには、name属性の値を使いますが。
例えば、Spring MVCであればRequestMappingのparamsに値を設定して。
@RequestMapping(method = RequestMethod.POST, params = "back") public String back(...) { ... }
ASP.NET MVCであれば、ActionMethodSelectorAttribute派生を作って対処しますが。
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)] public class ExistParamaterAttribute : ActionMethodSelectorAttribute { public string Name { get; set; } public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo) { return controllerContext.RequestContext.HttpContext.Request.Form.GetValues( Name ) != null; } }
しかし、IE6では両方のname属性を送ってきやがるという問題があって(#゚Д゚)
なら大丈夫なんですが、デザイン的に
var buttons = document.getElementsByTagName('button'); for(i = 0; i < buttons.length; i++) { if (buttons[i].onclick) continue; buttons[i].onclick = function () { for(j = 0; j < this.form.elements.length; j++) if (this.form.elements[j].tagName == 'BUTTON') { this.form.elements[j].disabled = true; } this.disabled=false; this.value = this.attributes.getNamedItem("value").nodeValue; } }
…などとリサイクルネタ(・ω・)