Chatter.comを試用してみた!

Chatter.comを試用してみた!

一応、自分はアジャイルシステム開発をやっている。
そのシステム開発等の話し合いで使えると感じた。
チャットベースで話し合いを行うと、議事録をとる必要がないし、自分のメモでも他の人の役に立つことがあるからだ。

印象としては、グループ内(会社)とかで使うtwitterっぽい感じ。
でも、twitterより閉じた世界で、社内コミュニケーションを活性化できそう!

Chatterを使ってみて良いと感じた点

  • グループチャットが出来るので、話し合いった結果のログをとっておける。
  • ファイル送信も可能なので、メールで送る必要もない。
  • 検索も可能なので、履歴も参照できる。
  • スマフォアプリもあるので、スマフォでも閲覧できる。
  • ハッシュタグとかも使える。

Chatterを使ってみて悪いと感じた点

  • コメントはメッセージにぶら下がって表示されるが、タイムラインの順序がメッセージ順なので、コメントの新規追加が分かりづらい。
  • リツイート的なものがない。(閉じた世界なので必要ないかも?)

以上、使ってみて思ったのは、すごくいいプロダクトだと思った。
これからも使っていくと思う。

JMockitでSeasar2のInterceptorのテストケースを書く。

テストフレームワークJMockit、WebフレームワークにSeasar2を使用した場合に、Interceptorのテストケースを書いたので、忘れないように書いておく。

TestInterceptor

第一引数に文字列が渡されたら、「test」という文字列に変換するインターセプタ。
public class TestInterceptor implements MethodInterceptor {

    public Object invoke(MethodInvocation invocation) throws Throwable {
        Object[] args = invocation.getArguments();
        if(args.length == 0) {
            return invocation.proceed();
        }
        Object obj = args[0];
        if(obj instanceof String) {
            String str = (String) obj;
            str = "test";
        }
        return invocation.proceed();
    }
}

TestInterceptorTest

JMockitを使って、上記インターセプタのテストケースを書く。
public class TestInterceptorTest {

    /** テスト対象 */
    private final TestInterceptor target = new TestInterceptor();
    
    private String testStr = "";

    /** MethodInvocationモック */
    @Mocked
    private MethodInvocation invocation;
    
    @Test
    public final void testInvoke() {
        new Expectations() {
            {
                invocation.getArguments();
                result = new Object[] {testStr };

                invocation.proceed();
                result = "test";
            }
        };
        target.invoke(invocation);

        assertEquals("test", testStr);
    }
}

こんな感じでよいのか。。
まだまだ勉強中。。

javascriptでの環境依存変数の管理方法

javascriptを使用しているときに、環境に依存する設定(例えばコンテキストパス等)が出てくる。
そんな時に、いろんなソースに環境設定を記述していては、後でソースを見直したときに分かりづらくなる。

今回、そんな環境設定を安全に管理しておくためのJavascriptを考えてみた。

env.js

/**
 * 環境設定<br />
 */
var Env = function() { };

/**
 * 環境設定を初期化する<br />
 */
Env.init =  function(envType) {
	
	// カプセル化し、他からアクセス不可能にする。
	var _env = null,
		ENV_TYPE_DEVELOPMENT = '0',
		ENV_TYPE_STAGING = '1',
		ENV_TYPE_PRODUCT = '2';
		
	// 環境ごとの環境設定
	var _settings = {
		local: {
			contextPath: '/',
			ajaxTimeout: 60000,
			debug: true
		},
		development: {
			contextPath: '/dev',
			ajaxTimeout: 60000,
			debug: true
		},
		staging: {
			contextPath: '/staging',
			ajaxTimeout: 60000,
			debug: false
		},
		product: {
			contextPath: '/product',
			ajaxTimeout: 20000,
			debug: false
		}
	}

	// 環境ごとに読み込む設定を変更する。
	if(envType == ENV_TYPE_DEVELOPMENT) {
		_env = _settings.development;
	} else if(envType == ENV_TYPE_STAGING) {
		_env = _settings.staging;
	} else if(envType == ENV_TYPE_PRODUCT) {
		_env = _settings.product;
	} else {
		_env = _settings.local;
	}
	
	// 各変数のアクセッサー。
	this.getContextPath = function() {
		return _env.contextPath;
	};
	this.getAjaxTimeout = function() {
		return _env.ajaxTimeout;
	};
	this.isDebug = function() {
		return _env.debug;
	};
};

環境設定の呼び出し方法は下記。

<html>
<head>
<script type="text/javascript" src="./env.js"></script>
<script type="text/javascript">
// 開発環境用の設定を行う。
Env.init('0');
// 環境設定変数の取り出し。
alert(Env.getContextPath());
</script>
</head>
<body>
...
</body>
</html>

こうする事で、環境依存の変数を一元管理し、後は、envType等はJSPで変数を取得すればよい。

Jqueryでグラフ表示

仕事でグラフを表示したいという要件が出てきた。
そのため、Jqueryでグラフ表示できるプラグインがないか調査することに。
んで、下記が候補として見つかったプラグイン

グラフの見栄えがよく、様々なグラフを表示できる。
表現方法が多い。(色やプロットの形など)
特に、グラフをドラッグで変更できる点などは、素晴らしい。
APIも整備されていて、実装時に、探しやすい。
見栄えはjqplotより落ちるが、様々なグラフを表示できる。
機能的な部分が多い。ズーム、スクロール、リアルタイム描画、描画対象の変更等。
軽量のスクリプトになっているので、ソースリーディングが容易。

上記2つで作成した所、スマフォでのグラフ表示ということもあり、グラフ内部のスクロールが出来なければならないため、今回は「flot」を採用することにした。

それぞれで作成した方法を記述しておく。

jqplot

まず、WEBアプリ側で下記のようなJSONを返却するものを作成する。(今回は割愛。)

{"tempList":[["0","25"],["1","30"],["2","0"],["3","10"],["4","80"],["5","50"],["6","70"]],"rainList":[["0","18"],["1","13"],["2","12"],["3","13"],["4","9"],["5","4"],["6","3"]]}

そして、javascriptで上記JSONを返却するURLを非同期で呼び出し、結果からグラフを作成する。
JavaScriptは下記。

$.ajax({
    type: 'GET',
    url: '/json/',
    dataType: 'json',
    async: true,
    success: function(data, textStatus) {
        var line1 = data.tempList;
        var line2 = data.rainList;
        var xticks = [[0, '12時'], [1, '13時'], [2, '14時'], [3, '15時'], [4, '16時'], [5, '17時'], [6, '18時']];
        var yticks = [0, 5, 10, 15, 20];
        var yticks2 = [0, 25, 50, 75, 100];
        var jqplot = $.jqplot('plotGraf', [line1, line2], {
            legend: {show:true}, 
            title: '2010/01/01',
            grid: {background:'#f3f3f3', gridLineColor:'#accf9b'},
            series: [
                {label:'気温', markerOptions:{style:'square'}},
                {label:'降水確率', markerOptions:{style:'circle'}, yaxis:'y2axis'}
            ],
            axes: {
                xaxis:{ticks:xticks}, 
                yaxis:{ticks:yticks, tickOptions:{formatString:'%d度'}},
                y2axis:{ticks:yticks2, tickOptions:{formatString:'%d%'}}
            }
        });
    },
    error: function() {
        console.log('error');
    }
});

その結果がこれ。

flot

同じく、WEBアプリ側で下記のようなJSONを返却するものを作成する。(今回は割愛。)

{"tempList":[["1293850800000","25"],["1293854400000","30"],["1293858000000","0"],["1293861600000","10"],["1293865200000","80"],["1293868800000","50"],["1293872400000","70"],["1293876000000","50"],["1293879600000","70"]],"rainList":[["1293850800000","18"],["1293854400000","13"],["1293858000000","12"],["1293861600000","13"],["1293865200000","9"],["1293868800000","4"],["1293872400000","3"],["1293876000000","3"],["1293879600000","3"]]}

そして、javascriptで上記JSONを返却するURLを非同期で呼び出し、結果からグラフを作成する。
JavaScriptは下記。

$.ajax({
    type: 'GET',
    url: '/json/',
    dataType: 'json',
    async: true,
    success: function(data, textStatus) {
        var tempData = data['tempList'];
        var rainData = data['rainList'];
        
        var options = {
            series: { lines: { show: true }, shadowSize: 0 },
            grid: { backgroundColor: '#FFFFFF' },
            xaxes: [
                    {
                    	panRange: [tempData[0][0], tempData[tempData.length-1][0]],
                        min: tempData[0][0],
                        max: tempData.length < 4 ? tempData[tempData.length-1][0] : tempData[3][0],    // 4つぶん出力する
                        tickSize: 3600000,    // 1日ぶんの間隔を空ける
                        tickFormatter: function(val, axis) {
                            var date = new Date(val);
                            return date.getDate() + "日" + date.getHours() + "時";
                        }
                    }
            	],
            yaxes: [
                {
                    panRange: false,
                    tickFormatter: function(val, axis) {
                        return val + '度';
                    }
                },
                {
                    panRange: false,
                    alignTicksWithAxis: "right",
                    position: "right",
                    tickFormatter: function(val, axis) {
                        return val + '%';
                    }
                }
            	],
            pan: {
                interactive: true
            }
        };

        var plot = $.plot($("#placeholder"),
            [
        	{data: data.list, label: '気温', points: { show: true } },
                {data: data.list2, label: '降水確率', points: { show: true }, yaxis: 2},
            ], options);
    },
    error: function() {
        console.log('error');
    }
});

また、flotのグラフスクロールは、スマフォには対応しておらず、少し改良が必要になる。
jquery.flot.navigation.jsの改良。

//147行目
function onDragStart(e) {
	// modify by ochi0218
	// スマートフォンで対応出来るように修正。
//  if (e.which != 1)  // only accept left-click
//  	return false;
//  var c = plot.getPlaceholder().css('cursor');
//  if (c)
//  	prevCursor = c;
//  plot.getPlaceholder().css('cursor', plot.getOptions().pan.cursor);
	if(!$.support.touch) {
        if (e.which != 1)  // only accept left-click
            return false;
        var c = plot.getPlaceholder().css('cursor');
        if (c)
            prevCursor = c;
        plot.getPlaceholder().css('cursor', plot.getOptions().pan.cursor);
	}
	e = (event.changedTouches && event.changedTouches.length) ? event.changedTouches[0] : e;
	// modify by ochi0218
    prevPageX = e.pageX;
    prevPageY = e.pageY;
}

function onDrag(e) {
    var frameRate = plot.getOptions().pan.frameRate;
    if (panTimeout || !frameRate)
        return;
    
    // add by ochi0218
	e = (event.changedTouches && event.changedTouches.length) ? event.changedTouches[0] : e;
    // add by ochi0218

    panTimeout = setTimeout(function () {
        plot.pan({ left: prevPageX - e.pageX,
                   top: prevPageY - e.pageY });
        prevPageX = e.pageX;
        prevPageY = e.pageY;
        panTimeout = null;
    }, 1 / frameRate * 1000);
}

function onDragEnd(e) {
    if (panTimeout) {
        clearTimeout(panTimeout);
        panTimeout = null;
    }

    // add by ochi0218
	e = (event.changedTouches && event.changedTouches.length) ? event.changedTouches[0] : e;
    // add by ochi0218

    plot.getPlaceholder().css('cursor', prevCursor);
    // modify by ochi0218
    // iphoneで正常に動作しないため、コメントアウト。
//    plot.pan({ left: prevPageX - e.pageX,
//               top: prevPageY - e.pageY });
    // modify by ochi0218
}

---------------------------------------------------------------------------------

// 191行目
if (o.pan.interactive) {
	// modify by ochi0218
	// スマートフォンで対応出来るように修正。
//      eventHolder.bind("dragstart", { distance: 10 }, onDragStart);
//      eventHolder.bind("drag", onDrag);
//      eventHolder.bind("dragend", onDragEnd);
	if ($.support.touch) {
            eventHolder.bind("touchstart", { distance: 10 }, onDragStart);
            eventHolder.bind("touchmove", onDrag);
            eventHolder.bind("touchend", onDragEnd);
	} else {
	    eventHolder.bind("dragstart", { distance: 10 }, onDragStart);
	    eventHolder.bind("drag", onDrag);
	    eventHolder.bind("dragend", onDragEnd);
	}
	// modify by ochi0218
}

---------------------------------------------------------------------------------

//316行目
function shutdown(plot, eventHolder) {
    eventHolder.unbind(plot.getOptions().zoom.trigger, onZoomClick);
    eventHolder.unbind("mousewheel", onMouseWheel);
    // modify by ochi0218
//  eventHolder.unbind("dragstart", onDragStart);
//  eventHolder.unbind("drag", onDrag);
//  eventHolder.unbind("dragend", onDragEnd);
	if ($.support.touch) {
        eventHolder.unbind("touchstart", onDragStart);
        eventHolder.unbind("touchmove", onDrag);
        eventHolder.unbind("touchend", onDragEnd);
	} else {
        eventHolder.unbind("dragstart", onDragStart);
        eventHolder.unbind("drag", onDrag);
        eventHolder.unbind("dragend", onDragEnd);
	}
    // modify by ochi0218
    if (panTimeout)
        clearTimeout(panTimeout);
}

plot.hooks.bindEvents.push(bindEvents);
plot.hooks.shutdown.push(shutdown);
}

その結果がこれ。
スマートフォンブラウザでも、PCブラウザでも、ドラッグorスワイプで次の要素を表示させることが出来る。

やっぱり、見栄えはjqplotの方がよいね。。
flotでも、見栄えが近づけれるようにもうちょい頑張ってみよう!

最近のももんが達

うちで飼っているペットのご紹介!

去年の2月に初めて購入した「ももんが」のまりもちゃん(♀)。
そして、去年の11月に購入した「ももんが」のるいちゃん(♂)。


ももんがって、飼ってみて分かったけど、めちゃめちゃなつっこい。
最近、飼い主以外の人には凶暴になってしまったけど。。

んで、2匹はそれぞれ別のケージで飼っているんだけど、ついに1カ月ほど前に2匹をあわせてみることに!
ももんがの繁殖期がそろそろなので、2匹の子供が生まれるのがめちゃ楽しみ!
うちのはフクロモモンガっていう種類のももんがなんだけど、子供が生まれたら、カンガルーみたいな袋で育てるらしい。
想像しただけでかわゆす!!

↓何回か会わせているうちに、2匹の距離が親密になって、LOVELOVEし始めた画像(笑)↓

これからも、ちょこちょこ2匹を追いかけていくので、よろしく☆

yuicompressor-maven-pluginでJS、CSSファイルを圧縮・難読化

Webサイトを作成していて、Javascriptのコードが増えてきた場合、Javascript圧縮&難読化をしたい場合が出てくる。
その処理を、mavenのwar作成処理の中に加えたい。

そんな時に、yuicompressor-maven-pluginというものが便利だったので紹介。

利用方法は以下。

pom.xml

  <pluginRepositories>
    <pluginRepository>
      <name>oss.sonatype.org</name>
      <id>oss.sonatype.org</id>
      <url>http://oss.sonatype.org/content/groups/public</url>
    </pluginRepository>
  </pluginRepositories>

  <build>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <configuration>
          <warSourceExcludes>WEB-INF/classes/**/*.*,WEB-INF/lib/*.jar</warSourceExcludes>
          <!-- 圧縮したものを加える。 -->
          <webResources>
            <resource>
              <directory>${project.build.directory}/minimized</directory>
              <targetPath>/</targetPath>
              <filtering>false</filtering>
            </resource>
          </webResources>
        </configuration>
      </plugin>

      <plugin>
        <groupId>net.alchim31.maven</groupId>
        <artifactId>yuicompressor-maven-plugin</artifactId>
        <executions>
          <execution>
            <goals>
              <goal>compress</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <webappDirectory>${project.build.directory}/minimized</webappDirectory>
          <linebreakpos>-1</linebreakpos> <!-- 1行にまとめる -->
          <encoding>UTF-8</encoding>
          <nosuffix>true</nosuffix> <!-- 圧縮後のファイルに接尾語を付けない。 -->
          <force>true</force>
          <jswarn>false</jswarn>
          <!-- すでに圧縮済みのファイルは除外する -->
          <excludes>
            <exclude>**/*.min.js</exclude>
            <exclude>**/*.min.css</exclude>
          </excludes>
        </configuration>
      </plugin>
    </plugins>
  </build>

その他の機能として、圧縮ファイルを1ファイルにまとめることも可能なようです。
こちらを参考にしてください。

URLDispatcher

WEB+DBPressの第60回にJQueryの特集をしていた。
JQuery好きとしては是非見たいところ。

WEB+DB PRESS Vol.60

WEB+DB PRESS Vol.60

その中に、URLDispatcherというものが紹介されていたので、自分なりに作り変えてみた。
どういうものかと言うと、URLごとに実行するJavascriptのコードを変更するという物。
結構Javascriptって、コードが散らばってたりするので、このJavascriptを1行読み込んで、URLごとに処理を切り出すようにする。


変更を加えた物は以下。

url.dispatcher.js

// 実行するものの定義
dispatcher([
	{
		// 全体の処理。
		path: '.',
		func: function() {
			// 全体に関わる処理を記述する。
		}
	},
	{
		// 「/hoge」で始まるURLでの処理。
		path: '^/hoge',
		func: function() {
			// 「/hoge」配下での処理を記述する。
		}
	}
]);

/**
 * パスによって指定の関数を実行する。
 * @param mappings
 * 	path:現在のパスにマッチする正規表現
 * 	func:実行させたい関数
 */
function dispatcher(mappings) {
	var pathname = location.pathname;

	for(var i = 0, len = mappings.length; i < len; i++) {
		var path = mappings[i]['path'], func = mappings[i]['func'];
		if(path && func) {
			if (pathname.match(new RegExp(path))) {
				func.apply(this);
			}
		}
	}
};