I have a Datatable that takes data from Knockout ViewModel array. New rows are added fine when I push new objects to the array, but when an object in that array is changed, the row isn't being updated.
The relecent ViewModel code:
.....
self.Products = ko.observableArray();
.....
Sample data (e.g result of ko.toJSON(VM.Products()) ):
"[
{"Price":"114.28","Name":"Pearls","Description":"Little and big","Quantity":3},
{"Price":"117.55","Name":"Silver","Description":"Sliver Coins","Quantity":2},
{"Price":"166.09","Name":"Phone","Description":"Nokia","Quantity":2},
{"Price":"169.36","Name":"Wood","Description":"Northen forest wood","Quantity":1}
]"
Then Datatable code:
var CartDt = $("#PurchasedPrdTbl").DataTable({
pageLength: 5,
aoData: ko.toJSON(VM.Products()),
columns: [
{ "title": 'Price' },
{ "title": 'Name' },
{ "title": 'Description' },
{ "title": 'Quantity' },
{
"className": 'remove-details-control',
"orderable": false,
"data": null,
"defaultContent": '',
"title": "Sell Me!",
"fnRender": function (oObj) {
return oObj.aData;
}
}
]
});
I'v tried ajax.reload but it didn't helped.
Any ideas would be great, thanks.
EDIT:
I'm trying fnUpdate (source) like this:
CartDt.fnUpdate([{"Price":"114.28","Name":"Pearls","Description":"Little and big","Quantity":3}],1);
But I get this:
Uncaught TypeError: undefined is not a function
This is because the html that is bound to your knockout observable array is mangled when datatables is initialized... The html is copied and reformatted to be "datatables" after initialization. so the elements that you view after datatables initialization are not the same elements that knockout has bound to. I have created a custom binding to use datatables with knockout. it is dependent upon a fork of knockout which adds before render all and after render all events. I have created a pull request with the knockout git hub repository. If the below solution helps you please leave a comment on the pull request to get it merged into knockout 3.4... thanks.
fiddle of working solution
my pull request
ko.bindingHandlers.DataTablesForEach = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var nodes = Array.prototype.slice.call(element.childNodes, 0);
ko.utils.arrayForEach(nodes, function (node) {
if (node && node.nodeType !== 1) {
node.parentNode.removeChild(node);
}
});
return ko.bindingHandlers.foreach.init(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var value = ko.unwrap(valueAccessor()),
key = "DataTablesForEach_Initialized";
var newValue = function () {
return {
data: value.data || value,
beforeRenderAll: function (el, index, data) {
if (ko.utils.domData.get(element, key)) {
$(element).closest('table').DataTable().clear();
$(element).closest('table').DataTable().destroy();
}
},
afterRenderAll: function (el, index, data) {
$(element).closest('table').DataTable(value.options);
}
};
};
ko.bindingHandlers.foreach.update(element, newValue, allBindingsAccessor, viewModel, bindingContext);
//if we have not previously marked this as initialized and there is currently items in the array, then cache on the element that it has been initialized
if (!ko.utils.domData.get(element, key) && (value.data || value.length)) {
ko.utils.domData.set(element, key, true);
}
return { controlsDescendantBindings: true };
}
};
Related
Select2 allows to improve standard <select> by applying on some style and feature.
I have to display Select2 styled dropdown list, custom binded with Knockout.
It is a phone number list, and user could add some entries.
I want it to be editable.
The user should be able to select an entry or type a new one wich can be used in the application (binded with manager.selectedPhoneNumber)
So, in the HTML:
<body>
<select id="list" data-bind="
options: manager.getMyPhoneNumbers(),
optionsValue: 'id',
optionsText: 'text',
customBinding_phoneNumbersEditableList: manager.selectedPhoneNumber
">
</select>
</body>
And in Javascript:
ko.bindingHandlers.customBinding_phoneNumbersEditableList = {
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
// initialize select2 with binded element
$(element).select2({
//createSearchChoice: function (term, data) {
// if ($(data).filter(function () {
// return this.text.localeCompare(term) === 0;
// }).length === 0) {
// return {
// id: term,
// text: term
// };
// }
//},
formatResult: function (item) {
return buildSomePrettyString();
},
formatSelection: function (item) {
return buildSomePrettyString();
},
sortResults: function (results, container, query) {
return results;
}
});
},
update: function (element, valueAccessor, allBindings, viewModel, bindingContext) { }
};
Uncomment the function createSearchCoice does not work. It causes an exception Error: Option 'createSearchChoice' is not allowed for Select2 when attached to a <select> element.
Someone could help me ?
I have a slider which has min 1, max 24 and steps of 1. Is it possible I can make the steps as 1, 2, 3, 4, 12, 18, 24? That's all the values that will be shown when the slider changes left or right.
<input id="ex1" data-slider-id="ex1Slider" type="text" data-bind="sliderValue: {value: loanperiod, min:0, max: 24, step: 1, formatter:formatter1}, event: { change: $root.getInvestmentDetailsForBorrower }, valueUpdate: 'afterkeydown' " style="display: none;">
Slider has knockout.js binding.
If needed then I am adding the slider binding here
// Custom binding for slider value
(function ($) {
ko.bindingHandlers.sliderValue = {
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
var params = valueAccessor();
// Check whether the value observable is either placed directly or in the paramaters object.
if (!(ko.isObservable(params) || params['value']))
throw "You need to define an observable value for the sliderValue. Either pass the observable directly or as the 'value' field in the parameters.";
// Identify the value and initialize the slider
var valueObservable;
if (ko.isObservable(params)) {
valueObservable = params;
$(element).slider({value: ko.unwrap(params)});
}
else {
valueObservable = params['value'];
if (!Array.isArray(valueObservable)) {
// Replace the 'value' field in the options object with the actual value
params['value'] = ko.unwrap(valueObservable);
$(element).slider(params);
}
else {
valueObservable = [params['value'][0], params['value'][1]];
params['value'][0] = ko.unwrap(valueObservable[0]);
params['value'][1] = ko.unwrap(valueObservable[1]);
$(element).slider(params);
}
}
// Make sure we update the observable when changing the slider value
$(element).on('slide', function (ev) {
if (!Array.isArray(valueObservable)) {
valueObservable(ev.value);
}
else {
valueObservable[0](ev.value[0]);
valueObservable[1](ev.value[1]);
}
}).on('change', function (ev) {
if (!Array.isArray(valueObservable)) {
valueObservable(ev.value.newValue)
}
else {
valueObservable[0](ev.value.newValue[0]);
valueObservable[1](ev.value.newValue[1]);
}
});
// Clean up
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$(element).slider('destroy');
$(element).off('slide');
});
},
update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
var modelValue = valueAccessor();
var valueObservable;
if (ko.isObservable(modelValue))
valueObservable = modelValue;
else
valueObservable = modelValue['value'];
if (!Array.isArray(valueObservable)) {
$(element).slider('setValue', parseFloat(valueObservable()));
}
else {
$(element).slider('setValue', [parseFloat(valueObservable[0]()),parseFloat(valueObservable[1]())]);
}
}
};
})(jQuery);
// Custom binding for slider
(function ($) {
ko.bindingHandlers.slider = {
init: function (element, valueAccessor, allBindingsAccessor) {
var options = allBindingsAccessor().sliderOptions || {};
$(element).slider(options);
ko.utils.registerEventHandler(element, "slidechange", function (event, ui) {
var observable = valueAccessor();
observable(ui.value);
});
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).slider("destroy");
});
ko.utils.registerEventHandler(element, "slide", function (event, ui) {
var observable = valueAccessor();
observable(ui.value);
});
},
update: function (element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
if (isNaN(value)) value = 0;
$(element).slider("value", value);
}
};
})(jQuery);
I think you just want to have a function that takes the raw slider value and translates it to your custom values. Then use the result of that wherever you need the value.
vm = {
sliderValues: [1, 2, 3, 4, 12, 18, 24],
rawSliderValue: ko.observable(1),
sliderValue: ko.pureComputed(function() {
return vm.sliderValues[vm.rawSliderValue() - 1];
})
};
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<input type="range" min="1" data-bind="attr {max: sliderValues.length}, value: rawSliderValue, valueUpdate: 'input'" />
<div data-bind="text: sliderValue"></div>
I've got an Ajax call that's returning a "Team". A team is comprised of a TeamName, LeagueName, and a list of FootballPlayers which have a Name, Position, and TeamName. The Ajax call is correctly returning the Team JSON properly and I'm about to make Knockout bindings to the properties directly on the Team. However, I want to bind a jQuery DataTable to the list of players, but have been unsuccessful in doing so. I can tell the DataTables call is being made, as I can see some of the DataTables controls like "Previous 1 Next" but no data is in the table. I can deploy what I have out to a publicly visible site if that'll be helpful. Thank you!
jQuery: Version 2.1.1
Knockout: Version 3.2.0
DataTabes: Version
1.10.4
HTML Table
<table id="playerList">
<thead>
<tr>
<th>Player Name <span class="glyphicon glyphicon-sort-by-alphabet" aria-hidden="true"></span></th>
<th>Position <span class="glyphicon glyphicon-sort-by-alphabet" aria-hidden="true"></span></th>
<th>Team <span class="glyphicon glyphicon-sort-by-alphabet" aria-hidden="true"></span></th>
</tr>
</thead>
<!--<tbody data-bind='foreach: PlayerList'>-->
<tbody>
<tr>
<td data-bind="text: $data.PlayerList.Name"></td>
<td data-bind="text: $data.PlayerList.SelectedPosition"></td>
<td data-bind="text: $data.PlayerList.TeamName"></td>
</tr>
</tbody>
</table>
Controller Javascript
$('.retrieveTeam').click(function () {
_getData('RetrieveTeam', JSON.stringify({ TeamKey: $(this).data("teamkey") }));
});
function _getData(url, postdata) {
var request = $.ajax({
url: url,
type: 'POST',
data: postdata,
datatype: "json",
contentType: "application/json"
});
request.done(_requestDone);
request.fail(_failedRequest)
}
function _requestDone(result)
{
_bindTeam(result);
my.Views.TeamView.showTeamInfo();
}
function _bindTeam(data) {
if (!viewModel) {
viewModel = ko.mapping.fromJS(data, {}, this);
ko.applyBindings(viewModel);
my.Views.TeamView.applyDataTable('#playerList');
} else {
ko.mapping.fromJS(data, viewModel);
}
}
View Javascript
var applyDataTable = function applyDataTable(element) {
$(element).DataTable(
{
responsive: true,
bFilter: false,
bInfo: false,
bLengthChange: false
});
}
Here is how I have done it... this fiddle uses a custom DataTablesForEach binding and also includes a fork of knockout... I have created a pull request with the knockout repository on github. If this solution helps you, please leave a comment on my pull request to get this merged into knockout 3.4.. thanks!
jsfiddle
pull request #1856 knockout/knockout repository on github
ko.bindingHandlers.DataTablesForEach = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var nodes = Array.prototype.slice.call(element.childNodes, 0);
ko.utils.arrayForEach(nodes, function (node) {
if (node && node.nodeType !== 1) {
node.parentNode.removeChild(node);
}
});
return ko.bindingHandlers.foreach.init(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var value = ko.unwrap(valueAccessor()),
key = "DataTablesForEach_Initialized";
var newValue = function () {
return {
data: value.data || value,
beforeRenderAll: function (el, index, data) {
if (ko.utils.domData.get(element, key)) {
$(element).closest('table').DataTable().clear();
$(element).closest('table').DataTable().destroy();
}
},
afterRenderAll: function (el, index, data) {
$(element).closest('table').DataTable(value.options);
}
};
};
ko.bindingHandlers.foreach.update(element, newValue, allBindingsAccessor, viewModel, bindingContext);
//if we have not previously marked this as initialized and there is currently items in the array, then cache on the element that it has been initialized
if (!ko.utils.domData.get(element, key) && (value.data || value.length)) {
ko.utils.domData.set(element, key, true);
}
return { controlsDescendantBindings: true };
}
};
I have this jsfiddle which shows a table and some users with roles.
I want to have a modal form pop up when some clicks add roles etc.
There seems to be an error on the update property of this ko.bindingHandlers.modal function:
ko.bindingHandlers.modal = {
init: function (element, valueAccessor) {
$(element).modal({ show: false }).on("hidden", function () {
var data = valueAccessor();
if (ko.isWriteableObservable(data))
data(null);
});
return ko.bindingHandlers["with"].init.apply(this, arguments);
},
update: function (element, valueAccessor) {
var data = ko.unwrap(valueAccessor());
$(element).modal( data ? "show" : "hide" );
return ko.bindingHandlers["with"].update.apply(this, arguments); // Error on this line
}
};
I don't why this is happening, I have copied the code from Ryan Niemeyer dev video
Its 34mins in.
It's a Bootstrap modal dialogue, using Knockout JS as the binding library
The with binding does no longer have the update function
From the init function use
ko.applyBindingsToNode(element, { with: valueAccessor() });
Update
ko.bindingHandlers.modal = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
$(element).modal({ show: false }).on("hidden", function () {
var data = valueAccessor();
if (ko.isWriteableObservable(data))
data(null);
});
ko.applyBindingsToNode(element, { with: valueAccessor() }, bindingContext);
return { controlsDescendantBindings: true };
},
update: function (element, valueAccessor) {
var data = ko.unwrap(valueAccessor());
$(element).modal( data ? "show" : "hide" );;
}
};
I have the following code:
<div class="icon-upload" data-bind="click: button('upload') "> Upload </div>
ko.bindingHandlers.button = {
init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext)
{
//alert('works');
console.log(element);
}
};
But I keep getting a button is not defined error. I'm trying to do an on('click') event with a parameter to determine latter what binding to initialize.
For example, when clicking on button('upload') I want to initialize the following binding
ko.bindingHandlers.image = {
init: function (element, valueAccessor, allBindingsAccessor, context)
{
var value = ko.utils.unwrapObservable(valueAccessor()),
$element = $(element);
console.log($element)
$element.html(value);
/*$element.pluploadQueue({
runtime: 'gears, browserplus, html5, flash, html4',
max_file_size: '10mb',
max_file_count: 10,
chunk_size: '1mb',
unique_names: true,
multiple_queues: true,
drop_element: true,
dragdrop: true,
filters : [
{title : "Image files", extensions : "jpg,gif,png"}
]
});*/
}
};
Do I must wrap the click code in a ViewModel = function() { like in
http://jsfiddle.net/jearles/xSKyR/
http://jsfiddle.net/FpSWb/ ?
Can't I do it the way I'm trying to do it?
You doing it wrong. You should create custom binding button which will be triggered on click event.
ko.bindingHandlers.button = {
init: function (element) {
$(element).click(function() {
// Your logic
});
}
update: function(element, valueAccessor, allBindingsAccessor) {
switch(ko.utils.unwrapObservable(valueAccessor())) {
case 'upload': ...
}
}
}
In view
<div class="icon-upload" data-bind="button: 'upload'"> Upload </div>