27.05.2012 16:29:00
Knockout.js bringt Datenbindung sowie Unterstützung für das Muster Model-View-ViewModel und somit einen populären Ansatz für das Trennen von Logik und Präsentation in JavaScript-basierten Anwendungen. Dojo bringt hingegen mit dem Sub-Projekt Dijit optisch ansprechende Steuerelemente für Web-Anwendungen. In Kombination funktionieren diese beiden Frameworks leider nicht miteinander – zumindest nicht von Haus aus. Aus diesem Grund habe ich nun ein generisches knockout-Binding implementiert, welches den Brückenschlag zwischen diesen beiden Technologien ermöglicht. Getestet wurde es mit den Steuerelementen Button, ToggleButton, CheckBox, RadioButton, NumberTextBox, SliderButton sowie ComboBox und Select.
Das nachfolgende Listing zeigt die Implementierung dieses Bindings.
@{
ViewBag.Title = "Index";
this.Layout = "";
}<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/dojo/1.7.1/dijit/themes/claro/claro.css">
<script src="http://ajax.googleapis.com/ajax/libs/dojo/1.7.1/dojo/dojo.js" data-dojo-config="isDebug: true, async: true, parseOnLoad: false"></script>
<script src="/Scripts/jquery-1.6.2.js" type="text/javascript"></script>
<script src="/Scripts/knockout-1.3.0beta.debug.js" type="text/javascript"></script>
<script language="javascript">
koHelper = {};
koHelper.getValue = function(prop, defaultValue) {
if (typeof prop == "undefined") {
return defaultValue;
}
else if (ko.isObservable(prop)) {
return prop();
}
else {
return prop;
}
}
koHelper.subscribeIfObservable = function (propertyName, propertyValue, widget) {
if (propertyValue && ko.isObservable(propertyValue)) {
propertyValue.subscribe(function (newValue) {
widget.set(propertyName, newValue);
});
}
}
koHelper.getEventHandler = function (handler) {
return (handler) ? handler : function () { };
}
ko.bindingHandlers.dijit = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var value = valueAccessor();
var dojoType = $(element).data("dojo-type");
dojoType = dojoType.replace(/\./g, "/");
require([dojoType], function (Widget) {
var config = {};
for (var property in value) {
config[property] = koHelper.getValue(value[property]);
}
var widget = new Widget(config, element);
widget.startup();
widget.set(onChange, function (newValue) {
var prop;
prop = value.value;
if (ko.isObservable(prop)) prop(newValue);
prop = value.checked;
if (ko.isObservable(prop)) prop(newValue);
});
for (var property in value) {
koHelper.subscribeIfObservable(property, value[property], widget);
}
});
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
}
};
</script>
[…]
Damit dieses Binding korrekt funktioniert, muss man zunächst das automatische Parsen der Seite durch Dojo unterbinden – das übernimmt das Binding. Bewerkstelligt wird dies durch Angabe von parseOnLoad: false im Attribut data-dojo-config jenes Script-Tags, mit dem Dojo eingebunden wird (siehe oben).
Die Anwendung dieses Bindings ist im nächsten Listing demonstriert. Wie gewohnt sind die Dojo-Typen im Rahmen der einzelnen Tags mit dem Attribut data-dojo-type anzugeben. Die Datenbindung mittels Knockout wird – ebenfalls, wie gewohnt – mit dem Attribut data-bind vollzogen. Im Rahmen dessen kommt jedoch die Eigenschaft dijit, welche über das Custom-Binding bereitgestellt wird, zum Einsatz. Diese Eigenschaft hat auf ein Objekt zu verweisen, welches die einzelnen Eigenschaften des Dijit-Steuerelements auf Observables oder Funktionen des View-Models bzw. auf statische Werte abbildet.
[…]
<script language="javascript">
function MainVM() {
var self = this;
this.buttonName = ko.observable("Klick mich!");
this.iconClass = ko.observable("dijitIconNewTask");
this.checked = ko.observable(true);
this.state = ko.observable("A");
this.state2 = ko.observable("");
this.age = ko.observable();
this.topping1checked = ko.observable(true);
this.topping2checked = ko.observable(false);
this.topping3checked = ko.observable(false);
this.percent = ko.observable(50);
this.options = ko.observableArray([{ key: "", value: "Bitte wählen Sie ..." }, { key: "D", value: "Deutschland" }, { key: "A", value: "Österreich" }, { key: "CH", value: "Schweiz"}]);
this.zutat = ko.observable();
this.clickFunction = function () {
if (window.console) console.log("Aua!");
self.buttonName("Aua!");
self.iconClass("dijitIconClear");
}
}
$(function () {
var vm = new MainVM();
ko.applyBindings(vm);
});
</script>
</head>
<body class="claro">
<h1>Index</h1>
<button data-dojo-type="dijit.form.ToggleButton" data-bind="dijit: { checked:checked, iconClass:dijitCheckBoxIcon }">
Toggle
</button>
<div>
Checked: <span data-bind="text: checked"></span>
</div>
<button data-dojo-type="dijit/form/Button" data-bind="dijit: { label: buttonName, onClick: clickFunction, iconClass: iconClass, showLabel:true }">Click Me!</button>
<input type="checkbox" id="dbox1" checked data-dojo-type="dijit.form.CheckBox" data-bind="dijit: { checked: checked }"><label for="dbox1">Want</label>
<div>
<select id="stateSelect" data-dojo-type="dijit.form.Select" data-bind="foreach:options, dijit: { value: state }" name="stateSelect">
<option data-bind="attr: { value: key }, text: value"></option>
</select>
</div>
<div>
State: <span data-bind="text: state"></span>
</div>
<div>
<select data-dojo-type="dijit.form.ComboBox" data-bind="foreach:options, dijit: { value: state2 }"
name="stateSelect">
<option data-bind="attr: { value: key }, text: value"></option>
</select>
</div>
<div>
State: <span data-bind="text: state2"></span>
</div>
<div>
<label for="number">Age:</label>
<input id="number" type="text" value="54" required="true" data-dojo-type="dijit.form.NumberTextBox" data-bind="dijit: { value: age }">
</div>
<div>
Age: <span data-bind="text:age"></span>
</div>
<ul>
<li>
<input id="topping1" type="radio" name="topping" value="anchovies" checked
data-dojo-type="dijit.form.RadioButton" data-bind="dijit: { checked: topping1checked}">
<label for="topping1">Anchovies</label>
</li>
<li>
<input id="topping2" type="radio" name="topping" value="olives"
data-dojo-type="dijit.form.RadioButton" data-bind="dijit: { checked: topping2checked}">
<label for="topping2">Olives</label>
</li>
<li>
<input id="topping3" type="radio" name="topping" value="pineapple"
data-dojo-type="dijit.form.RadioButton" data-bind="dijit: { checked: topping3checked}">
<label for="topping3">Pineapple</label>
</li>
</ul>
<div>
<div>topping1checked: <span data-bind="text: topping1checked"></span></div>
<div>topping2checked: <span data-bind="text: topping2checked"></span></div>
<div>topping3checked: <span data-bind="text: topping3checked"></span></div>
</div>
<div>
<input id="hslider" type="range"
data-dojo-type="dijit.form.HorizontalSlider"
data-bind="dijit: { value: percent, minimum: 0, maximum: 100, discreteValues: 101 }">
</div>
<div>
Percent: <span data-bind="text: percent"></span>
</div>
</body>
</html>
|