when using the click bind in knockout, how does knockout know to pass the correct parameter to the method its bound to?
<div id="test" data-bind="click: runTest"/>
</div>
self.runTest = function (coolParameter){
doSomethingCool();
}
When calling your handler, Knockout will supply the current model
value as the first parameter. This is particularly useful if you’re
rendering some UI for each item in a collection, and you need to know
which item’s UI was clicked.
from the documentation
There also is some discussion in the docs about how to pass more parameters by adding a wrapping function
<button data-bind="click: function(data, event) {
myFunction('param1', 'param2', data, event)
}">
Click me
</button>
knockout understands which value to pass from context. it's the current model object. e.g if you're in a foreach knockout passes the current item.
Related
I am using Knockout-Kendo.js to bind Kendo widgets to Knockout observables. I have a KendoListView that populates itself from html template based on a observable named "Filters". The problem I am having is that the input control that I am using inside the template is not converting into a Kendo widget, even though I am specifying it as a kendoComboBox. (I have left out properties of dataSource, etc for simplicity)
Other things to take note of.
On page load, there are no objects in the Filters observable property
in the Model.
ko.ApplyBindings(Model) is called within the Document ready function.
Users make various selections on the page, which then populates the
Filter observable in the model.
The controls do show up in DOM when Filters are added, but as native Html controls.
var Model = {
Filters: ko.observable([]),
FilterItemTemplate: function () {
return kendo.template($("#FilterItemTemplate").html())
}
}
<div id="Filters" data-bind="kendoListView: { data: Filters, template: FilterItemTemplate()}" ></div>
<script type="text/html" id="FilterItemTemplate">
<div>
<h4>#=ControlLabel#</h4>
<input id="#=ControlID#" name="FilterControl" data-bind="kendoComboBox: {}" />
</div>
</script>
What I wind up doing is
Removed the ListView from the Filters div.
Since the ListView is now removed, i subscribed to the Filters observable in my model with my javascript code. So whenever something is loaded or removed from the Filters observable, the function listener will be invoked.
Within the listener function manually created the Kendo widgets and append to the Filters div.
Scenario
I have a button (created via a directive) that looks like below (after being rendered via the directive):
<button class="btn btn-sm btn-primary" create-element="row" ng-click="setSettings([{span: 6}, {span: 6}], create);" settings="{}" data-dismiss="modal">One half block<button>
Note: The settings attribute of the button is bound to $scope.settings object.
The code in my controller is as follows:
$scope.settings = {}; // the settings object that I want to bind to the button
// function to be called if I'd want to set some data to the settings variable from the button
$scope.setSettings = function (setting, callback){
$scope.settings = setting;
callback();
};
$scope.create = function () {
$log.log($attrs.settings); // always reflects data set from previous click event
var settings = $attrs.settings;
var type = $attrs.createElement;
var element = DOMService(type, settings);
getTarget().append($compile(element)($scope));
};
Problem
I'd want to produce a two way binding via the ng-click directive so that any change in the variable settings is reflected in one of the buttons own attributes.
The first click on the button does change the $scope.settings object but the change isn't there in the button's settings attribute. So, upon every second click, the data that was set in the previous click is reflected in the attribute.
When the button is clicked, it calls the setSettings() function via which I'm passing an arbitrary settings object, and soon after, the callback is called. The problem is that when the settings object is set to some value with the click on the button, I cannot reflect the change occurring in it immediately in the view.
I know that a two way binding for instance on an input[type=text] is possible via ng-model directive. So I'm able to produce a two way binding as such:
<input class="input-sm form-control" ng-model="settings" placeholder="Title" />
and to reflect the change (in settings object), I can echo the object out in, say, another button in this way:
<button ng-click="create()" settings="settings" data-dismiss="modal">Create</button>
So, how do I achieve the same two-way binding through the ng-click directive. And if, the way I'm wanting to achieve this is wrong, then what is the appropriate one?
The goal
Get an object with submit binding of KnockoutJS.
The problem
I need to get the object instead of the element when I submit some form.
Here, on jsFiddle, open your console and then click on add button of some item. You'll receive the Products object and here everything is right. But here, also in jsFiddle, when you click on add button your response will be the element instead of the object — and I need the object.
The difference between the codes
Look to this function when I add:
self.add = function (item) {
var i = self.products.indexOf(item);
self.products()[i].isAdded(true);
};
But, when the binding is submit, the item parameter is different from response that click binding returns.
My scenario
In my real application, there is two ViewModels like this. I thought it would be simpler, but unfortunately, it isn't.
Someone have any idea?
You need to pass in the $data object when calling the function on the submit. Otherwise it will automatically pass the form object.
For example:
<!-- ko ifnot:isAdded -->
<form data-bind="submit: function() { $parent.add($data); }">
<button data-bind="ifnot:isAdded" class="btn btn-small action add">
<i class="icon-plus">Add</i>
</button>
</form>
<!-- /ko -->
Here's a working update to your fiddle: http://jsfiddle.net/G8zPT/4/
See edit at the bottom.
My company has a huge code base and we want to start using knockout more effectively. However, we have validation code in place already that takes care of all aspects of client-side validation. It uses jQuery to show validation error messages and to sanitize user input.
For example, if I add the class "validate-range" to an input, it will use jQuery change/focusout events to track changes and then if a value is out of the range, it will replace it with the min/max value using $(input).val(). Since this validation code makes changes this way programmatically, my knockout view model won't be updated when these kind of changes are made.
This validation code is used everywhere in the system, and can't be replaced at the moment, so in order to use knockout, I have to make it work along side this code. What i've tried so far is creating a custom value binding that adds an additional change event handler which is used to update the view model whenever the validation code changes an input's value.
This works surprisingly well in all cases except inside a foreach binding (which is the same as using the template/with binding I would imagine). My change event handler isn't being fired on any inputs inside the foreach that use the custom value binding, even though the custom binding is being reapplied to all inputs inside the foreach every time the observable array changes.
I was hoping someone has dealt with this problem before, having to make knockout work with existing javascript code that changes DOM values, and thus doesn't update the view model. Any help is greatly appreciated.
Javascript code for custom binding, creating view model, and old validation code:
// custom value binding for amounts
ko.bindingHandlers.amountValue = {
init: function (element, valueAccessor) {
var underlyingObservable = valueAccessor(),
interceptor = ko.computed({
read: function () {
var value = underlyingObservable();
return formatAmount(value);
},
write: function (newValue) {
var current = underlyingObservable(),
valueToWrite = parseAmount(newValue);
if (valueToWrite !== current)
underlyingObservable(valueToWrite);
else if (newValue !== current.toString())
underlyingObservable.valueHasMutated();
}
});
// i apply a change event handler when applying the bindings which calls the write function of the interceptor.
// the intention is to have the change handler be called anytime the old validation code changes an input box's value via
// $(input).val("new value"); In the case of the foreach binding, whenever the observable array changes, and the table rows
// are re-rendered, this code does get ran when re-applying the bindings, however the change handler doesn't get called when values are changed.
ko.applyBindingsToNode(element, { value: interceptor, event: { change: function () { interceptor($(element).val()); } } });
}
};
// view model creation
// auto create ko view model from json sent from server
$(function () {
viewModel = ko.mapping.fromJS(jsonModel);
ko.applyBindings(viewModel);
});
// old validation code
$(document).on("focusout", ".validate-range", function () {
var $element = $(this),
val = $element.val(),
min = $element.attr("data-val-range-min"),
max = $element.attr("data-val-range-max");
if (val < min)
// my change handler from custom binding doesn't fire after this to update view model
$element.val(min);
if (val > max)
// my change handler from custom binding doesn't fire after this to update view model
$element.val(max);
// more code to show error message
});
HTML code that uses the custom binding inside of a foreach binding:
<table>
<thead>
<tr>
<td>Payment Amount</td>
</tr>
</thead>
<tbody data-bind="foreach: Payments">
<tr>
<td><input type="text" class="validate-range" data-val-range-min="0" data-val-range-max="9999999" data-bind="amountValue: Amount" /></td>
</tr>
</tbody>
</table>
So in the above example, if I enter "-155" in an amount text box, my custom binding runs and sets the view model Amount to -155. Then the old validation runs and re-sets the value of the textbox to "0" with $(input).val(0). My view model doesn't get updated at this point, and still reflects the -155 value. My change event handler from the custom binding is supposed to be ran to update the view model to 0, but it doesn't.
Edit:
As pointed out in the answer, .val() does not trigger any change events. The change event handler I added didn't do anything. The reason the view model was being updated when the validation code changed a value outside of the foreach binding was because we had logic somewhere else in our javascript code that was manually triggering the change event using the blur event, which in turn triggered my custom binding to run and update the view model. This blur event handler was directly bound to the text boxes, instead of being delegated, so it worked for text boxes that were there when the page is first rendered, but not for the ones dynamically inserted by the foreach binding.
For now, I just changed this logic to delegate the events within the document, so it would include dynamically inserted text boxes, and it seems to be working fine. I'm hoping to come up with a better solution in the future.
Calling $(element).val("some value"); does not trigger the change event.
You would need to do: $(element).val("some value").change();
I have the following knock out js template:
<select id="ddlExpertise" data-bind="options: expertiseList(), optionsCaption: 'All', value: expertiseField" ></select>
Is there a way to add 'event: { change: doFilter }' to the template from the JavaScript / knockout library?
I don't want to put it in the template itself initially.
If I understand you correctly you want to react to change in the select and call a doFilter method. The proper KO "way" to achieve this would be to subscribe to changes on the expertiseField like so:
this.expertiseField.subscribe(function (newValue) {
doFilter(newValue);
});
Every time someone selects a new value on the select KO will update the expertiseField and your subscribe function will be called. It receives the new value allowing you to filter something or whatever. KO internally uses the change event to do the two-way data binding.
Hope this helps.