Good Evening,
does anyone know why the input toggle switch value is being flipped to the reverse value when emitted to the parent component? I'm new to Vue, I have been working with it on and off for a few days now. It works in concept, I can see the value of the attribute in both areas using vue dev tools. However, the child value is reversed when emitted to the parent and assigned. I could immediately fix by !'ing the incoming value, but I would like to find out if anyone knows why this is occurring.
Parent Update Bind
updateMiddle(article){
this.article.meta_title = article.meta_title;
this.article.meta_desc = article.meta_desc;
this.article.published = article.published;
this.article.is_review = article.is_review;
}
Child Emit
methods: {
update() {
this.$emit('changeMiddle',this.article)
}
Input
<input id="tc-review" type="checkbox" hidden="hidden" name="is_review"
v-model="article.is_review" v-on:input="update">
The problem is that the input event fires before the v-model binding has changed the data.
Simple solution is to use the change event instead. For example
<input v-model="article.is_review" #change="update">
Simplified demo ~ http://jsfiddle.net/u20h5tzv/
Hint: Try changing it back to #input and see the difference in timing.
Related
I faced an strange issue with Vue3 $emit. I was refactoring my own Vue2 "form-input" -component, which is basically an wrapper for rendering either text-input, (multi)select-option, textarea or any other basic input field.
This component used to bind value against v-model, so it was emitting input via "this.$emit('input', this.value)".
Since Vue 3 changed input to update:modelValue, I made this modification and additionally presented "Emits" -property-list to component. Everything was seemingly working...
But then I recognized that Vue-multiselect 3 (NPM package "vue-multiselect": "^3.0.0-alpha.2") was emitting primitive value, an integer number, which didn't trigger #input on parent-component.
form-input.vue
<input v-if="resolveType === 'text' || resolveType === 'number' || resolveType === 'tel' || resolveType === 'email'"
v-model="localValue"
#input="$emit('update:modelValue', $event.target.value)"
/>
<multiselect v-model="localValue"
class="full-width-tags"
#update:model-value="$emit('update:modelValue', $event)"
/>
// Please note that multiselects $event is an integer, I have checked this multiple times from console.log
... and when I'm listening for #input on parent component it seems "text"-type field triggers event-listener but multiselect doesn't. Only difference on these components is that multiselect is emitting primitive value (integer), while input is sending an event-object.
Could someone explain why event-object and primitive value are handled differently for #input , and how could I fix my multiselects emit so that it's emitting input? Currently I'm emitting both "update:modelValue" AND "input" -events one after another for multiselect, but I'm just wondering if there is easier way?
From your question it seems you have read the Vue 3 Migration Guide - particularly the part about change of v-model on custom components and changed your form-input to emit update:modelValue event instead of input event
So why are you listening for updates on your form-input component with #input instead of #update:modelValue ?
The only reason why the #input placed on your form-input component in the parent works, is because of this change
In short - because your component declares that it emits update:modelValue event (using emits option), every other event listener placed on it is directly bound to the root element of the component - native <input> in this case
This is not what you want. Just change <form-input #input="..." /> to <form-input #update:modelValue="..." />
I want to update input field externally (via jQuery) and I expect the associated object to be updated too, like I just typed the value into the input field by hand.
Here is my html:
<div id="content"></div>
<button id="btn">Put "Hello" in content.</button>
Here is my template:
<script id="myTmpl" type="text/x-jsrender">
<div><input type="text" id="title" data-link="{:Title trigger=true:}" /></div>
</script>
Here is my code:
<script type="text/javascript">
obj = { Title: 'Hey' }
var tmpl = $.templates('#myTmpl');
tmpl.link('#content', obj);
$('#btn').click(function() {
$('#title').val('Hello').change();
});
</script>
The thing is when I press the button, the value goes into the input field correctly but the object (obj) is not updated (when I'm watching it in the debugger). When I'm typing value directly in the input field, the object is updated correctly. What's the correct approach here.
An interesting thing is that it has been working correctly until I changed jsViews version from 0.9.71 to 1.0.7.
Here is a version with 0.9.71: https://jsfiddle.net/zhsmn1eg/
Here is a version with 1.0.7: https://jsfiddle.net/zhsmn1eg/1/
The difference in the behavior is because early versions listened to the change or keydown events on <input>s for triggering observable changes to the data. Subsequently the HTML5 input event became available, and was the preferred event for responding to any change in the <input> value. Later versions of JsViews use the input event.
Calling $('#title').val('Hello').change(); will raise a change event, but will not raise the input event.
You can instead raise the input event directly, by using:
$('#title').val('Hello').trigger('input');
Alternatively you can set trigger to false either globally or locally. The call to $('#title').val('Hello').change(); will then trigger a data update. (But typing into the input will now only trigger a data update on blur/change, not on keydown).
I have an input in my view:
<label for="map-latitude_input">Latitude {{mapLatitudeInput}}</label>
<input
type="text"
placeholder="00.000"
[(ngModel)]="mapLatitudeInput"
[ngFormControl]="newListingForm.find('mapLatitudeInput')"
id="map-latitude_input"
class="transparent">
and inside my controller I am listening to map drag event from google maps api. On drag I want to update value inside my input and in its associated label. At the moment this looks like this:
//Update coordinates on map drag
map.addListener('drag', (event) => {
this.mapLatitudeInput = map.getCenter().lat();
console.log(this.mapLatitudeInput);
});
Now when I drag a map console keeps outputting correct value as I move it around, however in my view it stays exactly the same. What I also noticed is that if I drag map around and than do some action like select an option in select menu within same form that my input is in, it updates mapLatitudeInput to correct value, i'm not sure why this happens, but thought I'd mention it.
It's perhaps due to ZoneJS. This question could give you some hints: View is not updated on change in Angular2.
From where do you get your map instance. If it's instantiated outside a zone, Angular2 won't be able to detect updates of the mapLatitudeInput attribute.
You could try something like that:
export class MapComponent {
constructor(ngZone:NgZone) {
this.ngZone = ngZone;
}
map.addListener('drag', (event) => {
this.ngZone.run(() =>
this.mapLatitudeInput = map.getCenter().lat();
console.log(this.mapLatitudeInput);
});
});
This question could be also related to your problem: Angular2 child property change not firing update on bound property.
Hope it helps you,
Thierry
I'm using a jQuery library of radio/checkbox components (yeah, I know using jQuery is bad with angular, but that was not my choice to use that library and I cannot change that) and I got a problem with refreshing ng-model data (radio component in library does a simple click trigger event when radio value change).
I noticed, that while one click trigger does nothing to model, triggering it twice solves the problem (but that is not the way I would like to solve this problem). I prepared a simple fiddle- a little example of what is my problem. First click (executing changeInput(1, 1) in the code below) on a button does a change in DOM, but does nothing to angular model - while clicking just on the radio button is doing just fine. Executing changeInput(1,2) does exacly the same as clicking the radio element.
function changeInput(obj, num){
for(var i = 0; i < num; i++){
$('input').eq(obj).click();
}
}
What else can I do? While reading stackoverflow I have noticed that people say that triggering 'input' solves the problem - but not in that case (JSFiddle). Is triggering click twice is the only way to solve this problem?
In Angular things don't work the way you might be used to. Once you get used to it, you will enjoy the much more straightforward and declarative nature of you code.
You said:
"in this question I would like only to know how to make model changes"
Nevertheless, in your attempts your are trying to make model changes by changing the view (programmatically). This is both unintuitive and a nightmare in terms of maintainability.
In Angular you should worry about your data (model) and watch the views adapt automagically.
So, if you want to make model changes, then all you need to do is to...well, change the model:
<input type="radio" name='test' ng-model="value" value="0" />
<input type="radio" name='test' ng-model="value" value="1" />
<button ng-click="changeInput(0)">Change value to 0</button><br/>
<button ng-click="changeInput(1)">Change value to 1</button><br/>
function Ctrl($scope) {
$scope.value = 0;
$scope.changeInput = function (newValue) {
$scope.value = newValue;
}
}
In order for Angular to do its magic and update the view you need to perform the action within the Angular context (ng-click instead of onclick takes care of that). If for whatever reason you can't use ng-click, you need to let Angular know something changed by wrapping your code in the changeInput() function in $scope.apply().
See, also, this short demo.
I had a similar issue once using only angular. The ng-binding of your input must be a property of an object defined in your controller. I think that otherwise, the input value is binded to a variable defined in the input own scope.
That wouldn't work :
<input type="checkbox" ng-model="selected"></input>
But that would :
<input type="checkbox" ng-model="someObject.selected"></input>
with for example in you controller :
$scope.someObject = {
selected: false
};
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();