Knockout.js and twoway binding of checkboxes - javascript

First: I am new to knockout js, and trying to wrap my head around the knockout/mvvm way of thinking, so please forgive me if my question turns out to be trivial.
What I have is a the following case: I have a knockout viewmodel containing an observableArray of selected ticket objects. This array represents a user-defined selection/subset of a bigger set of tickets. The whole set of tickets are listed in a jqgrid table, and each row has a checkbox which is supposed to be telling whether each ticket is selected or not. This means that the value of the checkbox needs to be updated whenever the "selectedTickets" array changes. In addition to this I also want the user to be able to click each checkbox in order to add/remove a ticket from the selection. Would seem like a fairly acceptable piece of functionality, right?
I do however have trouble seeing how I could use the knockout "checked" binding in order to achieve this. My first idea was to use a computed/dependent observable on the viewmodel object called "isSelected" which would reflect changes in the selectedTickets array and return true or false based on whether a ticket is in the selectedTickets array or not. The first problem here is that I then need to pass a parameter to the computed observable saying which ticket ID it's supposed to look up, and from what I can see that only works on a writable computed observable. Getting the state for the checkbox does however not seem like a write operation, so something already started to smell. Next issue is that the binding needed to be twoway, as I wanted the user to be able to change the state of each checkbox and having the selectedTickets array being updated accordingly. This is a different operation, as it would actually remove/add tickets to the selectedTickets array. Which would again trigger the computed observable trying to set the state of the checkbox. Seems like these two use cases may end up like an infinite loop if I try to do it this way. I haven't found a good way of combining these two use cases by just using the checked binding for the checkboxes.
I could of course do the event handling on the checkboxes manually, by hooking up listeners to the changed event on the checkboxes and to the selectedTickets array in the knockout viewmodel, but I was hoping this was possible to more automated with knockout bindings.
Hope there are some knockout masters out there who can guide me onto a good path, as I feel I've steered off-track with this one.

When using knockout.js, you need to stop doing things by halves - if you have a list of items, the data belongs in the viewmodel (not only the select items), and only the appearance is defined by the view.
Thus, I'd recommend an observable array items of type Item, which has a property isSelected - the selected items can then be made accessible via a computed observable:
var Item = function(name) {
this.name = ko.observable(name);
this.isSelected = ko.observable(false);
};
var ViewModel = function() {
var self = this;
self.items = ko.observableArray([
new Item('Foo'), new Item('Bar'), new Item('Foo Bar')
]);
self.selectedItems = ko.computed(function() {
return ko.utils.arrayFilter(self.items(), function(item) {
return item.isSelected();
});
});
};
http://jsfiddle.net/htZfX/

Related

Knockout Mapping: Why does apply bindings clear values from my nested objects?

I have a KO model with a lot of nested objects and collections of nested objects. I use the ko.mappings mapping options to make sure they get properly generated during model creation, but the value gets cleared. Trying to start with a simple drop down menu and bind objects to it:
<select id="myList" name="SelectedSurvey" id="SelectedSurvey" class="form-control"
data-bind="options: AvailableSurveys, optionsText: 'Name', value: SelectedSurvey,
optionsCaption: '-- Select Survey --'"></select>
Then, take some data, when creating a new object on the page, it works fine, but when trying to edit an existing record from the same page, the drop down value is never selected. When I check viewModel.SelectedSurvey() the value is undefined, but it only becomes undefined after I callapplyBindings()`. Why is this happening? How can I fix it?
Here is a working example: http://jsfiddle.net/6wLcr52y/3/
If you open the console and run it, you'll see the log before applyBindings() is called the full ViewModel, and the nested object SelectedSurvey() are properly komapping objects that have values, but then after it's called. SelectedSurvey() becomes undefined and as such my dropdown list never has a selected value.
The SelectedValue parameter doesn't seem to want to accept an object as a valid value. It therefore clears the SelectedValue observable as soon as you apply bindings thinking that no valid value was provided.
As a workaround, I created a solution that uses the optionsValue parameter to accept the 'Id' property as a value. This will allow you to maintain control over which survey is selected by passing in merely an 'Id' value as opposed to an entire Survey object.
This requires that you update your Knockout version to 3.4.0. Are you locked in to using 2.2.1 for any reason?
I also updated your jsfiddle to include more readable implementation of the mapping plugin. Hopefully this will fix your issue:
var ViewModel = function(data) {
var self = this;
self.AvailableSurveys = ko.mapping.fromJS(data.AvailableSurveys);
self.SelectedId = ko.observable(data.SelectedSurvey.Id);
self.SelectedSurvey = ko.pureComputed(function () {
for (i = 0; i < self.AvailableSurveys().length; i++) {
if (self.AvailableSurveys()[i].Id() === self.SelectedId()) {
return self.AvailableSurveys()[i];
}
}
})
}
Fiddle: http://jsfiddle.net/dw1284/v1L5tw6h/3/

Best practice for ReactJS form components

I am looking for a best practice to have a ReactJS component responsible for the form for users to edit a given entity. Very simplified example here. Actual forms would in many cases have several more fields and more GUI functionality.
React.createClass({
getInitialState: function() {
return {
entity: {
property1: null,
property2: null
}
};
},
handleChange: function(e) {
var entity = this.state.entity;
switch(e.target.name) {
case 'property1':
entity.property1 = e.target.value;
break;
case 'property2':
entity.property2 = e.target.value;
break;
}
this.setState({
entity: entity
});
},
render: function() {
return (
<div className="entity-form">
<form onChange={this.handleChange}>
<input type="text" name="property1" value={this.state.entity.property1} />
<br />
<textarea name="property2" value={this.state.entity.property2}></textarea>
<br />
</form>
</div>
);
}
});
The fields of the form is directly editing an entity object, that could then be saved to a RESTful api. I want the component to be updated as the user change the fields, so the GUI could react based on the input during typing (like validations, info etc).
In theory, I could have the whole state object represent the entity that is being edited, so every property of the entity is first level state variables. However, I want to be able to add additional state variables for GUI functions and other things related to what the component is going to do, so I would prefer the entity object to be one state variable like the "entity" state variable above. The object could of course be some more complicated object, like a Backbone model or similar, but in this simplified example, I just use a simple object whit the required properties.
So, in search of the best practice way to make React components for this purpose, I have some questions:
Props or state.
In this case, I have chosen to put the entity object with the content for the form in a state variable instead of prop. This is to be able to update the object during form input without having to call the parent and update the props. As far as my React experience goes, that would be the best practice for a form component like this.
Controlled or uncontrolled inputs.
In the simplified example above, I use controlled inputs. This leads to updating the state and re-rendering the component on every change (like every character entered of a text field). Is this the best practice? The good thing is that the component has full control of what happens, instead of having defaultValue paramters, and on some event (like the user pressing a save button), the component extract the values, update the entity and save it to the server. Is there any reasons (or opinions) on if controlled or uncontrolled inputs should be used in cases like this?
onChange for the form or every input
The example has an onChange on the form tag, and it causes the handleChange method to be called every time any of the fields in the form is changed. However, since the inputs are controlled (have value parameters), React complains that the input fields does not have an onChange property. Does this mean having a common onChange on the form tag is bad practice, and I should remove it and put onChange on every single field instead?
Updating individual properties
In the above example, I use a switch based on what input field is being update (when handleChange is called). I guess I could instead make sure all field names is in sync with the property names of the entity, and I can set properties of the entity object in handleChange based on the name of the field from the event (e.target.name). However, this makes it hard to have individual needs per field, even if most fields just update an entity property directly. I guess an alternativ is a switch with a default block setting based on the name of the input, and case blocks for any field that require other ways of updating (like filtering the value before setting it on the entity). Please comment this if you know some much better way of handeling field updates this way.
Updating the state entity
One big problem of this example, is the way the entity object is updated. Since the entity variable in the handleChange is set to the entity object from current state, this is just a pointer, and updating the entity variable will change the object in state. The React pages say you should never update state directly. One of the reasons is something I have experienced when updating the state this way before calling setState. If having a shouldComponentUpdate method, the prevState contain the new state, since the content of the prevState argument sent to the shouldComponentUpdate is based on what was in the state when setState was called. As far as I know, there is no simple way to clone a object in javascript. So the question is, when having whole objects that I need to update properties of (and not touching the other values in the object) instead of just running setState of a single state variable, what is the best way to do this without causing theese kinds of state mixups?
Anything that is going to change goes in State.
If you're looking at loading an existing entity and editing it, you want controlled inputs, and you want to set the values accordingly. I tend to stay away from defaultValue in most cases (outside of dropdowns)
This ties back in to your previous question. If you specify a value, you are using a controlled input, and you have to provide an onChange handler for any controlled input, otherwise it is set in stone. A benefit of controlled inputs is that users can't edit them, so if you had some properties locked down (maybe for read only, security reasons), when the user attempts to save, even if they edited the HTML directly, React should pull the property's value from the vDOM representation (could be wrong here, but I believe I've tested this before). Anyway, you have to have onChange set directly on controlled inputs. As far as using event delgation (at the form level), this isn't a bad practice at all for a lot of events, this is just a specific scenario (controlled inputs) where you need onChange events specified for each element.
Not entirely sure what the ask on this one is, but I used refs instead of target.name.
So, you're correct in that you should never alter the state directly, and this is a tricky bit from the docs. React is going to alter state directly, it's just going to do it in the implementation through setState. If you alter state outside of this method call, unexpected things will happen and errors will be thrown.
shouldComponentUpdate only does shallow comparisons, but there are a few solutions here.
One is to stringify the objects, this is a quick and dirty object comparison, don't really recommend it, but it works.
A better solution, and one I have used with React + Flux is to implement a propertyChanged bool, and just check that in your shouldComponentUpdate.
Now, this will require you to be aware of setting it when things change, i.e., you changed something deeper in the object graph. Say propertyOne is an object with a property that gets changed in your handleChange method. You would validate the input however you wish, then set propertyChanged = true, and you then need to implement componentDidUpdate. We're making an assumption here, but if the component has updated, you set propertyChanged back to false so you don't have any further triggering of unwanted updates. I hope that makes sense. It's kinda like a one-way notifyPropertyChanged.
I'm providing a quick example of what I would probably do for a more dynamic implementation that allows you to add more properties to your object (only shallow properties in this implementation, obviously you could write a more robust solution). Let me know if you have any further questions or if I didn't answer something.
http://jsfiddle.net/rpv9trhh/
var e = {
prop1: 'test',
prop2: 'wee',
prop3: 'another property',
propFour: 'Oh yeah!'
};
var FormComp = React.createClass({
getInitialState: function(){
return {
entity: this.props.entity
}
},
render: function() {
var ent = this.state.entity;
var that = this;
var inputs = [];
for(var key in ent){
inputs.push(<input
key={key}
style={{display:'block'}}
type="text"
ref={key} onChange={that._propertyChanged.bind(null, key)}
value={ent[key]} />)
}
return <form>
{inputs}
<input
type="button"
onClick={this._saveChanges}
value="Save Changes" />
</form>;
},
_propertyChanged: function(propName) {
var nextProp = this.refs[propName].getDOMNode().value;
var nextEntity = this.state.entity;
nextEntity[propName] = nextProp;
this.setState({
entity: nextEntity
});
},
_saveChanges: function() {
var updatedEntity = this.state.entity;
for(var key in updatedEntity){
alert(updatedEntity[key]);
}
//TODO: Call to service to save the entity, i.e.
ActionCreators.saveEntity(updatedEntity);
}
});
React.renderComponent(<FormComp entity={e} />, document.body);

Correctly mapping array element using the Knockout mapping plugin

I have a viewModel that looks something like this when simplified:
var viewModel = function(){
var self = this;
self.selectedObject = ko.observable({});
self.getUnit = function(){
//get the selected object from the server side as json
self.selectedObject(ko.mapping.fromJS(data,mapping));
};
self.addObjectMember = function(){
self.selectedObject().objectMembers.push(new ObjectMemberViewModel(null, self.selectedObject()));
self.save = function(){
var data = ko.mapping.toJS(self.selectedObject);
//ship data to server
}
The data received from the server contains an array called objectMembers that has some properties inside it. The properties might differ between different objects.
My mapping object look like this:
var mapping = {
'objectMembers': {
create: function(options){
return new ObjectMemberViewModel(options.data, options.parent);
}
}
};
To see my problem, I'll give an example:
The user loads the page, and then fetches an object. That object includes two elements inside the objectMembers array. Then those are mapped using the ko.mapping.fromJS and everything works just fine. I can modify my viewModel using my model and the viewModel is updated. Then the user clicks a button that triggers the addObjectMember function, adding a third entry to the observableArray. I can interact with this also, and any changes done to my model can be seen in the viewModel as expected.
The problem comes when I click save. If I debug the save method, and check the contents of self.selectedObject, I can see that it contains what I want it to, but the object that is mapped back into the variable data has the last element in the objectsMembers array as an empty object ({}). The two other object look as I want them to.
I think I know why. The two first object have their __ko_mapping__.mappedProperties containing all the properties they had when it got mapped initially. The last one however has an empty __ko_mapping__.mappedProperties, and therefore I guess that no properties are mapped back.
So I need to do one of these things I guess:
When adding the object to the array on addObjectMember I need to get the __ko_mapping__.mappedProperties set so that it gets mapped back when I save.
When mapping back, I include all the properties on the mapped object regardless of their presence inside the __ko_mapping__.mappedProperties.
I have no clue how to do any of them that does not feel like a dirty hack, so any help here would be appreciated.
I ended up using ko.toJS instead of ko.mapping.toJS. ko.toJS does not care about __ko_mapping__, and maps everything (including __ko_mapping__).
That means it maps a bit more than I need, but other than that it works just fine.

Knockout Options Object Binding

I'm struggling with Knockout.js Options binding, to an Object.
I'm attempting to create a workflow for the user that allows them to add an Item, edit its properties and then save/cancel to propagate those changes.
I've accomplished this type of task before with jquery. However I'd like to avoid the complicated stack calls that jquery would require. (if possible).
I've created an example:
http://jsfiddle.net/nAE2f/
Whats working in the example:
The Add button, creates a new object.
The Save Button will save it to the array.
The Select Dialog will update with a new Option.
Unfortunately, this is where my progress has halted. While the Select Option is created, it doesn't reflect the underlying objects Name. Also Switching between objects doesn't change the forms properties as I would expect.
I've tried assigning the optionValue to the id, but in that case the Select Options isn't created on save.
The problem with the way you binding member name to item. In your case saved item always has empty name that's why select also display empty text. I fixed this by creating selectedMember property within ViewModel to handle selected member and assigning member name to item on Save.
Check fiddle for example

Am I overusing the Knockout mapping plugin by always using it to do my viewmodel?

I'm still learning the proper usage of Knockout and I've found myself quickly getting away from ever typing ko.observable when setting up my viewmodel and instead just defining an object literal and passing it through the mapping plugin with something like
var viewModel = ko.mapping.fromJS(data);
or at the very least, something along the lines of stuffing all of my data into an attribute on the viewModel like so
var viewModel = {
... events etc ... ,
"data": ko.mapping.fromJS(data)
}
To be honest, the main reason I've been doing this is to get around having to type ko.observable and ko.observableArray repetitively. I'm just trying to figure out if this is a good approach and if there are any downsides to dropping the specific var x = ko.observable() declaration all together. Also, I'm doing this all on load, not in response to any ajax call etc, which from what I can tell, is what the mapping plugin was designed for.
In your work with knockout, do you still declare the observables manually, one by one, or have you gone with the mapping.fromJS method that I use? Are there any specific downsides to using the mapping plugin so frequently like this?
Edit:
Specific Example
In this article, Steve sets up his viewModel by doing
var initialData = [ { ... } , { ... } ]; // json from the serializer
var viewModel = {
gifts : ko.observableArray(initialData)
};
Normally, I'd just use ko.mapping.fromJS for this situation as well, specifically to make sure the objects within the array are turned into observables as well. Looking at what he did, my approach seems like its overkill and adds a bit of unnecessary overhead.
After using Knockout for a little longer, I've noticed that the mapping plugin has some additional options that give you much more fine grained control over the mapping process.
Control type and amount of properties generated
There are several ways to accomplish this, and I'll go over some, but the end result is that you end up with a lighter result from the mapping plugin because everything isn't observable.
Basically you leave everything that you don't think will change, as a normal property and only make observables out of the specific items that you want to observe.
Make mapping omit certain properties
You can make the mapping plugin omit properties entirely from the end result by specifying things like ignore or include. Both of these accomplish the same thing, just in opposite ways.
Note: Samples are from the knockout.js mapping plugin documentation, comments added by me
Mapping Plugin Argument: include
The following snippet will omit all properties from the source object other than those passed in via the include argument.
// specify the specific properties to include as observables in the end result
var mapping = {
// only include these two properties
'include': ["propertyToInclude", "alsoIncludeThis"]
}
// viewModel will now only contain the two properties listed above,
// and they will be observable
var viewModel = ko.mapping.fromJS(data, mapping);
Mapping Plugin Argument: ignore
If you want to only omit certain properties from the source object, use the ignore argument as shown below. It will make observables from all properties in the source object except for the specified properties.
// specify the specific properties to omit from the result,
// all others will be made observable
var mapping = {
// only ignore these two properties
'ignore': ["propertyToIgnore", "alsoIgnoreThis"]
}
// viewModel will now omit the two properties listed above,
// everything else will be included and they will be an observable
var viewModel = ko.mapping.fromJS(data, mapping);
Control what properties are or are not made observable
If you need to include properties but you don't think that they will need to be made observable (for whatever reason), the mapping plugin has something that can help.
Mapping Plugin Argument: copy
If you want the mapping plugin to simply copy the plain properties and not make them observable, use this argument, as shown below.
// tell the mapping plugin to handle all other properties normally,
// but to simply copy this property instead of making it observable
var mapping = {
'copy': ["propertyToCopy"]
}
var viewModel = ko.mapping.fromJS(data, mapping);
Gain complete control over the mapping process
If you want to have 100% control over what is created in the mapping process, including the ability to put closures and subscriptions in your objects, then you want to use the "create" option.
plain result with calculated properties
Here is an example where I was mapping data from an ajax call to an object with a results property. I didn't want anything observable and I just wanted a simple generated property that would be made of the other simple properties on the object. Maybe not the most compelling example but it demonstrates the functionality.
var searchMappingConfig = {
// specific configuration for mapping the results property
"results": {
// specific function to use to create the items in the results array
"create": function (options) {
// return a new function so we can have the proper scope/value for "this", below
return new function () {
// instead of mapping like we normally would: ko.mapping.fromJS(options.data, {}, this);
// map via extend, this will just copy the properties from the returned json element to "this"
// we'll do this for a more light weight vm since every last property will just be a plain old property instead of observable
$.extend(this, options.data);
// all this to add a vehicle title to each item
this.vehicleTitle = this.Year + "<br />" + this.Make + " " + this.Model;
}, this);
};
}
}
}
subscriptions and closures and mapping, oh my
Another situation is if you want closures and subscriptions in your result. This example is too long to be included in its entirety but its for a vehicle make/model hierarchy. I wanted all the models (children) for a given make (parent) to be un-enabled if the model was un-enabled and I wanted this to be done with a subscription.
// here we are specifying the way that items in the make array are created,
// since makes has a child array (Models), we will specify the way that
// items are created for that as well
var makesModelsMappingConfig = {
// function that has the configuration for creating makes
"create": function (options) {
// return a new function so we can have the proper
// scope/value for "this", below
return new function () {
// Note: we have a parent / child relationship here, makes have models. In the
// UI we are selecting makes and then using that to allow the user to select
// models. Because of this, there is going to be some special logic in here
// so that all the child models under a given make, will automatically
// unselect if the user unselects the parent make.
// make the selected property a private variable so it can be closure'd over
var makeIsSelected = ko.protectedComputed(false);
// expose our property so we can bind in the UI
this.isSelected = makeIsSelected;
// ... misc other properties and events ...
// now that we've described/configured how to create the makes,
// describe/configure how to create the models under the makes
ko.mapping.fromJS(options.data, {
// specific configuration for the "Models" property
"Models": {
// function that has the configuration for creating items
// under the Models property
"create": function (model) {
// we'll create the isSelected as a local variable so
// that we can flip it in the subscription below,
// otherwise we wouldnt have access to flip it
var isSelected = ko.protectedComputed(false);
// subscribe to the parents "IsSelected" property so
// the models can select/unselect themselves
parentIsSelected.current.subscribe(function (value) {
// set the protected computed to the same
// value as its parent, note that this
// is just protected, not the actual value
isSelected(value);
});
// this object literal is what makes up each item
// in the Models observable array
return {
// here we're returning our local variable so
// we can easily modify it in our subscription
"isSelected": isSelected,
// ... misc properties to expose
// under the item in the Model array ...
};
}
}
}, this);
};
}
};
All in all, what I've found is that you rarely need 100% of an object that you'd pass to the plugin and you rarely need 100% of it to be observable. Dig in with the mapping configuration options and create all sorts of complex and simple objects. The idea is to only get everything you need, nothing more or less.
My suggestion to you would the same another questioned I just answered at https://stackoverflow.com/questions/7499133/mapping-deeply-hierarchical-objects-to-custom-classes-using-knockout-mapping-plug.
Your reasoning for using mapping plug-in is reasonable and the one that I use. Why type more code than you have to?
In my experience with knockout (all of 4 months), I've found that the less I do manually and let the knockout routines do their thing, the better my apps seem to run. My suggestion is try the simplest approach first. If it doesn't meet your needs, look at how the simple approach is doing it's "thing" and determine what has to change to meet your needs.
Allen, my recent learning experience with Knockout.js has been similar to yours. We work with a deep hierarchical object graph from the server and I have defined explicit instantiable view model functions which preserve the basic structure of it.
I began by defining each property explicitly as an observable on the relevant view model, but that quickly got out of hand. Also, a major reason for switching to using the mapping plugin was that we have to do frequent Ajax posts of the graph back to the server where it is merged with the persisted version, then validated on the server in such a way that numerous properties can change and collections be modified, and a new instance returned as the Ajax result where it has to be re-merged with the client representation. That became seriously difficult, and the mapping plugin helped big time by allowing the specification of identifiers for resolving adds / deletes / updates and to remap an updated graph onto the original.
It also helped in the original graph creation through the use of the "create" option for sub view models. In each view model constructor I receive a reference to the parent view model plus the data with which to construct the child view model, then create further mapping options to create grandchildren from the passed-in child data.
The only (slight) downside I recently found, as detailed in this question, is that when doing ko.mapping.toJSON it doesn't hook into any toJSON overrides you may have defined on the prototypes of your view models in order to exclude properties from serialization. I have been able to get around that by specifying ignore options in the unmapping, as recommended by Ryan Niemeyer in that post.
So in summary, I'll definitely be sticking with the mapping plugin. Knockout.js rules.
A simpler but help-full add-on could be knockout-data-projections
Currently, it does not handle js to viewmodel mappings, but it handles quite well view model to JS mappings.

Categories