Update entire object with value binding on select with knockout.js? - javascript

I have a select dropdown menu which shows a list of objects, with the name property being the text displayed, the id property being the value each option is bound to, and a value: user.id binding which is from a separate property on another object.
<td data-bind=""><select data-bind="options: peopleResponsible, optionsText: 'name', optionsValue: 'id', value: user.id"></select></td>
When I select a new person object from the dropdown list, only the id on the user is being updated. All of the other properties (name, username, age, etc) are not being updated.
What I need for it to do is when a new peopleResponsible option is selected, I want it to copy across all of the properties from that object to the user object.
I have a suspicion that at the moment it currently does not work because the user object itself is not observable, only its properties are. Here is how my data is being mapped:
ko.mapping.fromJS(taskOutlines, {}, mappedTaskOutlines);
Where a TaskOutline contains many Tasks, and each Task contains a single User.
Any ideas?

You can do this:
var vm = {
peopleResponsible: ko.observableArray([{
id: ko.observable(1),
name: ko.observable("p1")
}, {
id: ko.observable(2),
name: ko.observable("p2")
}, {
id: ko.observable(3),
name: ko.observable("p3")
}]),
selectedUser: ko.observable()
}
vm.selectedUser(vm.peopleResponsible()[1]); // pre select a user
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<select data-bind="options: peopleResponsible, optionsText: 'name', value: selectedUser"></select>
<div data-bind="with: selectedUser">
<p>Id:
<label data-bind="text: id"></label>
</p>
<p>Name:
<label data-bind="text: name"></label>
</p>
</div>
When a selection is made that selection will be a reference to the arbitrary object in the observable array, properties and all. This selected object will then be placed in "selectedUser" observable.
So in short, removing the "optionsValue" binding will bind the entire object instead of the id property.

The value binding only sets one observable. Not knowing how things are used, it's hard to say what you should do to get the result you want. One possibility is to make a function that does the copying and subscribe to the user.id.

Related

How to fix value update delay in vuejs after axios request?

I am retrieving a list of data from an api and need to fill the specific <select></select> tags, which is associated to a few radio button, with some of the data as <options></options>. The radio buttons waiting for an event (#change/#click) and executing and axios get request. Everthing works fine. I click on a radio button and retrieving the data as response (vue tools also showing the right data) but the <option></option> tags are not updating. Now when I click on another radio button, I am getting again the right data from the api BUT now the <option></option> tags are refreshing with the data from the previous response.
Template
<!-- CREATING 7 RADIO BUTTONS FOR THE CURRENT WEEK FROM MON-SUN -->
<div class="wrapper" v-for="item in inputDetails">
<input :id="'datetime[0]['+item.labelText+']'" type="radio" name="datetime[0][date]" v-model="formData.datetime[0].date" :value="item.inputValue" #change="getTimes" />
</div>
<!-- CREATING THE TIME PICKER -->
<select id="datetime[0][time]" name="datetime[0][time]" v-model="formData.datetime[0].time">
<option selected="selected"></option>
<option v-for="item in selectOptionTimes[0]" :value="item.value">{{ item.label }}</option>
</select>
<!--
2 MORE RADIO BUTTON SECTION AND TIME PICKER SECTIONS WITH DIFFERENT INDEXES
<input id="datetime[1][time]"...
-->
Script
data() {
return {
formData: {
datetime: [
{date: '', time: ''},
{date: '', time: ''},
{date: '', time: ''},
]
}
selectOptionTimes: [],
}
},
methods: {
getTimes: function (current) {
let instanceIndex = current.currentTarget.id.match(/(?<=\[)([0-9])(?=])/g)[0]; // getting the index of the current datetime section
axios.get('/api-url', {
params: {
location_id: this.formData.location_id,
date: current.currentTarget.value
}
}).then(response => {
this.selectOptionTimes[instanceIndex] = response.data;
});
}
}
Does someone know what the problem is here?
You cannot assign a value to an arbitrary index within an empty Array in this way. You must either completely replace the Array with values that hydrate that index, or you must use $set.
So, to recap:
BAD
this.selectOptionTimes[instanceIndex] = response.data
GOOD
this.$set(this.selectOptionTimes, instanceIndex, response.data)
Note though, that this has an unintended consequence. If you have an empty array, and call this.$set on an index greater than 0, the array will be filled with empty values up to your index.
What might make more sense is using an {} instead along with this.$set and looping over the Object.keys instead of the array directly.
Fiddle showing $set on index with an empty array
Fiddle showing Object usage instead

Dynamically using ng-selected for <select> tag with data loaded from server

I am trying to properly set a series of SELECT objects that are dynamically created in angular. The server returns an array of objects that I create these dropdowns from.
What I'd like to do is, for every dropdown created, assign the default selected value (as in, set ng-selected) based on the value of the object's value (labelled 'package')
The function changeValue changes the data on the server successfully.
Here is the code so far:
<!-- HTML -->
<div ng-repeat="package in bucket.packages">
<select ng-options='option.value as option.name for option in sources' ng-change="changeValue('source', package.id, selection)" ng-model="selection"></select>
</div>
//js
$scope.sources = [
{name: 'License Source'},
{name: 'Project Website', value: 'projectWebsite'},
{name: 'File', value: 'file'},
{name: 'Expected License', value: 'expectedLicense'}
];
$scope.optionMatches = function (selection, option){
return selection === option.value;
};
//not sure where to put this
ng-selected="optionMatches(package, option)"

How to use ng-options to set default value of select element reading from object?

I have the following data:
$scope.users = [
{
'id': 0,
'name': 'Tom'
},
{
'id': 1,
'name': 'Jack'
}
];
And I have a default object that is dynamically generated.
$scope.default = {
'id': 1,
'name': 'Jack'
}
I'm trying to set a default selected option in my ng-options based on $scope.default but because it's an object as opposed to a string, it doesn't seem to work.
<select ng-options="user.name for user in users" ng-model="default.name"></select>
I'm aware of this other question, but it doesn't work with objects.
how to use ng-option to set default value of select element
You can to use "as" and "track by" to define the label and the field to be compared:
<select ng-options="user as user.name for user in users track by user.id" ng-model="default"></select>
see the jsbin
Check out https://docs.angularjs.org/api/ng/directive/ngInit
or try:
If it's dynamic ordering, then you could track by a custom field, user.orderNum or something you can alter. I haven't tested, but should get you in the right direction.
By default, ngModel watches the model by reference, not value. Try
<select ng-options="user.name for user in users track by user.name " ng-model="default.name"></select>.
More here

Dropdown list display [object object] instead of value using knockout

I am using knockout to try to bind data into a dropdown list but for some reason i am only seeing [object][object] instead of the actual value i want to display and not sure what i could be doing wrong. This is what i have so far:
self.views = ko.observableArray();
self.selectedView = ko.observable();
if (views){
for(viewOption = 0; viewOption < views.length; viewOption++){
self.views.push(
new viewModel(views[viewOption])
);
}
}
//Sample data
var sampleData = {
viewers: [
.....
],
views: [
{
vValue: 'View 1'
},
{
vValue: 'View 2'
}
]
};
//HTML
<select data-bind="options: views, value: selectedView"></select>
When i run this i get a dropdown displaying the right count of options but instead of showing View 1 and View 2 it shows [object][object] twice.
When you are using objects in array, you should use optionsText for option label and optionsValue for option value.
var vm = {
myItems: [
{ vValue: 'View 1', id: 1},
{ vValue: 'View 3', id: 3},
{ vValue: 'View 4', id: 4}
],
selected: ko.observable()
};
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<select data-bind="
options: myItems,
optionsText: 'vValue',
optionsValue: 'id',
value:selected" >
</select>
<br/>
Selected: <label data-bind="text: selected"></label>
Since you are supplying an array of complex types to the options binding, Knockout does not know what value you want to use as the "text" of your option item (even though you only have a single name/value pair). In most real-world scenarios, you would have more than just a text value in array of complex types.
You can either use the optionsText binding to instruct Knockout to use your value in the name/value pair of vValue, like this:
<select data-bind="options: views, value: selectedView, optionsText: 'vValue'">
</select>
Another way to handle this is to create the views array in your view model to just be an array of strings, then Knockout knows that the single string value in the array is the value to use as the option's text value.
UPDATE
You can just create a JavaScript array of strings, like this:
self.views = ["View1", "View2"];
Then you can keep your options binding syntax the same, as you do not have to bind to an observable array in Knockout, you can bind to just a plain ol' JavaScript array.
Note - Usually people have an observableArray, because their data is dynamic (either through loading from the server or user interaction), but there is no rule that bound objects must be "observable"; although you will not get two-way binding for something like a text input if you bind to a non-observable.

KnockoutJS selectedOptions not showing up

I'm using KnockoutJS to manage my web front-end. I am writing a CRUD/Admin site, but I am having problems with the selectedOptions binding.
The case:
The view model has form.products.all and form.products.selected observables. Both of these are generated by ko.mapping.
The HTML form has a multi-select of the form:
<select required="" multiple="" data-bind="options: products.all, optionsText: function (item) { return item.value.name.unName(); }, selectedOptions: products.selected" class="form-control">
The HTML form correctly shows all the options.
The HTML form does not show the selected options on load. In particular, if I inspect the viewModel object, I can see that the right objects get loaded into the products.selected array on load. But the multi-select does not select them automatically.
If I select objects in the form and then inspect the products.selected observable, I do see the objects in the array.
If I post the form, the right objects end up in the database, and then end up in the viewModel object on the next page load (so the only part missing in the cycle is actually marking the form based on what is in products.selected.
What am I doing wrong? I've seen conflicting advice, and some of it is outdated, so I'm not sure how to proceed.
The selectedOptions binding works as designed.
Your error is very likely that your selected observable does not contain the identical objects (i.e., references to the objects in all), but merely objects that have equal property values.
Knockout maintains the binding through object identity, it does not make any other comparisons.
Consider this simple example:
var vm = {
products: {
selected: ko.observableArray(),
all: ko.observableArray([
{
value: {
name: {
unName: ko.observable("Foo")
}
}
}, {
value: {
name: {
unName: ko.observable("Bar")
}
}
}
])
}
};
vm.products.selected.push(vm.products.all()[1]);
ko.applyBindings(vm);
pre {
font-size: small;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.0.0/knockout-min.js"></script>
<select required="" multiple="" class="form-control" data-bind="
options: products.all,
optionsText: function (item) {
return item.value.name.unName();
},
selectedOptions: products.selected
"></select>
<pre data-bind="text: ko.toJSON($root, null, 2)"></pre>
Unrelated side-note: Try to avoid defining functions in the binding attribute. It's ugly, inefficient, potentially repetitive and not very idiomatic. Make a corresponding property on the view model, for example like this:
function Product(data) {
ko.utils.extend(this, data);
this.displayName = this.value.name.unName;
}
and
<div data-bind="with: products">
<select required="" multiple="" class="form-control" data-bind="
options: all,
optionsText: 'displayName',
selectedOptions: selected
"></select>
</div>

Categories

Resources