A few people have spoken to me about the implementation and commentated on what they perceived as a leakage into the Separation of Concerns regarding how bindings are managed via the data-bind attributes. While the comments certainly led to some interesting debates, I thought how can I remove some of those Concerns away from the UI.
Some research and I came across Neil Kerkin's blog post Exploring TodoMVC and knockout.js with unobtrusive bindings where he discusses an approach using knockout.js supports custom binding providers that allow us to refactor the code to achieve a bit more separation.
I won't go too much into what I have done because Neil explains it a lot better than I could. Therefore I'll just post the newer code base that will be used for part 2.
The javascript which looked like the following:
Code Snippet
(function (getworkouts, $, undefined) { //create view model and initialise with default data.
getworkouts.viewModel = function() {
this.workouts = ko.mapping.fromJS(
[],
{
key: function (workout) { return ko.utils.unwrapObservable(workout.Id); }
});
}; //use ajax call to populate the view model from the Action and also bootstraps knockout
getworkouts.getDataFromSource = function () {
$.ajax({
type: 'POST',
url: '/Home/GetWorkouts',
dataType: 'json',
success: function (data) {
var model = new getworkouts.viewModel();
model.workouts = ko.mapping.fromJS(data);
ko.applyBindings(model);
}
});
};
} (window.getworkouts = window.getworkouts || {}, jQuery));$(document).ready(function () {
//bootstrap call
getworkouts.getDataFromSource();
});
getworkouts.viewModel = function() {
this.workouts = ko.mapping.fromJS(
[],
{
key: function (workout) { return ko.utils.unwrapObservable(workout.Id); }
});
}; //use ajax call to populate the view model from the Action and also bootstraps knockout
getworkouts.getDataFromSource = function () {
$.ajax({
type: 'POST',
url: '/Home/GetWorkouts',
dataType: 'json',
success: function (data) {
var model = new getworkouts.viewModel();
model.workouts = ko.mapping.fromJS(data);
ko.applyBindings(model);
}
});
};
} (window.getworkouts = window.getworkouts || {}, jQuery));$(document).ready(function () {
//bootstrap call
getworkouts.getDataFromSource();
});
becomes
Code Snippet
/* File Created: February 5, 2;01;2 ;*/
(function (getworkouts, $, undefined) {
window.setUpBindings.SetCustomBindings();
//create view model and initialise with default data.
getworkouts.viewModel = function () {
this.workouts = ko.mapping.fromJS(
[],
{
key: function (workout) { return ko.utils.unwrapObservable(workout.Id); }
});
};
//use ajax call to populate the view model from the Action and also bootstraps knockout
getworkouts.getDataFromSource = function () {
$.ajax({
type: 'POST',
url: '/Home/GetWorkouts',
dataType: 'json',
success: function (data) {
var model = new getworkouts.viewModel();
model.workouts = ko.mapping.fromJS(data);
getworkouts.bindings = {
workouts: { foreach: model.workouts },
workoutsDateTime: function () { return { text: this.DateTime }; },
workoutsWorkoutName: function () { return { text: this.WorkoutName }; }
};
//set ko's current bindingProvider equal to our new binding provider
ko.bindingProvider.instance = new ko.customBindingProvider(getworkouts.bindings);
ko.applyBindings(model);
}
});
};
} (window.getworkouts = window.getworkouts || {}, jQuery));
$(document).ready(function () {
//bootstrap
getworkouts.getDataFromSource();
});
With an additional javascript snippet of
Code Snippet
/* File Created: February 16, 2012 */
(function (setupBindings, $, undefined) {
setupBindings.SetCustomBindings = function() {
ko.customBindingProvider = function(bindingObject) {
this.bindingObject = bindingObject;
//determine if an element has any bindings
this.nodeHasBindings = function(node) {
return node.getAttribute ? node.getAttribute("data-class") : false;
};
//return the bindings given a node and the bindingContext
this.getBindings = function(node, bindingContext) {
var result = { };
var classes = node.getAttribute("data-class");
if (classes) {
classes = classes.split(' ');
//evaluate each class, build a single object to return
for (var i = 0, j = classes.length; i < j; i++) {
var bindingAccessor = this.bindingObject[classes[i]];
if (bindingAccessor) {
var binding = typeof bindingAccessor == "function" ? bindingAccessor.call(bindingContext.$data) : bindingAccessor;
ko.utils.extend(result, binding);
}
}
}
return result;
};
};
};
} (window.setUpBindings = window.setUpBindings || {}, jQuery));
The view then becomes
Code Snippet
<tableclass="gridtable">
<thead>
<tr>
<th>
Date Of Workout
</th>
<th>
Name Of Workout
</th>
</tr>
</thead>
<tbody data-class="workouts">
<tr>
<td>
<span data-class="workoutsDateTime"></span>
</td>
<td>
<span data-class="workoutsWorkoutName"></span>
</td>
</tr>
</tbody>
</table>
Once again the code is available at Github.
I've rushed over the implementation mainly because I don't want to any credit away from Neil Kerkin's solution to the Separation of Concerns problem.
See you at part 2.
No comments:
Post a Comment