View Temporary Variables in KO
by sukobuto
HTML
<script src="http://sukobuto.com/cdn/knockout-es5.min.js"></script>
<h1>KnockoutJS Variable Binding</h1>
<h2>for temporary variable in View</h2>
<p>
コンバータ関数で変換した結果等を一時変数に保持します。<br/>
通常のバインディングでも、仮想エレメント(コメント型)でも使えます。
</p>
<p>
一時変数を observable にする場合は無名関数でラップして渡して下さい。<br/>
無名関数でラップした場合、一時変数は ko.computed となります。<br/>
ただし Knockout-ES5 がロードされている場合は ko.defineProperty が使われるため<br/>
data-bind で () を付けて関数呼び出しする必要はなくなります。
</p>
<div id="demo" data-bind="click: function(d, e) { someText = vc.click2Text(e) }">
<svg width="200" height="200">
<polygon data-bind="attr: { points: vc.stats2Points(stats) }"></polygon>
<circle class="chartbase" cx="100" cy="100" r="80"></circle>
<circle class="chartbase light" cx="100" cy="100" r="60"></circle>
<circle class="chartbase" cx="100" cy="100" r="40"></circle>
<circle class="chartbase light" cx="100" cy="100" r="20"></circle>
<!--ko foreach: stats-->
<!--ko variable: {
lineEndPoint: function() { return vc.value2Point(120, $index(), $parent.stats.length) },
labelPoint: function() { return vc.value2Point(value, $index(), $parent.stats.length) }
}--><!--/ko-->
<line class="chartbase" x1="100" y1="100"
data-bind="attr: { x2: lineEndPoint.x, y2: lineEndPoint.y, }"></line>
<text data-bind="attr: { x: labelPoint.x - 4, y: labelPoint.y + 4 }, text: label"></text>
<!--/ko-->
</svg>
<div data-bind="foreach: stats">
<div>
<label data-bind="text: label"></label>
<input type="range" data-bind="value: value" min="0" max="100" />
<span data-bind="text: value"></span>
<button data-bind="click: $parent.remove">X</button>
</div>
</div>
<button data-bind="click: reverse">REVERSE</button><br/>
<input type="text" data-bind="value: statInput"/>
<button data-bind="click: createStat">Create a Stat</button>
<p...
CSS
body {
font-family: Helvetica Neue, Arial, sans-serif;
}
polygon {
fill: rgba(255, 136, 136, 0.5);
stroke: #f88;
stroke-width: 2px;
}
line.chartbase {
stroke: #999;
opacity: .7;
}
circle.chartbase {
fill: transparent;
stroke: #999;
opacity: .7;
}
circle.chartbase.light {
opacity: .3;
}
text {
font-family: Helvetica Neue, Arial, sans-serif;
font-size: 10px;
fill: #666;
}
label {
display: inline-block;
margin-left: 10px;
width: 20px;
}
JavaScript
ko.bindingHandlers['variable'] = {
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
var variables = valueAccessor();
for ( var v in variables ) {
if (variables.hasOwnProperty(v)) {
if (ko.isObservable(variables[v])) {
bindingContext[v] = variables[v];
} else if (typeof variables[v] === 'function') {
if (ko.hasOwnProperty('defineProperty'))
ko.defineProperty(bindingContext, v, variables[v]);
else bindingContext[v] = ko.computed(variables[v]);
} else {
bindingContext[v] = variables[v];
}
}
}
}
};
ko.virtualElements.allowedBindings['variable'] = true;
// View Converter
window.vc = (function() {
var root = this;
// stat の値からスターチャートの頂点座標に変換
root.value2Point = function(value, index, total) {
console.log(arguments);
var x = 0,
y = -value * 0.8,
angle = Math.PI * 2 / total * index,
cos = Math.cos(angle),
sin = Math.sin(angle),
tx = x * cos - y * sin + 100,
ty = x * sin + y * cos + 100
return {
x: tx,
y: ty
}
}
// stat の配列からスターチャートのパス頂点情報に変換
root.stats2Points = function(stats) {
return stats.map(function(stat) {
var point = root.value2Point(stat.value, stats.indexOf(stat), stats.length);
return point.x + ',' + point.y;
}).join(' ');
};
// クリックイベントからてきとうなテキストに変換
root.click2Text = function(e) {
return 'クリック位置 x=' + e.offsetX + ' y=' + e.offsetY;
};
return root;
})();
function AppViewModel() {
var self = this;
self.stats = [];
self.statInput = "";
self.someText = "";
ko.track(self);
self.addStat = function(data) {
...