Knockout checked binding issue - asp.net-mvc-3

I'm having a problem with checkbox bindings not quite working with KnockoutJS 2.0. I have an array of objects. One of the properties of those objects is an array of different objects. In the child objects there are a few properties, one of which is a boolean. I build a list for each parent object and under each parent I show the children. For each list of children I have two views, a read only and an edit view. In the read only I have images that represent whether or not the item is checked based on the boolean property. This works and if I update the boolean value through the console, I'm seeing what I would expect--the image goes away or displays based on the value I assign. In the edit view, the images are replaced with a checkbox. I see the same behavior when I update the value through the console--it is checked when I expect it to be and not when I don't. The problem comes in when I check or uncheck the checkbox. Doing this doesn't change the underlying value the checkbox is bound to.
Here's the basic idea of my data.
[
{
"xxx": "yyy",
"xxx": "yyy",
...
"Displays": [
{
"xxx": "yyy",
...
"Excluded": false,
},
{
"xxx": "yyy",
...
"Excluded": true,
}
],
}
]
Here's the binding
<input type="checkbox" data-bind="checked: !Excluded()" />

the problem is that "checked" here is a bidirectional binding: the bound property needs to be read to generate the correct view, but needs also to be updated when you click on the checkbox. Contrast this to a binding like:
<span data-bind="text: 'your name is ' + name()"></span>
when the expression is only read, so you can use an expression (and you need to unwrap the observable).
So, you need to bind directly to the observable property, without "unwrapping" it adding '()', it will be done by knockout when needed, both for read and write:
<input type="checkbox" data-bind="checked: Excluded" />
See http://jsfiddle.net/saurus/usKwA/ for a simple example. Note how the checkbox labels are updated on change, showing that the model is updated and the rendering triggers correctly.
If you need to negate the value (so that the checkbox is checked when the value is false), you can add a writeable computed observable, as explained on http://knockoutjs.com/documentation/computedObservables.html section "Writeable computed observables", or you can negate the data in the viewmodel, doing it on the server just before sending the data, or on the client before populating the viewmodel.
hope this helps.

I know my answer is a bit late to the game here, but I had this problem today and this was the closest thread I could find related to the problem, and it doesn't seem to have an answer that solves it. So here's my solution.
Essentially, the issue is that knockout really wants your viewModel values to be a string, not a boolean, but this isn't always practical. So, I created a binding called "isChecked" which works strictly with booleans. Note: This will only work with observable properties.
ko.bindingHandlers.isChecked = {
getElementDeclaredValue: function (element) {
var declaredValue = element.getAttribute("value");
// If a value is provided, we presume it represents "true",
// unless its explicitly "false". If no value is provided, we
// presume that a checked state would equal "true".
return declaredValue && Boolean.isBool(declaredValue)
? Boolean.parse(declaredValue)
: true;
},
init: function (element, valueAccessor) {
var updateHandler = function () {
var declaredValue = ko.bindingHandlers.isChecked.getElementDeclaredValue(element);
var elementValue = element.checked ? declaredValue : !declaredValue;
var modelValue = valueAccessor();
var currentValue = ko.utils.unwrapObservable(modelValue);
if (elementValue === currentValue)
return;
if (ko.isObservable(modelValue)) {
modelValue(elementValue);
}
};
ko.utils.registerEventHandler(element, "click", updateHandler);
},
update: function (element, valueAccessor) {
var elementValue = ko.bindingHandlers.isChecked.getElementDeclaredValue(element);
element.checked = elementValue === ko.utils.unwrapObservable(valueAccessor());
}
};
The two Boolean methods ("parse" and "isBool") are defined as follows:
Boolean.isBool = function (value) {
return (/^(?:true|false)$/i).test(value);
};
Boolean.parse = function (value) {
return (/^true$/i).test(value);
};
I'll ignore any comments that say I shouldn't be modifying a built-in object prototype; I'll do as I damn well please ;-).
Usage is the same as the checked binding. The "value" attribute is optional, unless you want the checked state to represent false:
<input type="radio" id="rbNewClaim" name="ClaimType" value="false" data-bind="checked: isExistingClaim" />
Hope this helps someone.

I gave up trying to get this to work with the bool values and created an array of selected objects and handled it that way. It isn't the optimal solution, but I was tired of fighting this.

Related

Function.bind used in event binding will always re-render because it is not pure

Working on render performance on React, wonder what is the best way to tackle this performance issue. (The code is overly simplified for clarity)
var TodoList = React.createClass({
getInitialState: function () {
return { todos: Immutable.List(['Buy milk', 'Buy eggs']) };
},
onTodoChange: function (index, newValue) {
this.setState({
todos: this.state.todos.set(index, newValue)
});
},
render: function () {
return (
this.state.todos.map((todo, index) =>
<TodoItem
value={todo}
onChange={this.onTodoChange.bind(null, index)} />
)
);
}
});
Assume only one single todo item has been changed. First, TodoList.render() will be called and start re-render the list again. Since TodoItem.onChange is binding to a event handler thru bind, thus, TodoItem.onChange will have a new value every time TodoList.render() is called. That means, even though we applied React.addons.PureRenderMixin to TodoItem.mixins, not one but all TodoItem will be re-rendered even when their value has not been changed.
I believe there are multiple ways to solve this, I am looking for an elegant way to solve the issue.
When looping through UI components in React, you need to use the key property. This allows for like-for-like comparisons. You will probably have seen the following warning in the console.
Warning: Each child in an array or iterator should have a unique "key" prop.
It's tempting to use the index property as the key, and if the list is static this may be a good choice (if only to get rid of the warning). However if the list is dynamic, you need a better key. In this case, I'd opt for the value of the todo item itself.
render: function () {
return (
this.state.todos.map((todo, index) => (
<TodoItem
key={todo}
value={todo}
onChange={this.onTodoChange.bind(null, index)}
/>
))
);
}
Finally, I think your conjecture about the nature of the onChange property is off the mark. Yes it will be a different property each time it is rendered. But the property itself has no rendering effect, so it doesn't come into play in the virtual DOM comparison.
UPDATE
(This answer has been updated based on the conversation below.)
Whilst it's true that a change to a non-render based prop like onChange won't trigger a re-render, it will trigger a virtual DOM comparison. Depending on the size of your app, this may be expensive or it may be trivial.
Should it be necessary to avoid this comparison, you'll need to implement the component's shouldComponentUpdate method to ignore any changes to non-render based props. e.g.
shouldComponentUpdate(nextProps) {
const ignoreProps = [ 'onChange' ];
const keys = Object.keys(this.props)
.filter((k) => ignoreProps.indexOf(k) === -1);
const keysNext = Object.keys(nextProps)
.filter((k) => ignoreProps.indexOf(k) === -1);
return keysNext.length !== keys.length ||
keysNext.some((k) => nextProps[k] !== this.props[k]);
}
If using state, you'll also need to compare nextState to this.state.

How to trigger DataBinding Validation for all Controls?

I have an OpenUI5 form consisting of a number of Inputcontrols. These Inputcontrols are bound to a model using the OpenUI5 DataBinding as described in the documentation.
For example:
new sap.m.Input({
value: {
path: "/Position/Bezeichnung",
type: new sap.ui.model.type.String(null, {
minLength: 1,
maxLength: 128
})
}
})
As in the example above I'm using constraints on the stringlength.
When a User changes the Value of the Input, the Validation is triggered and according to the Validationresult one of the functions descripted here is called.
In these functions I'm setting the ValueState of the control like this:
setupValidation: function() {
var oCore = sap.ui.getCore();
oCore.attachValidationError(function (oEvent) {
oEvent.getParameter("element").setValueState(sap.ui.core.ValueState.Error);
});
oCore.attachValidationSuccess(function (oEvent) {
oEvent.getParameter("element").setValueState(sap.ui.core.ValueState.None);
});
oCore.attachFormatError(function (oEvent) {
oEvent.getParameter("element").setValueState(sap.ui.core.ValueState.Error);
});
oCore.attachParseError(function (oEvent) {
oEvent.getParameter("element").setValueState(sap.ui.core.ValueState.Error);
});
},
Let's assume the bound model variable is initial.
I'm loading the view, the property value is parsed and displayed as empty.
The Validationerror/Parseerror method is not called although the constraints are not met.
This seems to be standard behaviour of OpenUI5. Only changes in the Control will be a validated.
Now let's assume I've a submit button and the Value of the Inputcontrol is still empty. When the user hits the submit button I'd like to trigger the DataBinding Validation for all childcontrols of my view. This would validate the above mentioned input and would result in an errorstate.
My question is: How can I trigger the databinding validation for all childcontrols of my view?
There is another question on SO where the poster asks for a way to define required fields. The proposed solution is to call getValue() on the control and validate the value manually. I think this is kind of cumbersome as formating and constraint information and logic is already present.
I suggest looking into field groups.
An example here in the UI5 docs
Field Groups allow you to assign group IDs to the input fields. Then you can call all of the input fields at once. You can set the name property and required property on each <Input> separately in your view, allowing you to handle some logic when you perform validation.
You can call this.getView().getControlsByFieldGroupId("fieldGroupId"), which will return an array of the input controls. Then you can loop through the controls, pass them through your logic, and use setValueState() to show the results.
Or, you can assign the validateFieldGroup event on the parent container, which is usually a form, but can be anything like a <VBox> that contains the controls. When the users focus moves out of the field group, the event is fired. You can then use the event handler in your controller to perform the validation.
In your case, I would assign a press event to your submit button, and in the handler, call the field group by ID and loop through the controls. At the end of your function, check to see if all fields are validated before continuing.
View
<Input name="email" required="true" value="{/user/email}" fieldGroupIds="fgUser"/>
<Input name="firstName" required="false" value="{/user/firstName"} fieldGroupIds="fgUser"/>
<Button text="Submit" press="onSubmit"/>
Controller
onSubmit: function() {
var aControls = this.getView().getControlsByFieldGroupId("fgUser");
aControls.forEach(function(oControl) {
if (oControl.getRequired()) {
//do validation
oControl.setValueState("Error");
oControl.setValueStateText("Required Field");
}
if (oControl.getName() === "firstName") {
//do validation
oControl.setValueState("Success");
}
});
var bValidated = aControls.every(function(oControl) {
return oControl.getValueState() === "Success";
});
if (bValidated) {
//do submit
}
}
The concept goes like this.
Use custom types while binding, to define validations. Validation
rules go inside these custom types (in the method 'validateValue').
When Submit is pressed, loop through the control hierarchy and
validate each control in your view. (By calling 'validateValue'
method of the Custom Type).
Validator (https://github.com/qualiture/ui5-validator ) uses this concept and it is a small library to make your life easy. Its main advantage is that it recursively traverses through the control library.
Using Message Manager (using sap.ui.get.core().getMessageManager() ) is the way to show the validation messages on the UI control.
Triggering data binding validations is not possible. Rather for empty fields that are having required property true you can do a work around using jQuery.
Please refer my answer to this same problem at Checking required fields

Backbone.js: avoid view→model→view double conversion

I’m trying to build this:
When I edit field on the left it should update the one on the right and vice-versa.
Editing a value in an input field causes the text cursor to jump at the end of it.
Typing "2" in the fahrenheit field gets replaced with 1.999999999999, as you can see on the screenshot. This happens because of the double conversion:
view’s Fº → model’s Cº → view’s Fº.
How can I avoid that?
Update:
I want to know the elegant way of dealing with two-way bindings in MVC frameworks such as Backbone.js.
MVC
var Temperature = Backbone.Model.extend({
defaults: {
celsius: 0
},
fahrenheit: function(value) {
if (typeof value == 'undefined') {
return this.c2f(this.get('celsius'));
}
value = parseFloat(value);
this.set('celsius', this.f2c(value));
},
c2f: function(c) {
return 9/5 * c + 32;
},
f2c: function(f) {
return 5/9 * (f - 32);
}
});
var TemperatureView = Backbone.View.extend({
el: document.body,
model: new Temperature(),
events: {
"input #celsius": "updateCelsius",
"input #fahrenheit": "updateFahrenheit"
},
initialize: function() {
this.listenTo(this.model, 'change:celsius', this.render);
this.render();
},
render: function() {
this.$('#celsius').val(this.model.get('celsius'));
this.$('#fahrenheit').val(this.model.fahrenheit());
},
updateCelsius: function(event) {
this.model.set('celsius', event.target.value);
},
updateFahrenheit: function(event) {
this.model.fahrenheit(event.target.value);
}
});
var temperatureView = new TemperatureView();
No MVC
celsius.oninput = function(e) {
fahrenheit.value = c2f(e.target.value)
}
fahrenheit.oninput = function(e) {
celsius.value = f2c(e.target.value)
}
function c2f(c) {
return 9/5 * parseFloat(c) + 32;
}
function f2c(f) {
return 5/9 * (f - 32);
}
Not only it fixes the problem, it’s also reduces the code 3.5⨉. Clearly I’m doing MVC wrong.
Here's my take on this; instead rendering the whole view on every change, in interactive views, use the view's jQuery or plain JS contexts just like your non-MVC example.
http://jsbin.com/fomugixe/1/edit
As the Backbone docs say:
"Two way data-binding" is avoided. While it certainly makes for a nifty demo, and works for the most basic CRUD, it doesn't tend to be terribly useful in your real-world app. Sometimes you want to update on every keypress, sometimes on blur, sometimes when the panel is closed, and sometimes when the "save" button is clicked.
Two methods come to mind. As Kinakuta mentioned you can do something like the following so you're math works on integers, instead of decimals:
temp = ((oldTemp * 100) * conversion stuff) / 100
Depending on how complex you want your app to be you can also use something like Backbone.ModelBinder. It automatically binds your view to your model so when one updates, the other updates automatically. You can then attach a converter function to the binding so when your value goes view -> model or model -> view it's run through the converter. I can elaborate more if that idea interests you.
Update: With a simple temp converter it's not surprising that Backbone requires 3.5x as much code. An MVC framework can reduce bloat in a large project, but for a small app it might be overkill. e.g. imagine using Backbone to display "Hello World".
As for your issue, how about only rendering the other input value when one is changed, instead of both? If F input changes, re-render value in C box. With ModelBinder I would do this by having two attributes in my model: tempF and tempC. When one is modified, I re-calculate the other and ModelBinder automatically displays it. Or you can go without MB and just listen for the change event.
set a variable at the view level where you hold the input field that started the conversion, so you don't call the conversion function on that field.

Kendo UI grid - batch update not executed

I'm implementing a simple (at least ,that was the goal) Kendo UI grid that displays two columns: one holding a checkbox, bound to a boolean, and one holding a display name for the item. The checkbox column has a simple template, and the change() event of the checkbox is handled so that the model in the datasource gets updated. I have verified this, and it works.
The data source has been configured for batch, and defines a transport for read and update. Both call a function that perform the ajax call. As I said before, the read function is handled as expected. However, the update function defined on the transport is not. The sync() on the datasource is triggered with a simple button whose click event is hooked to a function that calls datasource.sync() (or grid.saveChanges()).
transport: {
read: function(options) {
return loadStuff(options);
},
update: function (options) {
return updateStuff(options);
}
}
When debugging in the Kendo UI code, it looks like the models attribute on the ModelSet is always empty, and therefore the sync() decides that there's nothing to sync. Anyone got a clue what is happening here?
UPDATE:
Looks like something may be wrong when handling the checkbox check / uncheck. Apparently I should use something like
$('#divGrid').on('click', '.chkbx', function() {
var checked = $(this).is(':checked');
var grid = $('#divGrid').data().kendoGrid;
var dataItem = grid.dataItem($(this).closest('tr'));
dataItem.set("Selected", checked);
});
Unfortunately, it looks like the set() method is not defined on the data item. When debugging, it only contains the data, and no Model object having the set() method.
UPDATE 2:
Tried wrapping the data returned from the ajax call in a model defined with Model.define(). That seems to solve the issue of the model not being dirty, as the _modified property on the model returns true. However, the models array in the ModelSet remains empty. Is this a bug in Kendo UI, or am I going the wrong way?
You don't actually need to bind to click event on the checkboxes.
I´ve posted an example on using it in JSFiddle where you can see it running. This example displays in a grid two columns: first text (tick) and second boolean rendered as a checkbox (selected); the update is batch (so, it's pretty close to what you have).
Questions to keep in mind are:
For displaying the checkbox while not in edit mode, you should define a template, something like this. You might realize that the checkbox is in disabled state by default since you want to edit it as other fields (selecting the cell first). This also guarantees that the model is correctly updated:
{
field : "selected",
title : "Selected",
template: "<input type='checkbox' name='selected' #= selected ? 'checked' : '' # disabled/>"
}
Define in the model that this field is boolean:
schema : {
id : "id",
model: {
fields: {
symbol : { type: "string" },
selected: { type: "boolean" }
}
}
},
Define the transport.update function, something like:
transport: {
read : function (operation) {
// Your function for reading
},
update: function (operation) {
// Display modified data in an alert
alert("update" + JSON.stringify(operation.data.models, null, 4));
// Invoke updating function
// that should ends with an operation.success(the_new_data)
// In this example just say ok
operation.success(operation.data.models)
}
}
EDIT: If you want to be able to modify the checkbox state without having to enter in edit mode first, you should:
Remove the disabled from the template:
{
field : "selected",
title : "Selected",
template : "<input type='checkbox' name='selected' #= selected ? 'checked' : '' #/>"
},
Then bind the click event on checkboxes to the following handler function:
$("#stocks_tbl").on("click", "input:checkbox", function(ev) {
var dataItem = grid.dataItem($(this).closest('tr'));
dataItem.set("selected", this.checked);
});
Where #stocks_tbl is the id of the div that contains the grid. You might see it running here.
NOTE: It's important the on with the three parameters for making it live

MVC 3 Bind Model property to 2 fields (e.g. Other Title)

I'm trying to achieve a very common scenario whereas given a list of options to choose from, the last one says "Other" and when selected the user is presented with the input field to specify what "other" is.
In my case, it's a list of Person's titles:
public List<string> TitleList
{
get
{
return new List<string> { "Mr", "Mrs", "Miss", "Dr", "Other" };
}
}
and what I'm trying to do is this:
#Html.DropDownListFor(m => m.Title, new SelectList(Model.TitleList), "Please select...") #Html.TextBoxFor(m => m.Title)
I want the model to bind the TextBox value when "Other" is selected in the DropDownLis, and bind to selected item in DropDownList in all other cases.
Is this achievable without adding an extra property on the Model?
a better solution is not to bind to two fields, instead, copy selected item from a drop-down into bound textbox with some clever javascript:
#Html.DropDownList("ddlTitle", new SelectList(Model.TitleList), "Please select")
#Html.TextBoxFor(m => m.Title, new { maxLength = 10 })
Javascript:
ToggleTitleFields = function () {
var title, txtTitle;
title = $('select#ddlTitle').val();
txtTitle = $('input#Title');
if (title === "Other") {
txtTitle.val("");
txtTitle.show();
return txtTitle.focus();
} else {
txtTitle.hide();
txtTitle.val(title);
return $('span[data-valmsg-for="Title"]').empty();
}
};
$(document).on("change", "select#ddlTitle", function(e) {
return ToggleTitleFields();
});
hope this helps somebody
found a client-side solution: add/remove the "name" attribute from DropDownList. Here's my coffee script:
ToggleTitleFields = () ->
if $('select#Title').val() == "Other"
$('select#Title').removeAttr('name')
else
$('select#Title').attr('name','Title')
Tsar,
Being honest, this isn't a scenario that I've had to deal with before, but is nonetheless a good one. If I were faced with this dilemma and was allowed to use a javascript solution, i'd present a previously 'hidden' textbox when other was chosen. The user would then have to enter the new value into this texbox, which on loosing focus would populate the selectlist and be selected. Then when the form was submitted, you'd have this new value still as part of the exisiting model.
Of course, you could also do a similar logic of showing the textbox when selecting other but this time, do a little logic on the httpost controller action to determine if the 'other' item was selected and then populate the model from the 'textbox' value.
Of course, both scenarios would need a heap of validation, but in principle, either approach would 'work'

Resources