ngModelCtrl - $setViewValue without $setDirty(); - javascript

I have a directive which manipulates the $viewValue of an input field via ngModelCtrl.$setViewValue() on blur.
function _onFocus () {
presentation = false;
if(!ctrl.$isEmpty(ctrl.$modelValue)) {
ctrl.$setViewValue(String(ctrl.$modelValue).replace(/\./, ','));
ctrl.$render();
}
}
Plunkr
Since this is only an appearance thing, I wonder if there is the possibility of setting the ViewValue without altering the $pristine/$dirty state of the input field itself.
Reference Angular: $setDirty on $commitViewValue

I would probably set the value of the element instead of setting view value and rending all over, since it is just the appearance thing that happens on manually registered event, and you do not have to play with the ngmodel's validation properties.
i.e:
function _onBlur () {
//....
$element.val(_formatCurrency(ctrl.$modelValue));
}
function _onFocus () {
//...
$element.val(String(ctrl.$modelValue).replace(/\./, ','));
}
Plnkr

Related

Knockout.js - Prevent change event binding to trigger when setting the value through the observable

I have a DropDownList with the following bindings:
<select data-bind="value: DropDownValue, event: { change: OnChange }">
<option value="1">Val 1</option>
/* and more */
</select>
The OnChange event is fired correctly when the user select a different value from the DropDownList.
The event is also fired when updating the value of the observable property using viewModel.DropDownValue(1).
What I'm trying to achieve, is to trigger the change event ONLY when the user sets the value through the UI.
Is it possible to block the change event when updating the value through the observable?
This is the JSFiddle example: https://jsfiddle.net/5ex5j7jL/3/
Looks like one way to do it is to use the isTrusted property of the event object (true when the event was generated by a user action, false when generated by a script):
self.OnChange = function(viewModel, event) {
if(event.isTrusted) {
console.log("from dropdown");
return;
} else {
console.log("NOT from dropdown");
// do something
}
};
See updated fiddle
EDIT
Of course, you have to implement some king of mechanism if you want to prevent the user from changing the dropdown via the UI:
function ViewModel() {
var self = this;
self.DropDownValue = ko.observable();
self._original = null;
self.OnChange = function(viewModel, event) {
if(event.isTrusted) {
// rollback the viewModel value to the old one
viewModel.DropDownValue(self._original)
return false
} else {
// keep a reference to the latest value for later rollback
self._original = ko.unwrap(viewModel.DropDownValue)
}
};
};
See this updated fiddle

Focus remains on field

I'm trying to figure out why my focus remains on an element. This is html from the autocomplete angular plug-in that I'm using:
<autocomplete id="search" ng-model="query" attr-placeholder="" click-activation="true" data="items" on-type="updateItems" on-select="searchItems"></autocomplete>
but every time I press enter no matter if I have my focus on the input field or not, or even on an other field, the on-select function is called every time.
this thing is in the plugin itself, maybe it needs some changes?
document.addEventListener("blur", function (e) {
// disable suggestions on blur
// we do a timeout to prevent hiding it before a click event is registered
setTimeout(function () {
scope.select();
scope.setIndex(-1);
scope.$apply();
}, 150);
}, true);
You could monitor what was the last selected input field with this code :
var lastFocusedElement = '';
// This would catch any input field - so you could add your forms selector too.
// for example : $("#myForm:input")
$(":input").focus(function () {
lastFocusedElement = $(this);
});
Then use the complete callback function from animate :
$("#div_NotificationOuter").animate({ bottom: '+=30px' }, 4000,function(){
if (lastFocusedElement != ''){
lastFocusedElement.trigger('focus');
}
});

AngularJS checkbox ng-checked not rendering if preventDefault used

I'm dynamically building a set of checkboxes. Clicking any of the checked boxes should uncheck the first (index wise) checked box. Clicking any of the unchecked boxes should check the last unchecked box.
I'm building the checkboxes using ng-repeat like this:
<input
type="checkbox"
ng-checked="values[$index]"
ng-repeat="n in values track by $index"
ng-click="click($event,$index)" />
And my controller looks like this:
.controller("myCtrl", function ($scope) {
$scope.values = [true,true,true,true];
$scope.click = function (event,n) {
event.preventDefault();
if ($scope.values[n] === true) {
$scope.values[$scope.values.indexOf(true)] = false;
} else {
$scope.values[$scope.values.lastIndexOf(false)] = true;
}
}
Here's a codepen of it all together http://codepen.io/anon/pen/rVLewJ
The problem that I'm running into is that using preventDefault seems to prevent ng-checked from updating the rendering of the box you click on (the others boxes re-render correctly). This causes the display to become out of sync with $scope.values.
Likewise, removing preventDefault doesn't prevent the box you're clicking on from changing its rendering, but (I believe because of ng-repeat's conservative re-rendering) ng-checked doesn't fire so it also gets out of sync.
I'm not using ng-changed because I'm specifically trying to prevent the checkboxes from changing if you're clicking on the "wrong" one. Regardless, I've tried using it instead of ng-clicked and it didn't fix anything.
I've tried using ng-model instead of ng-checked, but that seemed to prevent $scope.values from changing at all. Using $scope.$apply(), didn't help. Some of the things I've read have lead me to think I may need to use $watch but I'm teaching myself Angular with this project so I'm not sure exactly how to apply that.
Update
Nagasimha Iyengar provided a working solution here which I've simplified thusly
<input
type="checkbox"
ng-model="values[$index]"
ng-repeat="n in values track by $index"
ng-change="click($index)" />
Controller:
.controller("myCtrl", function ($scope) {
$scope.values = [true,true,true,true];
$scope.click = function (n) {
if ($scope.values[n] === true) {//clicking on unchecked box
$scope.values[n] = false;
$scope.values[$scope.values.lastIndexOf(false)] = true;
} else {//clicking on checked box
$scope.values[n] = true;
$scope.values[$scope.values.indexOf(true)] = false;
}
}
Codepen: http://codepen.io/anon/pen/zGBKxr
This solution is based on allowing the click event to happen, then undoing it before proceeding with the custom logic. It's certainly a simple solution but feels somewhat improper. Is it the most correct way of solving this problem?
Another small update. I dropped in ngTouch to try and make the app feel a bit quicker on mobile. At least in iOS Safari 8, ngTouch broke this solution. Still works fine on desktop, but the overridden ngClick prevents this solution form working. If you switch back to the original proposed logic it fixes iOS, but of course doesn't work on the desktop. I feel like this confirms my suspicion that the solution was not the correct one.
Here it is - the logic was reversed. And I used $apply with a timeout. Modified codepen: http://codepen.io/nagasimhai/pen/VLjjPe
angular
.module("myApp", [])
.controller("myCtrl", function ($scope) {
$scope.values = [true,true,true,true];
$scope.click = function (event,n) {
//event.preventDefault();
//console.log("b", n, $scope.values,$scope.values.indexOf(true), $scope.values.lastIndexOf(false));
if ($scope.values[n] === true) {//clicking on unchecked box
$scope.values[n] = false;
$scope.values[$scope.values.lastIndexOf(false)] = true;
//console.log("11");
} else {//clicking on checked box
$scope.values[n] = true;
$scope.values[$scope.values.indexOf(true)] = false;
//console.log('22');
}
//console.log("a",n, $scope.values);
setTimeout(function () {
$scope.$apply(function () {
});
}, 2000);
}
});
Your answer was a bit confusing but I believe I'm on the correct path. The problem. What you want to use is event.stopPropagation() to stop the event from propagating. In other words, you only want the event to apply to the specific element. If you use event.preventDefault() it cancels the event if cancelable, without stopping further propagation of the event.
angular
.module("myApp", [])
.controller("myCtrl", function ($scope) {
$scope.values = [true,true,true,true];
$scope.click = function (event,n) {
event.stopPropagation();
if ($scope.values[n] === true) {
$scope.values[$scope.values.indexOf(true)] = false;
$scope.values[n] = false;
} else {
$scope.values[$scope.values.lastIndexOf(false)] = true;
}
}
});
Try the code out at the codepen http://codepen.io/anon/pen/bdepPW

Intercept value changes within .blur() event

I'm using .blur() function to execute some code every time a text field loses focus (also when its value doesn't change).
Now I need to add some logic that must be executed only when text field value changes. Is there a way to combine .change() event with .blur()? Or, better, is there a way to know if the value in my text field is changed just using .blur()?
Not directly, but you can store the value on focus event..
something like
$('input')
.on('focus',function(){
// store the value on focus
$(this).data('originalValue', this.value);
})
.on('blur',function(){
// retrieve the original value
var original = $(this).data('originalValue');
// and compare to the current one
if (original !== this.value){
// do what you want
}
});
Of course you could just bind different handlers for each event..
$('input')
.on('change', function(){/*your change code*/})
.on('blur', function(){/*your blur code*/});
The event change is trigger every time that the field lose the focus and the content has change. I think what you need is to use change() instead of blur(). Take a look at this jsfiddle
$('#in').change(function(){
alert('change!');
});
If what you need is to execute the same code when the input loses the focus and when the value changes, you can combine both events
$('in').on('change blur', function(){
//code
});
you can use closure to store the previous value and compare them later
var createOnBlurFunc = function(){
var prevVal = '';
return function(e){
if(prevVal === $(this).val()){
//same value
} else {
prevVal = $(this).val();
// do something
}
}
};
$('input').blur(createOnBlurFunc());
I think this is a more generalized way, for those that are created on the fly:
var $bodyEl = $('body'), inputOldValue;
$bodyEl.on('focus', 'input, textarea, select', function () {
inputOldValue = $(this).val();
});
$bodyEl.on('blur', 'input, textarea, select', function () {
if (inputOldValue != $(this).val()) {
$(this).trigger('changeBlur');
}
});
input, textarea, select is faster than :input as a selector.

HTML: listen for *all* changes to an input, not just keyboard-driven changes?

For example, if I've got an input like this:
<input id="myInput" type="text" />
How can I be notified when the value is changed programatically (for example, as a result of $("#myInput").val("new value"))?
An example: http://jsfiddle.net/2ax9y/
Edit: please note, the question is asking how to listen for changes, not “how to manually dispatch changes”. I'm well aware that I can manually dispatch change events, but I'm trying to avoid that.
You can change the $ prototype to always trigger change() events whenever val(value) is called:
$.prototype.changeval = $.prototype.val;
$.prototype.val = function(v) {
if (v) {
$(this).changeval(v);
$(this).change();
} else {
return $(this).changeval();
}
}
Trigger the change: http://jsfiddle.net/maniator/2ax9y/1/
A little differently: http://jsfiddle.net/maniator/2ax9y/2/
You can't reliably subscribe to every change on an input element, but you can check if there has been a change since you last checked and do that within some desired time granularity.
Make a checker function and loop it with your desired time granularity—100ms is a good start, adjust to your needs.
Hypothetical untested implementation:
var last_value = null;
function handle_changed() {
// Do something here
}
function check_value() {
var v = $("#myelement").val();
if(v !== last_value) {
last_value = v;
handle_changed();
}
}
setInterval(check_value, 100);
I don't think this is an inherent ability of the change event. You could manually trigger the change event when you programmatically change the value.
$("#myInput").val(+(new Date())).change();

Categories