custom ko binding for jquery data tables - javascript

I have used jquery dataTable in knockout.js.In that i am generating data rows from function with remove link.It will remove row form table as well form observable array.The remove link works once and remove the row from table but when i try to remove another one they do not remove it.
Here you can check http://jsfiddle.net/zongweil/PLUKv/1/
$(document).ready(function () {
/* Custom binding */
ko.bindingHandlers.dataTable = {
init: function (element, valueAccessor) {
var binding = ko.utils.unwrapObservable(valueAccessor());
// If the binding is an object with an options field,
// initialise the dataTable with those options.
if (binding.options) {
$(element).dataTable(binding.options);
}
},
update: function (element, valueAccessor) {
var binding = ko.utils.unwrapObservable(valueAccessor());
// If the binding isn't an object, turn it into one.
if (!binding.data) {
binding = {
data: valueAccessor()
};
}
// Clear table
$(element).dataTable().fnClearTable();
// Rebuild table from data source specified in binding
$(element).dataTable().fnAddData(binding.data());
}
};
/* Object code */
function GroupMember(id, name, isGroupLeader) {
var self = this;
self.id = id;
self.name = name;
self.isGroupLeader = ko.observable(isGroupLeader);
self.link = ko.computed(function () {
return "/#user/" + self.id;
});
self.nameWithLink = ko.computed(function () {
return '' + self.name + '';
});
self.actions = ko.computed(function () {
return '<a class="btn btn-danger" data-bind="click: function() {removeMember(' + self.id + ')}">' + '<i class="icon-minus-sign"></i>' + '</a>';
});
}
/* View model */
var groupViewModel = {
groupMembers: ko.observableArray([
new GroupMember("1", "Abe", true),
new GroupMember("2", "Bob", false),
new GroupMember("3", "Bill", false)])
};
groupViewModel.membersTable = ko.computed(function () {
var self = this;
var final_array = new Array();
for (var i = 0; i < self.groupMembers().length; i++) {
var row_array = new Array();
row_array[0] = self.groupMembers()[i].nameWithLink();
row_array[1] = self.groupMembers()[i].actions();
final_array.push(row_array);
}
return final_array;
}, groupViewModel);
groupViewModel.removeMember = function (id) {
var self = this;
self.groupMembers.remove(function (groupMember) {
return groupMember.id == id;
});
};
ko.applyBindings(groupViewModel);
});

When you call fnClearTable in your custom binding's update function, you clear a part of your DOM behind knockout's back.
You then add new DOM elements by calling fnAddData.
Your buttons work using the click binding. For the click binding to work, knockout has to applyBindings.
If you want to keep using both the dataTable and a click binding to work with the DOM, you'll have to manually apply bindings every time you make a change. In your init method, let knockout know you're taking care of descendant bindings:
return { controlsDescendantBindings: true };
In your update method, apply bindings by hand:
ko.applyBindingsToDescendants(viewModel, element);
This makes sure your click bindings will work again.
Here's your example with this code added: http://jsfiddle.net/5t15rhyq/

Related

KnockoutJS - ViewModel Grandparent - Parent - Child using ko.computed to access Parent/Grandparent Value

I have a Grandparent, Parent, Child ViewModel relationship setup in knockout and knockout mapping, CustomerViewModel, WorkOrderViewModel, and RepairViewModel.
I want to setup a child ko.computed value within the child that Take the amount of hours in the RepairViewModel and multiplies it by Rate within the WorkOrderView Model.
Within the RepairViewModel I have code like this:
self.RepairCost = ko.computed(function () {
return (self.Hours() * self.parent.LabourChargeCost()).toFixed(2);
});
Is there any way to get the parent's value?
Thanks so much!
Here is the JS code I'm using (simplified):
var workOrderMapping = {
'WorkOrders': {
key: function (workOrders) {
return ko.utils.unwrapObservable(workOrders.WorkOrderId);
},
create: function (options) {
return new WorkOrderViewModel(options.data);
}
},
'Repairs': {
key: function (repairs) {
return ko.utils.unwrapObservable(repairs.RepairId);
},
create: function (options) {
return new RepairViewModel(options.data);
}
}
};
RepairViewModel = function (data) {
var self = this;
ko.mapping.fromJS(data, workOrderMapping, self);
self.RepairCost = ko.computed(function () {
return (self.Hours() * self.parent.LabourChargeCost()).toFixed(2);
})
;
}
WorkOrderViewModel = function (data) {
var self = this;
ko.mapping.fromJS(data, workOrderMapping, self);
}
CustomerViewModel = function (data) {
var self = this;
ko.mapping.fromJS(data, workOrderMapping, self);
self.save = function () {
//alert(ko.toJSON(self));
$.ajax({
url: "/Customers/Save/",
type: "POST",
data: ko.toJSON(self),
contentType: "application/json",
success: function (data) {
//alert("succ");
//alert(data.customerViewModel);
// if (data.customerViewModel != null) {
//alert("succ2");
new PNotify({
title: 'Saved',
text: 'Record saved successfully',
type: 'success',
styling: 'bootstrap3'
});
ko.mapping.fromJS(data.customerViewModel, workOrderMapping, self);
if (data.newLocation != null)
window.location = data.newLocation;
},
});
};
}
You can't access the parent in the child model unless you pass the parent to the child and kept a reference.
What has worked better for me in the past is passing the value to the child model and then add to subscribe in the parent to update the child when the value is changed.
function Parent(){
var self = this;
self.someValue = ko.obserable();//init if you need to
self.children = [new Child(self.someValue())]
self.someValue.subscribe(function(value){
for(var i = 0;i<self.children.length;i++){
self.children[i].parentValue(value);
}
});
}
function Child(value){
var self = this;
self.parentValue = ko.observable(value);
}
Unfortunately you cannot do that, and it is a limitation not of Knockout but JS: you don't have access to the parent context from within an object property.
What you can do is, as #GrayTower mentioned, pass your parent as a parameter, but this to me, feels a bit like a hack (I admit to using it sometimes though). You could also modify your child viewmodels once they have been initiated, from either within the parent, or externally, before the view model is bound. I don't really understand the flow of properties in the code you presented, but I hope a smaller test case will suit your needs: http://jsfiddle.net/kevinvanlierde/507k237y/. Suppose we have the following parent-child viewmodels:
// symbolizes an employee
function ChildVM(name, hoursWorked) {
var self = this, parent;
this.name = name;
this.hoursWorked = hoursWorked;
}
// symbolizes a payment system
function MasterVM() {
var self = this;
this.rate = 25; // dollars/hour
this.employees = [
new ChildVM('Chris',16),
new ChildVM('Cagle',32)
];
}
var app = new MasterVM(),
view = document.getElementsByTagName('table')[0];
We want to add a property payout to each ChildVM in employees, which will use rate from MasterVM in combination with hoursWorked from ChildVM, i.e. a ko.computed. You could simply paste a function inside the MasterVM constructor, like this:
ko.utils.arrayForEach(self.employees, function(i) {
i.payout = ko.computed(function() {
return i.hoursWorked*self.rate;
});
});
Or you could make it a method and call it before calling ko.applyBindings:
this.initEmployees = function() {
ko.utils.arrayForEach(self.employees, function(i) {
i.payout = ko.computed(function() {
return i.hoursWorked*self.rate;
});
});
}
app.initEmployees();
ko.applyBindings(app, view);
Or you could even build an applyBindings wrapper, which executes a 'callBefore' (cf. <=> AJAX callbacks) before binding the view and the model, like so:
function initVM(VM, callbefore, element) {
callbefore(VM);
ko.applyBindings(VM, element);
}
initVM(app, function(vm) {
ko.utils.arrayForEach(vm.employees, function(i) {
i.payout = ko.computed(function() {
return i.hoursWorked*vm.rate;
});
});
},view);
fiddle
Note: Using ko.mapping.fromJSconverts all values to observables, while when your values don't need updating you don't need observables, you can use plain JS values/ objects.

knockout sortable with computed observable not working

jsfiddle example. Like the title says I am trying to use a computed observable along with rniemeyer knockout sortable example. I keep getting
the write method needs to be implemented
This error is viewable in the developer console.
I have a write method implement on my ko.computed but it still errors out. What I am I doing wrong?
html and javascript below
<div id="main">
<h3>Tasks</h3>
<div class="container" data-bind="sortable: tasks">
<div class="item">
<span data-bind="visible: !$root.isTaskSelected($data)">
</span>
<span data-bind="visibleAndSelect: $root.isTaskSelected($data)">
<input data-bind="value: name, event: { blur: $root.clearTask }" />
</span>
</div>
</div>
</div>
var Task = function(first,last) {
var self = this;
self.firstName = ko.observable(first);
self.lastName = ko.observable(last);
self.TestName = ko.computed({
read: function (){
return self.firstName() + " " + self.lastName();
},
write: function (item) {
console.log(item);
}
});
return self;
}
var ViewModel = function() {
var self = this;
self.testTasks = ko.observableArray([
new Task("test","one"),
new Task("test","two"),
new Task("test","three")
]);
self.tasks = ko.computed({
read: function() { return self.testTasks();},
write: function(item) {console.log(item);}
});
self.selectedTask = ko.observable();
self.clearTask = function(data, event) {
if (data === self.selectedTask()) {
self.selectedTask(null);
}
if (data.name() === "") {
self.tasks.remove(data);
}
};
self.addTask = function() {
var task = new Task("new");
self.selectedTask(task);
self.tasks.push(task);
};
self.isTaskSelected = function(task) {
return task === self.selectedTask();
};
};
//control visibility, give element focus, and select the contents (in order)
ko.bindingHandlers.visibleAndSelect = {
update: function(element, valueAccessor) {
ko.bindingHandlers.visible.update(element, valueAccessor);
if (valueAccessor()) {
setTimeout(function() {
$(element).find("input").focus().select();
}, 0); //new tasks are not in DOM yet
}
}
};
ko.applyBindings(new ViewModel());
As the very author of this plugin says here, you can't use a computed observable; the sortable plugin depends on an actual observable array.
Which makes sense when you think about it: the plugin is actually manipulating the various indexes of the array as you re-sort the elements.
Here's a "writableComputedArray" if you want the best of both worlds. If you add/remove from the array, and a subsequent re-compute of the observable performs the same add/remove, subscribers will not get notified the second time. However, it's your responsibility to make sure that there are no discrepancies between the computation of the array and what actually gets added/removed. You can accomplish this by making the necessary changes in the sortable binding's afterMove event.
ko.writeableComputedArray = function (evaluatorFunction) {
// We use this to get notified when the evaluator function recalculates the array.
var computed = ko.computed(evaluatorFunction);
// This is what gets returned to the caller and they can subscribe to
var observableArray = ko.observableArray(computed());
// When the computed changes, make the same changes to the observable array.
computed.subscribe(function (newArray) {
// Add any new values
newArray.forEach(function (value) {
var i = observableArray.indexOf(value);
if (i == -1) {
// It's a new value, push it
observableArray.unshift(value);
}
});
// Remove any old ones. Loop backwards since we're removing items from it.
for (var valueIndex = observableArray().length - 1; valueIndex >= 0; valueIndex--) {
var value = observableArray()[valueIndex];
var i = newArray.indexOf(value);
if (i == -1) {
// It's an old value, remove it
observableArray.remove(value);
}
}
});
return observableArray;
};

Knockout custom binding doesn't update the computed function

I have a custom binding for an html editable field..
I changed it to use another custom binding now (HtmlValue), because EditableText had an error when updating the values (both custom bindings are included in the jsfiddle).
Anyone knows how to fix this?
This is the code that doesn't update the value:
ko.bindingHandlers.htmlValue = {
init: function (element, valueAccessor, allBindingsAccessor) {
ko.utils.registerEventHandler(element, "keyup", function () {
var modelValue = valueAccessor();
var elementValue = element.innerHTML;
if (ko.isWriteableObservable(modelValue)) {
modelValue(elementValue);
}
else { //handle non-observable one-way binding
var allBindings = allBindingsAccessor();
if (allBindings['_ko_property_writers'] && allBindings['_ko_property_writers'].htmlValue) allBindings['_ko_property_writers'].htmlValue(elementValue);
}
}
)
},
update: function (element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor()) || "";
if (element.innerHTML !== value) {
element.innerHTML = value;
}
}
};
You can try it out here: http://jsfiddle.net/DMf8r/
There is a bunch of problems with the way the view model is constructed and with the bindings themselves...
The tax_total computed should be declared after lines because it accesses lines and Knockout executes tax_total as soon as the computed is created.
this needs to be passed into the computed so that this inside the computed is the view model
elem needs to be defined in the $.each() call
To loop the underlying array in $.each(), you need to use this.lines() instead of this.lines
The values inside lines need to be observables, otherwise the computed would not be notified of changes.
The span is using a value binding, it should be text.
There might have been more problems but it's hard to keep track of what all the changes were...
this.lines = ko.observableArray([
{ unit_price: ko.observable(5.0), tax_rate: ko.observable(21.00) },
{ unit_price: ko.observable(5.0), tax_rate: ko.observable(21.00) }]);
this.add_line = function () {
this.lines.push({ unit_price: ko.observable(5.0), tax_rate: ko.observable(21.00) });
}.bind(this);
this.tax_total = ko.computed(function () {
var total = 0; //this.subtotal()
$.each(this.lines(), function (index, elem) {
total += (elem.unit_price() * (elem.tax_rate() / 100));
});
return total;
}, this);
<span data-bind="text: tax_total">1.02</span>
Fiddle: http://jsfiddle.net/DMf8r/1/

knockout variable not defined - scoping issue?

I have the following code:
var ObjectViewModel = function (testObject) {
//debugger;
var self = this;
self.id = testSet.id;
self.details = testOject.details;
self.children = ko.observableArray(testObject.children);
self.childCount = ko.computed(function() {
return self.children().length;
});
self.addObject = function () {
//debugger;
// Pending UI
// Call API here
// On success, complete
self.children.push(dummyObject);
self.childToAdd("");
}.bind(self);
}
/ etc
However in childCount, this.children() is undefined. I'm trying to let the view show the length of the children array in real-time, so as the user adds/removes items, the count is updated. Any idea why this isn't working?
You can pass what the value of this should be when the function is executed to the computed function with the last parameter:
this.childCount = ko.computed(function() {
return this.children().length;
}, this);
You could also store a reference to this outside of the computed:
var self = this;
this.childCount = ko.computed(function () {
return self.children().length;
});

IE select not appending options

I created a currency converter object and it works great except in IE. None of options get appended to the select element. I have been trying to find a solution for hours but can't figure out what is going on. I am new to javascript so I may be doing something completely wrong just not sure what. It seems like the render method is not getting called from within fetch. Thanks
var CurrencyConverter = {
// Initialize Currency Converter
// total: jQuery wrapped object that contains the price to convert
// select: jQuery wrapped select element to render the options tag in
init: function (total, select) {
var that = this;
this.total = total;
this.base_price = accounting.unformat(this.total.text());
this.select = select;
this.fetch();
select.change(function () {
var converted = '',
formated = '';
fx.settings = { from: fx.base, to: this.value };
converted = fx.convert(that.base_price);
formated = accounting.formatMoney(converted, { symbol: this.value, format: "%s %v", precision: "0" });
$(that.total).text(formated);
});
},
// Render Currency Options
render: function () {
var that = this,
accumulator = [],
frag = '';
for (var propertyName in fx.rates) {
accumulator.push(propertyName);
}
$.each(accumulator, function ( i, val ) {
var the_price = $(document.createElement('option')).text(val);
if (val == fx.base) {
the_price.attr('selected', 'true');
}
// TODO: not optimal to run append through each iteration
that.select.append(the_price);
});
},
// Fetch & set conversion rates
fetch: function () {
var that = this;
// Load exchange rates data via the cross-domain/AJAX proxy:
$.getJSON(
'http://openexchangerates.org/latest.json',
function(data) {
fx.rates = data.rates;
fx.base = data.base;
that.render();
}
);
}
};
if ($('#currency-select')) {
CurrencyConverter.init($('#price'), $('#currency-select'));
}
Your problem is scope.
init: function (total, select) {
var that = this; // Ok, `that` is `init`...
this.total = total;
this.base_price = accounting.unformat(this.total.text());
this.select = select; // So `init.select = select`...
.
.
.
render : function () {
var that = this, // Ok, `that` is `render`
accumulator = [],
frag = '';
.
.
.
that.select.append(the_price); // ?????
The easiest way to solve this, is to create a constructor function instead of a literal object so you can pass $select as an object to which you have access within any method.
var CurrencyConverter = function($select){
this.init = function(){ ... }
this.render = function() { $select.append('...'); }
.
.
.
};
var currency = new CurrencyConverter($('select'));
Ye, i've ran too in this. Don't know if it's the right way to solve this but it works, implying that.select is a jQuery result:
that.select.get(0).add(the_price.get(0))
Tutorial about working

Categories

Resources