【JavaScript-MVVM】Knockout ドキュメント翻訳中!

Knockout.日本語ドキュメント

ただいまスコブトでは、MVVM 設計モデルを提供するJavaScript フレームワーク「Knockout」のドキュメント翻訳を行なっております。

Knockout.日本語ドキュメント

なぜかと言ったら、やはり流行って欲しいからです。
JavaScript の MV* フレームワークは、有名なものでも10種類以上あります。
参考:The Top 10 Javascript MVC Frameworks Reviewed
すべてのフレームワークを試したわけではありませんが、私が Knockout をおすすめする理由は、その学習コストの低さと、他のフレームワークとの親和性にあります。

Knockout は強力なバインド機構を提供するフレームワークであり、それ以上でも以下でもありません。バインド機構は、単純に言えば「背後のデータ・モデルの値と画面を同期する仕組み」です。これは MVVM (Model-View-ViewModel) 設計パターンの前提です。MVVM が何たるかについては概念から学ばれることをおすすめしますが、本家チュートリアル を一度試していただければ、バインド機構がもたらすメリットがわかるはずです。

Knockout のバインド機構について理解したらすぐにでも使い始めることができますが、Knockout の優れている点はそれにとどまりません。Knockout はバインド機構以外に提供するものはありませんが、慣れ親しんだ jQuery や クライアントサイド・ルーティングを提供する Sammy.js、オブジェクト指向ライブラリの Base.js など様々なライブラリと組み合わせることで、更にパワフルな開発を行うことができます。

Knockout.日本語ドキュメントでは、今のところドキュメントの翻訳を進行中ですが、今後 Tips の公開や カスタムバインディング を共有できるフォーラムの開発を検討しています。

Posted in: Javascript, Web

EclipseでCSS/Javascriptを自動minimizeする方法

auto minimize css javascript in eclipse

auto minimize css javascript in eclipse

WebサイトやWebサービスを新たに公開する際、
気を配るべきはデザイン性や機能性だけではありません。
せっかく優れたサイトであっても、表示に時間がかかってしまうと
訪れた人は待ちきれずに去ってしまうかもしれません。

サイトの表示速度を向上するためにできることはいくつかあります。

  • コンテンツのファイルサイズを小さくする
  • Webサーバのチューニング
  • プログラムのパフォーマンスチューン

etc…

中でも、意外と無駄が多いのが Javascript や CSS などのソースコード資材です。
丁寧にコメントを書き入れている場合、開発や保守の観点ではとても有用です。
しかし、サイトを訪れた人にとってはメリットがありません。
改行やインデントについても、同じことが言えます。

今回は普段Eclipseを使用されている方向けに、
CSSとJavascriptのソースコードを自動でminimize(省サイズ化)する簡単な方法を紹介します。
内容にもよりますが、Javascript/CSSのファイルサイズを20%~80%削減することができます。

この方法で実現するもの

  • ビルド時に「~.src.js」を自動minimize→ 同じフォルダに「~.js」として保存
  • ビルド時に「~.src.css」を自動minimize→ 同じフォルダに「~.css」として保存
  • それぞれファイルサイズを20%~80%削減

YUICompressorを手に入れる(無料)

この方法ではYUICompressorのminimize機能を利用します。
次の手順で手に入れて下さい。

次のURLにアクセスし、「Version 1.x.xxx(xxxは最新の値)」をクリックしダウンロードします。
http://yuilibrary.com/download/builder/(ダウンロード-ビルドツール)
Download YUI Build Tools

ダウンロードした「builder_1.x.xxx.zip」を解凍すると、「builder」というフォルダが展開されます。
少し階層が深いですが、YUICompressorはその中にあります。
builder > componentbuild > lib > yuicompressor > 【yuicompressor-2.4.2.jar】

YUICompressor 2.4.2

Eclipseで自動的にminimizeされるようにする

Eclipseのワークスペースフォルダを開き、「.yuicompressor」というフォルダを追加します。
(Windowsの場合、GUIで「.」から始まるフォルダを作成できないことがあります。その場合は「.」を省くか、コマンドプロンプトから作成してください。)
add .yuicompressor

作成したフォルダの中に、先ほど入手した「yuicompressor-2.4.2.jar」を配置して下さい。
次に、それぞれのOSに応じて、バッチスクリプトを作成・配置して下さい。

Linux / Mac OS の方はこちら【yuicompressor.sh】

1
2
3
4
5
6
#!/bin/bash
if [ -f $1 ]
then
 file=`echo $1 | sed 's/\.src//'`
 java -jar yuicompressor-2.4.2.jar $1 -o $file --charset utf-8 --nomunge
fi

Windows の方はこちら【yuicompressor.bat】

1
2
3
4
5
6
@echo off
set inputfile=%1
set outputfile=%inputfile:.src=%
if not %inputfile% == %outputfile% (
  java -jar yuicompressor-2.4.2.jar %inputfile% -o %outputfile% --charset utf-8 --nomunge
)

下図のようになっていればOKです。(Windowsの場合)
.yuicompressorフォルダの中身

最後に、プロジェクトのプロパティを設定します。
※以降はプロジェクト毎に設定する必要があります。
ツールバー「Project」>「Properties」を開きます。
Build Properties on Eclipse

「New…」をクリックすると下図のような画面が表示されるので、
「Program」を選択し「OK」をクリックして下さい。
Program を選択

以下のように設定します。
・Name: “JS_CSS_Minimize_prj1″(適当)
・Location: [Browse File System...] をクリックして先ほど作成したバッチスクリプトを選択
・Working Directory: [Browse File System...] をクリックしてバッチスクリプトがあるフォルダを選択
・Arguments: “${resource_loc}”

次に、同画面の「Refresh」タブを開き、次の2つにチェックを入れます。
・「Refresh resources upon completion.」
・「The folder containing the selected resource」
Refresh / Eclipse YUICompressor

最後に同画面の「Build Options」タブを開き、次の3つにチェックを入れます。
・「After a “Clean”」
・「During manual builds」
・「During auto builds」
Build Options / Eclipse YUICompressor

「OK」をクリックして設定完了です。

それではテストしてみます。
下図のようなサンプルプロジェクトを作ってみました。

それぞれのファイルを変更した際の自動ビルドによって、「.src」を除いた名前のファイルが作成されます。

コメントやインデントはすべて取り除かれ、ファイルサイズが小さくなりました。

あとはHTMLに、「.src」なしの「~.css / ~.js」へのリンクを入れればOKです。

以上です。
是非お試しください。

参考:

Minimizing Javascript and CSS in Eclipse / Nicolas DEBARNOT
Automagically minimize javascript in Eclipse on Windows / ten Berge ICT

Posted in: Javascript, Web

Knockout.js で jQuery UI ‘sortable’を使う

前回に引き続き、勉強中の Knockout の記事です。
今回は、リッチなUIを手軽に実装できる jQuery UI との組み合わせを検証してみました。

jQuery UI ‘sortable’ とは

sortable とは jQuery UI に含まれる機能で、リスト要素などの各アイテムをドラッグ&ドロップで並び替えできるようにするものです。
sortable の動きについては jQuery UI Demos & Documentation を見ていただければわかると思います。

Knockout と組み合わせる

Knockout で jQuery UI などのプラグインをバインドで実行できるようにするため、カスタムバインディングを作成します。

今回は、名前もそのまま「sortable」というカスタムバインディングを作成しました。

ko.sortable.js 4.35KB ライセンスフリー
↑右クリックして「名前をつけて保存」してください。
knockout.js の後に読み込まれるように設置すればOKです。

次のサイト・ページを参考にしました。
http://stackoverflow.com/questions/4146751/knockoutjs-with-jquery-ui-sortable
http://jsbin.com/knockoutsortable/20/edit

HTML で、通常の foreach バインディング(もしくは template バインディング)とともにバインドします。
(相応の ViewModel が適用されているものとします。)

1
2
3
<ul data-bind="foreach: List, sortable: true">
	<li data-bind="text: Text"></li>
</ul>

これだけで、リストのアイテムをドラッグ&ドロップで並び替えられるようになります。
バインドされている ViewModel のプロパティである配列「List」でも、該当するアイテムの順番が変動します。

複数のリストを扱う

jQuery UI の sortable には、単なるソートだけでなく複数のリストをまたいでアイテムを移動できるようにする機能があります。
これを実現するには、次のようにクラス名で関連付け、sortable バインディングのオプションを追加します。

1
2
3
4
5
6
<ul class="sortable" data-bind="foreach: ListA, sortable: { connectWith: '.sortable' }">
	<li data-bind="text: Text"></li>
</ul>
<ul class="sortable" data-bind="foreach: ListB, sortable: { connectWith: '.sortable' }">
	<li data-bind="text: Text"></li>
</ul>

これで2つのリスト間で、自由にアイテムをドラッグ&ドロップできるようになりました。
ドロップすると、バインドされている ViewModel のプロパティである配列「ListA, ListB」でも、該当するアイテムが移動します。
connectWith オプションは jQuery UI sortable の connectWith オプションに直接値を渡しています。
つまり、CSSのセレクタであれば問題ありません。

蛇足「ゴミ箱」

ところで、foreach バインディングを書き忘れたリストにアイテムをドロップした場合、
アイテムが追加されるべき ViewModel のプロパティが無いため、アイテムは消滅していく運命です。
というわけで、ゴミ箱オプションをつけてみました。

1
2
3
4
<ul class="sortable" data-bind="foreach: List, sortable: { connectWith: '.sortable' }">
	<li data-bind="text: Text"></li>
</ul>
<ul class="sortable" data-bind="sortable: { connectWith: '.sortable', trashBox: true }"></ul>

2つめのリストにドロップされたアイテムは、自動的に削除されます。
ゴミ箱というか、シュレッダーみたいな感じです。

sortable バインディングの各オプションについて

最後に、各オプションについて紹介します。

オプション 説明
trashBox foreach バインディングなしで、かつ trashBox オプションを true に設定した場合
接続された他のリストからこのリストにドロップされたアイテムは削除される。
また、trashBox: ‘destroy’ とすることで論理削除を行うようにすることができる。
itemAdding ほかのリストからこのリストにアイテムがドロップされる際に実行されるコールバック関数

false を返却することで、アイテムの追加(他リストからの移動)をキャンセルすることができる。

引数:

  1. item: ドロップされたアイテム(DOM要素ではなく、バインドされたアイテム)
  2. array: リストにバインドされた配列
  3. oldIndex: 移動前のリスト中でのインデックス
  4. newIndex: ドロップされたリスト中のインデックス
itemAdd ほかのリストからこのリストにアイテムがドロップされた際に実行されるコールバック関数

引数:

  1. item: ドロップされたアイテム(DOM要素ではなく、バインドされたアイテム)
  2. array: リストにバインドされた配列
  3. oldIndex: 移動前のリスト中でのインデックス
  4. newIndex: ドロップされたリスト中のインデックス
itemRemove このリストからほかのリストへアイテムがドロップされた際に実行されるコールバック関数

引数:

  1. item: ドロップされたアイテム(DOM要素ではなく、バインドされたアイテム)
  2. array: リストにバインドされた配列
  3. oldIndex: 移動前のリスト中でのインデックス
itemSort このリスト内でアイテムが並べ替えられた際に実行されるコールバック関数

引数:

  1. item: ドロップされたアイテム(DOM要素ではなく、バインドされたアイテム)
  2. array: リストにバインドされた配列
  3. oldIndex: 移動前のリスト中でのインデックス
  4. newIndex: ドロップされたリスト中のインデックス
itemDisposing trashBox オプションを true または ‘destroy’ に設定した場合、アイテムが削除される前に実行されるコールバック関数

false を返却することで、アイテムの削除をキャンセルすることができる。

引数:

  1. item: 削除されるアイテム(DOM要素ではなく、バインドされたアイテム)
itemDisposed trashBox オプションを true または ‘destroy’ に設定した場合、アイテムが削除された後に実行されるコールバック関数

引数:

  1. item: 削除されたアイテム(DOM要素ではなく、バインドされたアイテム)
jQuery UI
‘sortable’
各オプション
connectWith を含め jQuery UI の各オプションは、そのまま使用可能。
※現在、オプションに observable プロパティをバインドすることは不可能。

以上です。
そろそろちょっとしたサービスみたいなものを作ってみよう!

Posted in: Javascript, Web

Knockout.js勉強中! サンプルアプリ『電卓』

最近、Knockout というJavascriptフレームワークにハマってます。

JSでの開発をラクにするKnockout

Knockoutは一言で言えば、「HTML(CSS)とJavascriptによる開発にMVVMモデルを適用する」ためのフレームワークです。
フレームワークといっても、開発者を制約で縛り上げるようなものではなく、開発がとてもラクになります。

たとえば Knockout では、HTMLの要素と、Javascriptで書いたシンプルなオブジェクトのプロパティを、定義ベースで関連付けます。
関連付けられたプロパティが更新されると、HTMLの要素も上書きされます。
これだけでも、次のようなメリットが考えられます。

  • 「開発効率向上」マークアップエンジニアとスクリプトエンジニアの分業
  • 「保守性向上」マークアップ(View)の変更によるスクリプト(ViewModel)への影響がほとんどない
  • 単純に、書くべきコードの量が減る!

Knockoutがフレームワークとして介入するのは HTML(View) と Javascript(ViewModel) との間を取り持つ部分のみです。
したがってjQuery等の既存のフレームワークも、問題なく利用することができます。

詳細は本家サイトにて公開されているデモムービー(英語だがなんとなく見てるだけでも結構わかる!)などをご参考下さい。
同サイトのチュートリアルをすべてクリアすれば、Knockoutを理解するとともにMVVMパターンのメリットについても少し詳しくなれます。

12/23 追記:
Knockout ドキュメントの翻訳を開始しました。
Knockout.日本語ドキュメント

下記のサンプルは、やや「モンキーレンチで釘を打っている」ような、意味開発者の意図を伝えきれていないという問題があります。Knockout でどんなことができるかを手っ取り早く知りたいという方には、Knockout Demo & Tips をご覧になることをお勧め致します。

勉強中にサンプルつくりました

というわけで、さっそくサンプルとして電卓を作ってみました。
ただの電卓だけだと、MVVMの恩恵をあまり受けられないので、計算履歴を表示するようにしました。

HTML (View)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="calc1.css">
<script type="text/javascript" src="../js/knockout-2.1.0.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script type="text/javascript" src="vm.calc1.js"></script>
</head>
<body>
 
<section class="calcPanel" data-bind="animateStyle: { width: calcPanelWidth, options: { duration: 'fast', queue: false } }">
 
	<section id="calc">
		<h1>電卓 powered by Knockout</h1>
		<section class="displayPanel">
			<span class="mainArea" data-bind="text: currentNum().toString().substr(0,19)"></span><!-- 入力中の数値や計算結果を表示するエリア -->
			<span class="subArea" data-bind="text: subText"></span><!-- 入力された演算子やその前に入力された数値を表示するエリア -->
		</section>
		<section class="buttonPanel">
			<table>
				<colgroup>
					<col/>
					<col/>
					<col/>
					<col/>
					<col/>
				</colgroup>
				<tbody>
					<tr>
						<td><button data-bind="click: backSpace">BS</button></td>
						<td><button data-bind="click: clearEntry">CE</button></td>
						<td><button data-bind="click: clearAll">C</button></td>
						<td><button data-bind="click: ternSign">±</button></td>
						<td><button data-bind="click: sqrt"></button></td>
					</tr>
					<tr>
						<td><button data-bind="click: inputNum.bind($data,7)">7</button></td>
						<td><button data-bind="click: inputNum.bind($data,8)">8</button></td>
						<td><button data-bind="click: inputNum.bind($data,9)">9</button></td>
						<td><button data-bind="click: inputOperator.bind($data,'/')">/</button></td>
						<td><button data-bind="click: persent">%</button></td>
					</tr>
					<tr>
						<td><button data-bind="click: inputNum.bind($data,4)">4</button></td>
						<td><button data-bind="click: inputNum.bind($data,5)">5</button></td>
						<td><button data-bind="click: inputNum.bind($data,6)">6</button></td>
						<td><button data-bind="click: inputOperator.bind($data,'*')">*</button></td>
						<td><button data-bind="click: reciproc">1/x</button></td>
					</tr>
					<tr>
						<td><button data-bind="click: inputNum.bind($data,1)">1</button></td>
						<td><button data-bind="click: inputNum.bind($data,2)">2</button></td>
						<td><button data-bind="click: inputNum.bind($data,3)">3</button></td>
						<td><button data-bind="click: inputOperator.bind($data,'-')">-</button></td>
						<td rowspan="2" data-bind="click: equal"><button>=</button></td>
					</tr>
					<tr>
						<td colspan="2"><button data-bind="click: inputNum.bind($data,0)">0</button></td>
						<td><button data-bind="click: inputPoint">.</button></td>
						<td><button data-bind="click: inputOperator.bind($data,'+')">+</button></td>
					</tr>
				</tbody>
			</table>
		</section>
	</section>
 
	<section id="history" data-bind="fadeVisible: showHistory">
		<h2>計算履歴(<span data-bind="text: calcHistories().length"></span>) <a href="#" data-bind="click: clearHistories">クリア</a></h2>
		<table>
			<colgroup>
				<col width="18px"/>
				<col/>
			</colgroup>
			<tbody data-bind="foreach: sortedCalcHistories">
				<tr><th data-bind="text: id"></th><td data-bind="text: fullExpr"></td></tr>
			</tbody>
		</table>
	</section>
 
	<div id="historyTab" data-bind="click: toggleHistoryVisible">.<br>.<br>.</div>
 
</section>
 
</body>
</html>

Javascript (ViewModel)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
function unwrapHashObservable(hash) {
	for (var name in hash)
		hash[name] = ko.utils.unwrapObservable(hash[name]);
	return hash;
}
 
/**
 * Custom binding [ fadeVisible ]
 * binding-type: boolean/object
 * binding-object: {
 * 	visible:	true=可視(フェードイン), false=不可視(フェードアウト)
 * 	speed:		フェードにかかる時間。'slow', 'normal', 'fast'
 * 				またはミリ秒にて指定する。デフォルトは'normal'。
 * 	complete:	フェード終了後に実行するコールバックを指定する。
 * }
 */
ko.bindingHandlers.fadeVisible = {
    update: function(element, valueAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor());
        var defaults = {
        	visible: true,
        	speed: 'normal',
        	complete: null
        };
        value = $.extend(
        	defaults,
        	(
        		typeof value == 'boolean'
        		? {visible: value}
        		: unwrapHashObservable(value)
        	)
        );
        value.visible
        ? $(element).fadeIn(value.speed)
        : $(element).fadeOut(value.speed);
    }
};
 
/**
 * Custom binding [ animateStyle ]
 * binding-type: object
 * binding-object: {
 * 	(styles): 		アニメーションの結果として設定される横幅。
 * 	duration:	アニメーションにかかる時間。'slow', 'normal', 'fast'
 * 				またはミリ秒にて指定する。デフォルトは'normal'。
 * 	easing: 	アニメーションパターン。デフォルトは'swing'.
 * 	complete:	アニメーション完了時に実行されるコールバック
 * 	delay:		アニメーション開始までの待ち時間をミリ秒で指定する。
 * 				デフォルトは 0。
 * }
 */
ko.bindingHandlers.animateStyle = {
	update: function(element, valueAccessor) {
		var value = valueAccessor();
		var defaults = {
			delay: 0
		};
		var options;
		if (value.options) {
			options = $.extend(defaults, unwrapHashObservable(value.options));
			delete value.options;
		} else options = defaults;
		var animate = function() {
			delete options.delay;
			$(element).animate(unwrapHashObservable(value), options);
		};
		if (options.delay > 0) setTimeout(animate, options.delay);
		else animate();
	}
};
 
function CalcHistory(id, expr, result) {
	var self = this;
	self.id = id;
	self.expr = expr;
	self.result = result;
	self.fullExpr = expr + ' = ' + result;
}
 
function CalcViewModel() {
 
	// Const value
	const showHistoryWidth = 594;
	const hideHistoryWidth = 334;
 
	// Private Field
	var self = this;
	var isResult = true;
	var isOperated = false;
	var isAfterOperator = false;
	var historyText = "";
 
	// Private Method
	function calc() {
		var cOperator = self.currentOperator();
		if (cOperator == "") return;
		var oNum = self.operandNum();
		var cNum = self.currentNum();
		switch (cOperator) {
		case '+': self.currentNum(+oNum + +cNum); break;
		case '-': self.currentNum(oNum - cNum); break;
		case '*': self.currentNum(oNum * cNum); break;
		case '/':
			if (cNum == 0) {
				self.currentNum("infinity");
				isResult = true;
				alert("0で割ることはできません。");
				return;
			}
			self.currentNum(oNum / cNum);
			break;
		}
	};
 
	// Properties
	self.operandNum = ko.observable(0);
	self.currentNum = ko.observable(0);
	self.subText = ko.observable("");
	self.currentOperator = ko.observable("");
	self.showHistory = ko.observable(true);
	self.calcPanelWidth = ko.computed(function() {
		return self.showHistory() ? showHistoryWidth : hideHistoryWidth;
	});
	self.calcHistories = ko.observableArray();
	self.sortedCalcHistories = ko.computed(function() {
		return self.calcHistories().sort(function(left, right) {
			return left.id == right.id ? 0 : (left.id < right.id ? 1 : -1);
		});
	});
 
	// Operations
	self.inputNum = function(num) {
		var cNum = '' + (isResult ? 0 : self.currentNum()) + num;
		self.currentNum(+cNum);
		isAfterOperator = isResult = false;
	};
	self.inputOperator = function(operator) {
		var lastNum = self.currentNum();
		if (!isAfterOperator) {
			calc();
			self.operandNum(self.currentNum());
			isAfterOperator = true;
			historyText = self.subText();
			isResult = true;
		}
		self.currentOperator(operator);
		self.subText(historyText + ' ' + lastNum + ' ' + operator);
		isOperated = true;
	};
	self.backSpace = function() {
		if (isResult) return;
		var cNum = '' + self.currentNum();
		self.currentNum(+(cNum.substring(0, cNum.length - 1)));
	};
	self.clearEntry = function() {
		self.currentNum(0);
		isAfterOperator = self.currentOperator() != "";
		isResult = false;
	};
	self.clearAll = function() {
		self.operandNum(0);
		self.currentNum(0);
		self.subText("");
		historyText = "";
		self.currentOperator("");
		isOperated = false;
		isResult = true;
		isAfterOperator = false;
	};
	self.equal = function() {
		if (!isOperated || isResult || isAfterOperator) return;
		var lastNum = self.currentNum();
		calc();
		self.calcHistories.push(
			new CalcHistory(
				self.calcHistories().length + 1,
				self.subText() + ' ' + lastNum,
				self.currentNum()
			)
		);
		self.subText("");
		isResult = true;
		isOperated = false;
		isAfterOperator = false;
		self.operandNum(0);
		historyText = "";
		self.currentOperator("");
	};
	self.inputPoint = function() {
		var cNum = '' + (isResult ? 0 : self.currentNum()) + ".";
		self.currentNum(cNum);
		isAfterOperator = isResult = false;
	};
	self.ternSign = function() {
		if (isResult) return;
		self.currentNum(-self.currentNum());
	};
	self.reciproc = function() {
		var cNum = self.currentNum();
		if (cNum == 0) {
			alert("0で割ることはできません。");
			return;
		}
		self.currentNum(1 / cNum);
		self.subText("reciproc(" + cNum + ")");
		isResult = true;
	};
	self.persent = function() {
		if (isResult || isAfterOperator) return;
		var oNum = self.operandNum();
		var cNum = self.currentNum();
		self.currentNum(oNum * (cNum / 100));
	};
	self.sqrt = function() {
		if (isAfterOperator) return;
		self.currentNum(Math.sqrt(self.currentNum()));
	};
	self.toggleHistoryVisible = function() {
		self.showHistory(!self.showHistory());
		console.log(self.showHistory());
	};
	self.clearHistories = function() {
		self.calcHistories.removeAll();
	};
}
 
$(function() {
	ko.applyBindings(new CalcViewModel());
});

改善したいところ

本来であれば、計算履歴エリアの開閉アニメーションをjQueryではなくCSSアニメーションにして
CSSバインディングのみで動くようにしたいところです。

この例では、アニメーション用のカスタムバインディング(animateStyle)を作っています。
つまり、アニメーションに使用するパラメータをバインドしているわけです。
本来 CSS に書くべきパラメータ width を ViewModel のプロパティ(calcPanelWidth)として公開するはめになっています。

表示幅を修正するときのことを考えると、ViewModelまでチェックしなければならないのはあまり好ましくありませんね。
CSSアニメーションなどもフル活用することで、リッチなUIをより簡潔に記述することができそうです。

以上です。
次はなにつくろうかなぁ…

Posted in: Javascript, Web

【jQuery】要素の”外側”のクリックイベントを捕捉「skOuterClick」

skOuterClick



jQueryでダイアログボックスのような機能を開発していたとき、ふと「ある要素”以外”」にクリックイベントハンドラを仕掛けられたらいいのにと思いました。

そんな思いでプラグインを探していたところ、kawama.jpさんの記事 jqueryで指定要素”以外”のクリックイベントを実装するouterClickプラグイン にたどり着きました。

残念なことに現在は肝心のプラグインが公開されておらず、リンク切れになってしまっている模様です。ほかを探してみましたが、探し方が悪いのか一向に見つからず…。

ということで、outerClickプラグインのスコブト版、その名も「skOuterClick」を作りました!

Continue reading →

Posted in: Javascript, Web

塩塚玲さんの公式サイト『魁!』が完成しました!

akira-shiotsuka.com

Webデザイナーのsenoさんと共同で制作していたサイト
塩塚玲(シオツカ アキラ)の歌って踊るホームページ「魁!」が完成しました!

とはいっても、今回は私の出番が少なく、ほとんどsenoさんに任せっきりでした。
私の出番はといえば、

  • サーバの手配・メールサーバの構築
  • フォトギャラリーのスクリプト
  • お問い合わせCGIの設置

だけでした。

次の案件はCMSなので、私の出番が豊富!がんばろう!日本!!

Posted in: 制作実績

【jQuery】真・入力欄にフォーカスすると消えるヒントを表示する方法

まずは、こちら↓をご覧下さい。

SE時代にWeb系の試験をしていて、「~を入力して下さい」ように「ヒント」として初期値を表示しておいてフォーカスが当てられると初期値を消去するフォーム部分にてバグが発覚したことがあります。
当時は「仕様です」という言葉でクローズされたのですが・・・。

今回は「仕様です」と言わずに済むスマートな実現方法を紹介します。

Continue reading →

Posted in: Javascript, Web

【CentOS-5.x】レンタルサーバのつくりかた

howToMakeAHostingServer

自宅サーバやVPSに、複数ユーザをホスティングさせる際の忘備録です。

概要

レンタルサーバのように、ホスティングユーザ権限でCGI/PHPを実行できるようにします。
これにより、複数のユーザを一つのサーバに共存させたときのセキュリティリスクを
最小限に抑えるとともに、ホスティングユーザへ一定の可用性を提供することができます。
ApacheのsuEXECという機能を使用して実現します。

この記事でできるプログラム実行環境: Perl, PHP5 (suEXEC)
ホスティングユーザのDocumentRoot: /home/ユーザ名/public_html
所要時間: 1~2時間

  1. Webサーバをインストール(RPMパッケージから / ソースコードから)
  2. PHP5をCGIモードでインストール
  3. Webサーバの設定
  4. ホスティングユーザの環境整備
  5. Webサーバ起動・テスト

Continue reading →

Posted in: Linux

イタリアに行って来ました(その2)

OLYMPUS DIGITAL CAMERA

その1

続いて観光1日目のコロッセオから。

Continue reading →

Posted in: プライベート

イタリアに行って来ました(その1)

OLYMPUS DIGITAL CAMERA

1月27日~2月1日
サン・ピエトロ大聖堂が見たくて、イタリアに行って来ました。
ローマを拠点に、オプショナルツアーとしてバチカン・ローマ市内とポンペイの遺跡を観てきました。

Continue reading →

Posted in: プライベート