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" );;
}
};
Related
I am using knockout to make a custom binding for analytics.track, but it seems to be having trouble. It seems if the analytics.track is nested in more than 2 functions the track call fails silently. It doesn't hit the callback and it doesn't report in segments debugger. I have provided 2 examples demonstrating the problem here:
Without Closure (works):
function sendTrack(event, props) {
console.log("Enter sendTrack");
analytics.track('Signed Up', {
plan: 'Enterprise'
}, {}, function () {
console.log('track callback logged');
});
}
ko.bindingHandlers.segmentTrack = {
init: function (element, valueAccessor) {
console.log("Init");
var value = ko.unwrap(valueAccessor());
ko.applyBindingsToNode(element, { click: sendTrack });
}
};
ko.applyBindings({});
With Closure (doesn't work):
(function(ko, $, analytics){
'use strict';
function sendTrack(event, props) {
console.log("Enter sendTrack");
analytics.track('Signed Up', {
plan: 'Enterprise'
}, {}, function () {
console.log('track callback logged');
});
}
ko.bindingHandlers.segmentTrack = {
init: function (element, valueAccessor) {
console.log("Init");
var value = ko.unwrap(valueAccessor());
ko.applyBindingsToNode(element, { click: sendTrack });
}
};
ko.applyBindings({});
})(window.ko, window.jQuery, window.analytics);
Edit1: Also note this works with if I move the analytics.track to init:
(function(ko, $, analytics){
'use strict';
ko.bindingHandlers.segmentTrack = {
init: function (element, valueAccessor) {
console.log("Init");
analytics.track('Signed Up', {
plan: 'Enterprise'
}, {}, function () {
console.log('track callback logged');
});
}
};
ko.applyBindings({});
})(window.ko, window.jQuery, window.analytics);
Please advise
This is very likely because of the order things load / are initialized on the window object. Because the iife executes immediately, the analytics variable will be set to whatever window.analytics is the moment the iife is encountered by the browser. In the first case, window.analytics will be resolved when the code is run.
Put differently: the closure captures window.analytics in a scoped analytics variable at the time the iife executes.
Here's a demo showing the problem.
Without closure:
function sendTrack() {
console.log("Tracking...");
analytics.track("stuff");
}
ko.bindingHandlers.segmentTrack = {
init: function(element) {
console.log("Init");
ko.applyBindingsToNode(element, { click: sendTrack });
}
}
ko.applyBindings({ });
// Simulate loading analytics now:
window.analytics = { track: function(txt) { console.log("Tracking " + txt); } };
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script>
<div data-bind="segmentTrack: true">CLICK ME</div>
vs with closure:
(function(ko, analytics) {
function sendTrack() {
console.log("Tracking...");
analytics.track("stuff");
}
ko.bindingHandlers.segmentTrack = {
init: function(element) {
console.log("Init");
ko.applyBindingsToNode(element, { click: sendTrack });
}
}
ko.applyBindings({});
})(window.ko, window.analytics); // window.analytics isn't quite okay yet!
// Simulate loading analytics now:
window.analytics = { track: function(txt) { console.log("Tracking " + txt); } };
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script>
<div data-bind="segmentTrack: true">CLICK ME</div>
True, in my examples the second scenario throws an error whereas you mention in the question nothing happens, but then again the question doesn't contain an actual repro so it's hard to tell where that difference lies.
So analytics.js asynchronously loads its self in the page. In the mean time it queues all calls to the API with a snub version of the object. Once analytics.js loads it executes all the calls in the queue. Then redefines its self, breaking all refs to the original window.analytics. So any calls that are encountered fast enough to My only work around for this is to make my exposer a function call that returns the current version of the window.analytics.
(function (ko, $, analytics) {
function sendTrack(event, props) {
analytics().track(event, props);
}
ko.bindingHandlers.segmentTrack = {
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
var value = ko.unwrap(valueAccessor());
ko.applyBindingsToNode(element, { click: function () { sendTrack(value.event, value.options) }});
}
}
})(window.ko, window.jQuery, function () { return window.analytics; });
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 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 };
}
};
I have a custom knockoutJs binding for the date picker.
ko.bindingHandlers.valueAsDatePicker = {...}
When the bound input field status (enabled/disabled) is bound to a KO observable it doesn't enable/disable the datepicker icon.
HTML
<input id="txtRequestedTo" type="text" placeholder="dd/mm/yyyy"
data-bind="valueAsDatePicker: reqDateTo, disable: reqDateFrom().length < 1" />
Custom Binding
ko.bindingHandlers.valueAsDatePicker = {
init: function(element, valueAccessor, allBindingsAccessor) {
ko.bindingHandlers.value.init(element, valueAccessor, allBindingsAccessor);
formatAndSetDateValue(element, valueAccessor, allBindingsAccessor);
// Init UI datepicker
var dateFormat = allBindingsAccessor.dateFormat
$(element).datepicker({
dateFormat: dateFormat,
changeMonth: true,
changeYear: true,
yearRange: '1900:' + new Date().getFullYear(),
maxDate: 0,
showOn: "button",
buttonImage: "Content/images/sprite-base/sprite/icon-calender.png",
buttonImageOnly: true,
constrainInput: false,
buttonText: ""
});
},
update: function(element, valueAccessor, allBindingsAccessor) {
// Use the value binding
ko.bindingHandlers.value.update(element, valueAccessor, allBindingsAccessor);
formatAndSetDateValue(element, valueAccessor, allBindingsAccessor);
valueAccessor().valueHasMutated();
}
};
I want the datepicker to be disabled if the element is disabled, and Vice Versa.
Thanks a million Robert,
Here is the solution that worked.
Using KnockOutJS V3.1
init: function (element, valueAccessor, allBindings) {
...
//Disable the datepicker if the item is disabled or enabled.
if (allBindings.has('disable')) {
if (allBindings.get('disable')()) {
$(element).datepicker('disable');
}
else {
$(element).datepicker('enable');
}
var subscription = allBindings.get('disable').subscribe(function (newValue) {
if (newValue) {
$(element).datepicker('disable');
} else {
$(element).datepicker('enable');
}
});
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
subscription.dispose();
});
}
}
You have to use
if (allBindings.has('disable')) {
otherwise
allBindings.get('disable')
will be undefined as not all datepicker bound fields in the view have the disabled attribute.
The trick here is to get access to the disabled binding as an observable so you can attach a manual subscription. At the moment the result of the disable expression is passed into bindingHandler ( via allBindingAccessor ).
One you get a subscription you can invoke DatePicker's disable option
HTML
<input id="txtRequestedTo" type="text" placeholder="dd/mm/yyyy"
data-bind="valueAsDatePicker: reqDateTo, disable: reqDateFrom.disabled" />
JAVASCRIPT
var reqDateFrom = ko.observable();
var reqDateTo = ko.observable();
reqDateTo.disabled = ko.computed( function() {
return (reqDateFrom() || '').length === 0;
} );
ko.bindingHandlers.valueAsDatePicker {
init: function(element, valueAccessor, allBindingsAccessor) {
....
// ko 3.0 syntax. For 2.x use allBindingAccessor['disable']
var subscription = allBindingAccessor.get('disable').subscribe( function(newValue) {
$(element).datepicker('option', 'disabled', newValue);
});
// Make sure the subscription is disposed when the element is torn down
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
subscription.dispose();
});
},
update: function(element, valueAccessor, allBindingsAccessor) {
}
}
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>