JQTouchを使ってみた。

以前jqueryMobileを使ってみたので、今度はJQTouchを使ってみた。
印象としては、JQTouchは拡張性があって、こちらの方が使いやすそう。
UIはjqueryMobileの方が強そうだけど。

使ってみたExtensionは以下。

jqt.autotitles.js

リンク名をそのままタイトルに自動挿入してくれるもの。

jqt.floaty.js

フロートウィンドウを表示するもの。

jqt.location.js

GPS情報を取得するもの。

jqt.offline.js

オフラインでWEBページを表示するもの。
html5から使用できるローカルストレージにキャッシュするユーティリティ。

TouchScroll for JQTouch

タッチでスクロールできるようにするもの。
jqt.scroll.jsというのもあるけど、こちらの方が使えそう。
ただ、スクロールしたい要素を追加したときに再度イベントリスナーに登録しないといけないので、追加時に自動的にイベント登録できるように修正した。
↓↓修正内容↓↓
var scrollers = [];
$('.scroll').each(function(scroller) {
	scrollers.unshift(new TouchScroll(this, {elastic: true}));
});
//追加対象DIVのclass属性に「holdScroll」をつける。
$('.holdScroll').live('pageAnimationStart', function(e, data) {
	if(data.direction != 'out') {
		return;
	}
	$(this).find('.scroll').each(function() {
		scrollers.shift();
	});
});
$('.holdScroll').live('pageAnimationEnd', function(e, data) {
	if(data.direction != 'in') {
		return;
	}
	$(this).find('.scroll').each(function() {
		scrollers.unshift(new TouchScroll(this, {elastic: true}));
	});
});
↑↑修正内容↑↑


JQTouch Extensionの作り方も分かったので、自分もExtensionを作ってみた。

jqt.swipe.js

スワイプで別ページに遷移できるようにするもの。
//Swipe時のイベントを登録する。
//JQTocuch.goTo()は、存在する要素のみなので、拡張する。	
if ($.jQTouch) {
    $.jQTouch.addExtension(function SwipeExtension(jQTouch) {
    	var newSwipePageCount = 0,
    		history = new Object(),
    		isStart = false,
    		siwpeSelector = 'div.touch';
    	
    	/**
    	 * スワイプイベントを登録する。<br>
    	 */
    	function setUpSwipe() {
    	    $(siwpeSelector).swipe(swipePageChange);
    	}

    	/**
    	 * スワイプしてページ遷移する。
    	 * @param e	イベント
    	 * @param data イベントデータ
    	 */
    	var swipePageChange = function(e, data) {
    		if(isStart) {
    			return;
    		}
    		isStart = true;
    		
    	    var target = $(e.target);
    	    if(data.direction == 'left') {
    	        var swipeLeftHref = target.attr('swipe-left-href');
    	        if(!swipeLeftHref) {
    	    	    isStart = false;
    	        	return false;
    	        }
    	        goToPage(swipeLeftHref, 'slide');
    	    } else if(data.direction == 'right') {
    	        var swipeRightHref = target.attr('swipe-right-href');
    	        if(!swipeRightHref) {
    	    	    isStart = false;
    	        	return false;
    	        }
    	        goBackPage(swipeRightHref, 'slide');
    	    }
    	    
    	    isStart = false;
    	    
    	    /**
    	     * 指定のページに戻り遷移する。
    	     * @param href 遷移先のURL
    	     * @param animation アニメーション
    	     */
    	    function goBackPage(href, animation) {
    	    	if(!href || typeof(href) != 'string') {
    	    		return false;
    	    	}
    	    	
    	    	var newHref = href;
    	    	if(href.substring(0, 1) != '#') {
    	    		newHref = '#' + createPageElement(href);
    	    	}
    	    	jQTouch.goTo(newHref, animation, true);
    	    }
    	    
    	    /**
    	     * 指定のページに遷移する。
    	     * @param herf 遷移先のURL
    	     * @param animation アニメーション
    	     */
        	function goToPage(href, animation) {
    	    	if(!href || typeof(href) != 'string') {
    	    		return false;
    	    	}
    	    	
    	    	var newHref = href;
    	    	if(href.substring(0, 1) != '#') {
    	    		newHref = '#' + createPageElement(href);
    	    	}
    	    	jQTouch.goTo(newHref, animation);
    	    }

        	/**
        	 * ページの要素を新たに作成する。
        	 * @param 要素のURL
        	 * @return 新たに作成されたページのID
        	 */
    	    function createPageElement(href) {
    	    	if(history[href]) {
    	    		return history[href];
    	    	}
    	    	
    	    	var newSwipePageId;
    	    	// ページの作成完了まで行うので、同期して通信する。
    	    	$.ajax({
    	    		type: 'GET',
        			url: href,
        			dataType: 'html',
        			async: false,
        			success : function(data, textStatus) {
    	    			newSwipePageId = 'swipe-page-' + (++newSwipePageCount);
    	    			$data = $(data);
    	    			$data.attr('id', newSwipePageId);
    	    			$data.appendTo($('#jqt'));
    	    			history[href] = newSwipePageId;
    	    		},
    	    		error : function(data, textStatus, errorThrown) {
    	    			console.log('error handle');
    	    		}
    	    	});
    	    	return newSwipePageId;
    	    }
    	    
    	};
	    
    	return setUpSwipe();
    });
}

使い方は、下記の1行を追加するだけ。

<script src="../jqtouch/extensions/jqt.swipe.js" type="text/javascript" charset="utf-8"></script>

んで、HTML側はこう。

<div class="touch" swipe-left-href="../prevpage.html" swipe-right-href="../nextpage.html">
..コンテンツ
</div>

そうすると、DIV上で、スワイプイベントを検知して、画面遷移する。
ただ、ページ読み込みに時間がかかるとあまりよろしくない動きをするので、処理を再検討中。。。
他にもいろいろ作ってみたいなー。

現在は、TouchScroll for JQTouchを拡張して、最下部で自動ロードをする拡張JSを作成中。

JQTouch本家サイト

jqueryMobileのα版を使ってみた。

現在スマートフォン用のサイトを作るために、簡単にデザイン変更が可能なJqueryMobileがあったので、使ってみた。

下記のように、サイトに基づいて作ってみた。
あと、こちらのまとめサイトも参考にさせていただいた。

<!DOCTYPE HTML>
<html lang="ja">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<head>
<title>Jquery Mobile Test</title>
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.0a2/jquery.mobile-1.0a2.min.css" />
<script src="http://code.jquery.com/jquery-1.4.4.min.js"></script>
<script src="http://code.jquery.com/mobile/1.0a2/jquery.mobile-1.0a2.min.js"></script>
</head>
<body>
<div data-role="page" data-theme="d">

    <div data-role="header">
        <h1>title</h1>
    </div>

    <div data-role="content">
        <ul data-role="listview" data-inset="true" data-theme="c" data-dividertheme="b">
            <li data-role="list-divider">outer-test-links</li>
            <li><a href="./jq-mobile-test-2.html">test-link</a></li>
            <li><a href="./jq-mobile-test.html">my-link</a></li>
        </ul>
        <ul data-role="listview" data-inset="true" data-theme="c" data-dividertheme="b">
            <li data-role="list-divider">inner-test-links</li>
            <li><a href="#inner-test">test-link</a></li>
        </ul>
    </div>
    
    <div data-role="footer">
        <h4>footer</h4>
    </div>

</div>

</body>
</html>

HTMLの属性にJqueryMobile特有のものを記述すると、それを解釈して、デザインをスマートフォン用に変換してくれるようだ。

使っていくうちに1点問題があった。
同じページに遷移すると、HTMLが壊れるという問題。。

JqueryMobileのソースを追っていくと、下記の部分が問題があることが分かった。

if(transition && (transition !== 'none')){	
	$pageContainer.addClass('ui-mobile-viewport-transitioning');
	// animate in / out
	from.addClass( transition + " out " + ( back ? "reverse" : "" ) );
	to.addClass( $.mobile.activePageClass + " " + transition +
		" in " + ( back ? "reverse" : "" ) );
	
	// callback - remove classes, etc
	to.animationComplete(function() {
		from.add( to ).removeClass(" out in reverse " + $.mobile.transitions.join(' ') );
		from.removeClass( $.mobile.activePageClass );
		loadComplete();
		$pageContainer.removeClass('ui-mobile-viewport-transitioning');
	});
}
else{
	from.removeClass( $.mobile.activePageClass );
	to.addClass( $.mobile.activePageClass );
	loadComplete();
}

3200行目あたり。

transitionは遷移時の切り替えActionをしている部分。
fromは遷移元のページ(or要素)、toは遷移先のページ(or要素)。

この処理は、fromの要素を消して、切り替えActionが指定されている場合は、その切り替えActionでtoの要素を表示するというもの。
つまり、fromの要素とtoの要素が同じであれば、最終的に何も表示されない。。

そこで、下記のように変更した。

if(transition && (transition !== 'none')){	
	$pageContainer.addClass('ui-mobile-viewport-transitioning');
	// animate in / out
	from.addClass( transition + " out " + ( back ? "reverse" : "" ) );
	to.addClass( $.mobile.activePageClass + " " + transition +
		" in " + ( back ? "reverse" : "" ) );
	
	// callback - remove classes, etc
	to.animationComplete(function() {
		from.add( to ).removeClass(" out in reverse " + $.mobile.transitions.join(' ') );
		
		// chenged by ochi0218 start
//		from.removeClass( $.mobile.activePageClass );
		if(to.attr('id') != from.attr('id')) {
			from.removeClass( $.mobile.activePageClass );
		}
		// chenged by ochi0218 end

		loadComplete();
		$pageContainer.removeClass('ui-mobile-viewport-transitioning');
	});
}
else{
	from.removeClass( $.mobile.activePageClass );
	to.addClass( $.mobile.activePageClass );
	loadComplete();
}

これで、とりあえず表示されるようになった。

早く安定版でないかなー。

Seasar2での認証の方法

Seasar2を使用している場合、認証の方法をどのように実装しようか迷う。
今までは、Interceptorで認証を実装していたが、Validatorの方が先に動作するために、正常な認証は出来ない。。
Filter、RequestProcessor等で実装も考えたが、やはり楽に(DIの恩恵を受けれて)実装できるInterceptorの方がよい!
そんな時に、sastruts-extensionを知った。
これを使用すれば、Validatorが動作する前に、認証のロジックを動作させることが可能になった。

実装方法は以下。

まず、認証のロジックを作る。

AuthenticationProxy.java

public class AuthenticationProxy implements ActionProxy {

    /** 認証ロジック */
    @Resource
    protected AuthenticationLogic authenticationLogic;

    /**
     * {@inheritDoc}
     */
    @Override
    public String execute(ProxyChain proxyChain) throws Exception {
        if (authenticationLogic.isLogined()) {
            return proxyChain.invoke();
        }
        return "/login/";
    }
}

proxyパッケージの配下に作ること。
ActionProxyをimplementsして、executeメソッド内で、認証を実装すればよい。認証が通れば、ProxyChain#invokeを返し、通らなければ、ログイン画面のパスを返す。

これらをDIするために、下記の設定ファイルを変更する。

creator.dicon

<component class="jp.ardito.seasar.struts.creator.ProxyCreator"/>

ProxyCreatorのコンポーネント定義を追加。

customizer.dicon

<component name="actionCustomizer" class="org.seasar.framework.container.customizer.CustomizerChain">
<initMethod name="addAspectCustomizer">
  <arg>"aop.traceInterceptor"</arg>
</initMethod>
<initMethod name="addAspectCustomizer">
  <arg>"actionMessagesThrowsInterceptor"</arg>
  <arg>true</arg>
</initMethod>
<initMethod name="addCustomizer">
  <arg>
    <component class="org.seasar.framework.container.customizer.TxAttributeCustomizer" />
  </arg>
</initMethod>
<initMethod name="addCustomizer">
  <arg>
    <component class="jp.ardito.seasar.struts.customizer.A3ActionCustomizer">
      <initMethod name="addGlobalProxyClass">
        <arg>@apps.proxy.AuthenticationProxy@class</arg>
      </initMethod>
    </component>
  </arg>
</initMethod>
</component>
<component name="proxyCustomizer" class="org.seasar.framework.container.customizer.CustomizerChain"/>

A3ActionCustomizerの設定と、proxyCustomizerのコンポーネント定義を追加。

struts-config.xml

<controller
    maxFileSize="1024K"
    bufferSize="1024"
    processorClass="jp.ardito.seasar.struts.action.A3RequestProcessor"
    multipartClass="org.seasar.struts.upload.S2MultipartRequestHandler"/>

認証のロジックを実行させるために、A3RequestProcessorに変更。

実際のAction。

IndexAction.java

public class IndexAction {

    @Proxy(type = ProxyType.APPEND, proxy = AuthorizationProxy.class)
    @Execute(validator = false)
    public String execute1() {
        ...
    }

    @Proxy(type = ProxyType.DEFAULT)
    @Execute(validator = false)
    public String execute2() {
        ...
    }

    @Proxy(type = ProxyType.NONE)
    @Execute(validator = false)
    public String execute3() {
        ...
    }

    @Proxy(type = ProxyType.OVERRIDE, proxy = AuthorizationProxy.class)
    @Execute(validator = false)
    public String execute4() {
        ...
    }
}

適用方法は、Proxyアノテーションを使う。
デフォルトは、ProxyType.DEFAULTなので、記述しなければ、適用される。
Proxyアノテーションについてはサイトを参照のこと。

これで、Validatorの前に実行される認証のロジックが出来る。
なんて簡単!!!

最適なチーム構成を考えてみた

各プロジェクトで最適なチームをどのように組めばよいか考えてみた。
各プロジェクトの最適化と、新人の育成に観点を置いた。

登場人物は下記の人物とし、プロジェクトは3つ存在するとする。

初期段階

先行で達人プログラマのみのチームを編成し、プロジェクトAを進める。

次段階

プロジェクトAがある程度進んだ段階で、プロジェクトAから達人プログラマを残り2つのプロジェクトに配置する。
(※プロジェクトAのリソースを残りのプロジェクトは再利用する)
また、新人プログラマプロジェクトAに入れることによって、新人教育も行う。

こうする事で、成熟した共通的なリソースを確保しつつ、普通プログラマのレベルアップにもつながる。
新人プログラマを達人プログラマの中に入れ、ペアプログラミング等をすることにより、飛躍的な能力アップも期待できる。
(※プロジェクトの進行具合によって、新人プログラマの人数を増やすことも考えれる)
また、共有のリソース作成に関わった達人プログラマを各プロジェクトに配置することによって、リソースの共有(ナレッジ共有)もスムーズに行える。


社内で同じ言語を使用していることが多いと思うので、社内独自の共有リソースを作成することが大事だと思う。
また、開発の規約・ソースのフォーマット・システムの構成等々を決めておくと、プロジェクトが変わったときでも楽に入れると思う。

MacBookAirよ当選してくれ

この間iMacを買い損ねて、違うWindowsのパソコンを購入した。
約6年ぶりに買い換えたので、動きのサクサク感に、喜び!!

でも、でも、でもでもでもでもそんなの関係ねぇ!!!
(↑古いけど)

MacBook Air 11インチ欲しい!

良し!これで当選確実!

JMeterの設定

久しぶりにJMeterで負荷テストをすることになった。
設定方法を忘れていたので、方法をまとめておくことにする。

●定数の定義方法

ユーザー定義変数を使用する。
※おもにサーバIPや、コンテキストパスなどを定義しておくとよい。
パラメータを使用するときは${paramName}で使用する。

CSVからのテストパラメータの取得方法

CSVDataSetを使用する。
※variableNamesに、列ごとにパラメータを指定する。
ループ回数が増えるたびに、次の行がパラメータに入る。
ファイル名を相対パスで記述した場合は、jmxファイルからのパスになる。
絶対パスでの記述も可能。

↓下記のように設定を行う↓

●ロジックコントローラ
  • シンプルコントローラ
特に機能は無いが、グループ分けをして見やすくする。
(1スレッドでグループ内をすべて実行する)

※同時に実行するため、順番に実行したい場合は、定数タイマを指定すると良いかも。

  • インターリーブコントローラ
そのグループ内の一つを実行する。
※1スレッドでグループ内の1つを実行して、ループするたびに、順番に実行する。
  • 一度だけ実行されるコントローラ
ループ回数を指定していても、1度しか実行されない。
※ログイン処理等に使用する。
●自動テスト作成

プロキシを使用して、自動的にHTTPリクエストを作成することが出来る。
手順は以下。

1.HTTPプロキシサーバを用意し、ポート番号を指定する。
2.プロキシを「localhost」と、1で指定したポート番号を設定する。
3.テスト対象のページをブラウジングすれば、HTTPリクエストが生成される。

あとは、下記を各サービスに合わせて設定しておく。

  • スレッド数
  • Ramp-up機関
  • ループ回数

なるほど!

Seasar2のHotDeployがどう便利なのかいまいち分からなかったが、やっと理解した。
WTPで毎回ワーキングフォルダをPublishingしにいってほしくない設定を参考に、テンポラリ領域にサーバを立てなければよいのだ!

これで、よりサクサク開発できるぞ!