Setting VueJS model property doesn't update select element in DOM - javascript

I have a cascading dropdown using VueJS (1.0), and I'm having a problem where a change in the Vue model isn't being reflected in the DOM.
The elements in the dropdown need to be an object, but once selected I'm trying to change the value of the property/dropdown to an int.
I'm doing this with a watch event, which processes the necessary information from the object, then uses $set on the property to set it to the required int.
Using the VueJS Chrome dev tools, I can see the change reflected on the component's property, but when submitting the form it's POSTed as the string [object Object], as if the DOM was never updated.
Here is the relevant dropdown in the template:
<select :disabled="releases.length < 1" v-model="release" options="releases" class="form-control input-sm" name="{{formname}}[release_id]">
<option selected="selected" value="">Choose Release...</option>
<option v-for="obj in releases" v-bind:value="obj">{{obj.text}}</option>
</select>
And here is the watch event:
"release": function() {
this.$parent.$data.promos = this.release.promos;
this.$set('release', this.release.id);
}
After changing the dropdown, the root promos property is updated, and according to dev tools the release property of the component is correctly set to the id
But when the form is submitted, I just get the string representation of the object!
Does anyone know what I'm doing wrong here; or is this a VueJS bug/is there a workaround?
Thank you!

I will not be able to find the bug in your code unless I can play around with it in jsFiddle or equivalent.
But I have an alternate implementation of cascading dropdown for you here: https://jsfiddle.net/mani04/Lgxrcc5p/1/
You may see if that provides any pointers. This example uses Vue 2.0.3
In your code sample above, I specifically do not understand this part:
<select :disabled="releases.length < 1" ...
Is that a copy-paste error? I hope you have the following code in your editor:
<select :disabled="releases.length < 1" ...

The value attribute of an option can be tied to an object in Vue, but the browser still needs to render the value as valid HTML so the object is cast to a string.
One thing you can do is add a hidden field to the form with a value set to the ID of the selected release object.
Edit: Another option could be overriding the toString method of the class prototype to return the ID of the object.

Related

Vue.js - select element not updating when using :value, v-model is fine

I have two <select> elements. When I select a value in the first, I want to default the value for the second. It works with v-model, but not with :value.
One is a list of vehicles.
<select :value="vehicle" #change="setVehicle($event.target.value)">
<option value="CAR">Car</option>
<option value="PLANE">Plane</option>
</select>
The other is a list of parts. The WINGS part is only visible if PLANE is selected.
<select :value="part" #change="setPart($event.target.value)">
<option value="ENGINE">Engine</option>
<option value="WINDOWS">Wheels</option>
<option v-if="vehicle === 'PLANE'" value="WINGS">Wings</option>
</select>
The setter for vehicle is trivial, but the default part for a plane is the conditionally rendered WINGS.
setVehicle: function(newVehicle) {
this.vehicle = newVehicle;
if (this.vehicle === "PLANE") {
this.part = "WINGS";
} else {
this.part = "ENGINE";
}
}
The reactive part data is set to the correct value, e.g. you can display it with {{ part }}, but the <select> element is not properly updated. If I change the part's select to use v-model it works fine:
<select v-model="part">
Using v-model is not an acceptable workaround because in my real world app the part is readonly and can only be mutated with a setter (using a store-like architecture). Another workaround is using a setTimeout(.., 1) in the setVehicle setter. I really do not like that solution. What I really would like to know is why v-model behaves differently than :value/#change when the documentation suggest v-model is simply syntactic sugar.
Demo: https://codesandbox.io/s/eager-liskov-2yor4?file=/src/App.vue
Since the third option is not in the DOM yet when you are updating this.part, it is not selected at that time. So you have two option:
1) Use nextTick() to wait for component update, then set the new value:
setVehicle: function(newVehicle) {
this.vehicle = newVehicle;
this.$nextTick(()=>{
if (this.vehicle === "PLANE") {
this.part = "WINGS";
} else {
this.part = "ENGINE";
}
})
}
or,
2) Keep the third option rendered, but hide it when the vehicle is car. In such way it will get selected, at the moment of changing the vehicle, and showed later when the DOM will be updated. You will do it with v-show instead of v-if.
<select :value="part" #change="setPart($event.target.value)">
<option value="ENGINE">Engine</option>
<option value="WINDOWS">Wheels</option>
<option v-show="vehicle === 'PLANE'" value="WINGS">Wings</option>
</select>
Here's my solution.
Basically you shouldn't be afraid of v-model. Copy the values into the component itself and use them as you please.
But I agree. :value is behaving weirdly, even if using .sync prop. (I suspect it's because of setting property vehicle to PLANE and v-if renders right after setting part WINGS. But not quite sure.)
My solution
Found an alternative workaround that may resolve similar issues:
Bind the <select> element using a computed property that simply returns the reactive part property.
<select v-model="partComputed" #change="setPart($event.target.value)">
computed: {
partComputed: function () {
return this.part;
}
}
The question of why v-model behaves different than :value/#change combo is still open if anyone has insight into that. I tried Googling it, but the closest match was my own bug report I filed several years ago that I completely forgot about! I may see if this behavior is the same in Vue 3.

How to bind [(ngModel)] values in select2 when using angular html and ts file (without Javascript or Jquery)

I am trying to add a search option to a dropdown which performs the functionality of autocomplete. I have been given a basic dropdown with select tag.
I tried searching for the following options:
1) I tried to convert my select table into a p-dropdown table using PrimeNG but was unsuccessful.
2) I found select2 option for angular and have been unable to bind the selected value from the dropdown to the ngModel value. The value selected must be retained when navigating between the previous or next pages, however that isn't happening.
3) I tried to convert select table to p-autocomplete as well but do not know how to.
Please guide me with any one of the following ways.
This is the provided select dropdown:
<select class="form-control" [(ngModel)]="selectedColumnDetails[rowData.rowId]"
(ngModelChange)="handleChange(rowData.rowId)">
<option *ngFor="let option of tableColumnDetails[selectedTableDetails[rowData.rowId]]">
{{option.col_name}}
</option>
</select>
This is what I tried with select2 for angular.
{{option.col_name}}
Error: No value accessor for form control with unspecified name attribute
This error is thrown for the following line:
[(ngModel)]="selectedColumnDetails[rowData.rowId]
try to add the name attribute along with ngmodel in the same tag
<select> works slightly differently with ngModel.
Taking your code snippet, Modifying it to pick ControlValue
<select class="form-control" [(ngModel)]="selectedColumnDetails[rowData.rowId]"
(ngModelChange)="handleChange(rowData.rowId)">
<option *ngFor="let option of tableColumnDetails[selectedTableDetails[rowData.rowId]]" [(ngValue)]="option">
{{option.col_name}}
</option>
</select>
Should work for you.
You should find ample examples provided by angular here and here
For select2 in Angular, try:
<select2 ... [value]="selectedColumnDetails[rowData.rowId]" (valueChanged)="handleChange(rowData.rowId)"></select2>
Of course, you would have to update selectedColumnDetails[rowData.rowId] inside handleChange(rowData.rowId).
[(ngModel)]="data" is short for [value]="data" (input)="data = $event.target.value". Unfortunately the ng2-select2 package does not support the input event so you have to write this out in the long form with valueChanged.

jQuery not returns actual select option attribute

I have a jQuery UI select widget.
Widget may be fully re-initialized after Ajax call that returns html of select, on which afterwords widget is applied.
The problem is that after widget initialization the actual chosen select option is not selected.
When debugging I'm getting following outputs from Chrome DevTools console:
> thisWidget.element
[<select name=​"main.model" id=​"ui-id-297" style=​"display:​ none;​">​<option selected=​"selected" value>​ Choose ​</option>​<option value=​"express">​ Express ​</option>​</select>​]
> thisWidget.element.find('option')
[<option selected=​"selected" value>​ Choose ​</option>​, <option value=​"express">​ Express ​</option>​]
> thisWidget.element.find('option:selected')
[]
> thisWidget.element.find("option[value='']")
[<option selected=​"selected" value>​ Choose ​</option>​]
> thisWidget.element.find("option[value='']:selected")
[]
As I see it the html on which widget is initialized is correct, but the option with empty value is not selected despite that it should be.
Why am I getting this behavior ?
I found out why.
I was setting select value before widget was destroyed and re-initialized.
So events were occuring like this:
// getting re-rendered html select through ajax call
mySelect.val('foo') // setting the value of select which in fact may not have option with such value
// ...then destroy event occurs
// ... and then widget is reinitiazlied
So probably interlieving with undestroyed widget and value setting (mySelect.val('foo')) was the cause of such behavior.

Why does Angular not update the DOM by adding a 'selected' attribute to an <option> tag within ng-options when it correctly updates the model?

Using Angular 1.2.29 when I build a <select> with ng-options it appears on the surface to work as expected, when I select an option the model is updated, and visually the <select> indicates that the newly selected option has been chosen.
However, when using Developer Tools to view the mark-up I can see that the option tags are not updated, specifically the selected attribute is not removed from the previously selected option, nor is it added to the newly selected option.
<div data-ng-controller="MainController as main">
<pre> {{ main.test.item }} </pre>
<select
data-ng-model="main.test.item"
data-ng-options="item.label for item in main.test.items"
required="required">
<option value="" label="What do you want?"></option>
</select>
</div>
By setting the model this.test.item within the controller selected="selected" is added to the second option (with the label 'B'), however subsequent changes (made by using the select) do not update the mark-up accordingly.
angular
.module('myApp')
.controller('MainController', MainController);
function MainController () {
this.test = {};
this.test.items = [
{ label : 'A' },
{ label : 'B' },
{ label : 'C' }
];
// Pre-select the second item.
this.test.item = this.test.items[1];
}
It’s great that the model is kept up to date, but why does the mark-up remain unchanged?
What can be done to fix this so that it is updated to match the model?
https://jsfiddle.net/paulhhowells/4hmwhbe8/
Sorry, don't have enough reputation to post comments, apparently.
#paulhhowells I think the answer there still applies. To elaborate, the DOM has properties and attributes. Properties have the actual values that you care about, they'll update as you change the selection. What you're seeing when you see selected="selected" in the markup is the attribute selected. Attributes don't update as you change the selection, they exist in the markup to create the element, and are usually used to initialize the element's properites
EDIT: There's a better explanation in the jQuery documentation, take a look at the Attributes vs Properties section here.
The selected attribute should not be expected to be updated.
It is only used to indicate that the option it is on should be initially selected.
https://developer.mozilla.org/en/docs/Web/HTML/Element/option
Therefore, Angular does not update the DOM by adding a 'selected' attribute to an <option> tag within ng-options when it correctly updates the model, because it should not.

Remove blank option ng-repeat and ng-options after filter

There are several questions very similar to this one yet I have been unable to come up with a solution.
I have a select list using angularJS. I need to use the title attribute so I have an ng-repeat to create the options, there is always a blank option even if I use ng-selected to always select the first option.
Even if I make a selection and the blank option goes away, if I then filter out that selected value the blank will reappear.
I have included a select list using ng-option (which does not include my needed tittle attribute) and a default value to show that the blank will appear after filter.
The behavior I desire would be to never have a blank option (always selecting first option would be fine) and to possibly have a directive per option for special handling of click events.
Thanks in Advance!
JS Fiddle: http://jsfiddle.net/32DFM/3/
<select size="3" ng-model="person.current">
<option ng-repeat="p in people | filter:person.SearchTerm"
ng-selected="$first"
value="{{p}}"
title="{{p.name}}">
{{p.name}}
</option>
</select>
I forked your fiddle (if I may be so blunt): http://jsfiddle.net/XsFe8/2/
This fixes it somewhat. Although I haven't gotten it to work properly together with the filter.
Anyway, what I do here, is to use the person.id as the value on each option.
<select ng-model="person.current">
<option ng-repeat="p in people | filter:person.SearchTerm" ng-selected="$first" value="{{p.id}}" title="{{p.name}}">
{{p.name}}
</option>
</select>
And set the initial calue on the person.current model:
$scope.person.current = $scope.people[1].id;
But it's still not 100% though. I'm a bit stumped to why the blank spaces appear when you filter the select....
An alternative that might or might not work, would be to use something like ng-repeat="p in filterPeople() and filter your array in a filterPeople function. But I'm not sure if this will change anything.
UPDATE: I tested out my suggestion above, here: http://jsfiddle.net/XsFe8/2/
If you set the selected object to be the first object in the filtered array, it works. I do this each time a new filtered array is created:
$scope.filterPeople = function () {
var array = filterFilter($scope.people, $scope.person.SearchTerm);
$scope.person.current = array[0].id;
return array;
};
It looks like things get hairy when another object than what is visible in the select is actually selected. This is kind of understandable :)
Your actual problem is the value in ngModel is referencing a value which doesn't exist in the select anymore.
A solution is to whenever you alter the select options, you also check the person.current to ensure that it points to a valid entry.
This also implies that you might want to move your filter into the controller, and set the options in the scope (you can use the $filter service in your code to get same behaviour there, https://docs.angularjs.org/api/ng/filter/filter). This way you can have a function in your controller checking if person.current is valid, and if not, set it to desired options (e.g. the first one).
the hairyness cited above is due to an empty array when all items are filtered out and is fixed by:
if(array.length>0)
$scope.person.current = array[0].id;
http://jsfiddle.net/b0z6vpr8/
Hope this helps

Categories