setViewValue in directive on input not updating actual visible input value - angularjs

I've been fighting with this for almost two days. I hope you guys can help me.
Summary:
I have problems setting the view value of some input fields programatically.
I have a form with inputs whose values are saved before the form is removed (multiple elements and multiple forms possible, user might close a form, and reopen later). On reopening the form I want to restore the previous view values (main reason is to get back also the invalid view values which were not saved in the model). This doesn't work.
If I call ctrl.$setViewValue(previousValue) I get the model (visibly) updated (if valid), the view values of the formControl (while debugging in console) are changed too, but I don't get them actually rendered in the input fields. I don't understand why :(
I reduced the problem to this fiddle:
http://jsfiddle.net/g0mjk750/1/
javascript
var app = angular.module('App', [])
function Controller($scope) {
$scope.form = {
userContent: 'initial content'
}
}
app.controller('Controller', Controller);
app.directive('resetOnBlur', function () {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ngModel) {
element.bind('blur', function () {
console.log(ngModel);
scope.$apply(setAnotherValue);
});
function setAnotherValue() {
ngModel.$setViewValue("I'm a new value of the model. I've been set using the setViewValue method");
}
}
};
});
Html
<form name="myForm" ng-app="App" ng-controller="Controller" class="form">
Text: {{form.userContent}}
<hr />
If you remove the text, "Required!" will be displayed.<br/>
If you change the input value, the text will update.<br/>
If you blur, the text will update, but the (visible) input value not.
<hr />
<input class="input" type="text" ng-model="form.userContent" name="userContent" reset-on-blur required></textarea>
<span ng-show="myForm.userContent.$error.required">Required!</span>
</form>
I hope you guys can explain to me why this doesn't work and how to fix this...

You need to call ngModel.$render() to have the viewvalue change reflected in the input. There is no watch created on $viewValue so that changes are automatically reflected.
function setAnotherValue() {
ngModel.$setViewValue("I'm a new value of the model. I've been set using the setViewValue method");
ngModel.$render();
}
Plnkr
Default implementation of $render does this:-
element.val(ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue);
However you can override and customize your implementation for $render as well..

try scope.$apply() to invoke change on model since you're liking changing model outside of scope where ngModel was inited

Related

Emoticons support for textarea or contenteditable div

Trying to implement a textarea component with emoticons support while writing.
I want to be able to backup the original text (ascii chars only) while presenting the filtered/generated html outcome (with an angular emoticons filter) on a div.
My initial solution is to
<textarea ng-model="text" ng-change="..." ng-focus="..."></textarea>
<div ng-bind-html="text | myEmoticonsFilter"></div>
but I'm having trouble getting to the part of using a hidden textarea. Also, with this I wouldn't be able to mouse-select text and delete or copy/paste safely.
I also thought of using a <div contenteditable="true"> but ng-focus and ng-change wouldn't be handled.
Does anyone have any sugestion on how to continue this?
Edit 1: here is a jsfiddle with an attempt on what I'm doing. Up until now, able to replace the first occurrence, but the behavior remains erratic since that. I'm using a contenteditable directive for 2-way data binding and to filter the emoticon pattern.
Edit 2: regarding my statement saying that ng-focus and ng-change wouldn't be handled, that is not true - ng-focus works natively on <div contenteditable="true"> and ng-change will work as long as a directive is declared using the ngModel and setting the appropriate $modelValue and $viewValue (an example is provided in the jsfiddle in Edit 1).
The only way to do this in a consistently cross-browser manner is to use a WYSIWYG field that converts emoji to images.
There's a jQuery plugin jquery-emojiarea that does what you need, so you'd just need to create a directive that wraps this plugin and you're off to the races. Since it inputs into a hidden textarea with emoji syntax :smile: angular should have no difficulty binding.
Here's a working directive I threw together. http://jsfiddle.net/dboskovic/g8x8xs2t/
var app = angular.module('app', []);
app.controller('BaseController', function ($scope) {
$scope.text = 'This is pretty awesome. :smile: :laughing:';
});
app.directive('emojiInput', function ($timeout) {
return {
restrict: 'A',
require: 'ngModel',
link: function ($scope, $el, $attr, ngModel) {
$.emojiarea.path = 'https://s3-us-west-1.amazonaws.com/dboskovic/jquery-emojiarea-master/packs/basic';
$.emojiarea.icons = {
':smile:': 'smile.png',
':angry:': 'angry.png',
':flushed:': 'flushed.png',
':neckbeard:': 'neckbeard.png',
':laughing:': 'laughing.png'
};
var options = $scope.$eval($attr.emojiInput);
var $wysiwyg = $($el[0]).emojiarea(options);
$wysiwyg.on('change', function () {
ngModel.$setViewValue($wysiwyg.val());
$scope.$apply();
});
ngModel.$formatters.push(function (data) {
// emojiarea doesn't have a proper destroy :( so we have to remove and rebuild
$wysiwyg.siblings('.emoji-wysiwyg-editor, .emoji-button').remove();
$timeout(function () {
$wysiwyg.emojiarea(options);
}, 0);
return data;
});
}
};
});
And usage:
<textarea ng-model="text" emoji-input="{buttonLabel:'Insert Emoji',wysiwyg:true}"></textarea>
If you want the editable field to convert text like :( as you type you'll need to fork that jquery plugin and modify it slightly to parse input text on change as well as on init. (like, a couple lines of code)

AngularJS directive remove class in parent element

I am using the following code to add / remove class "checked" to the radio input parent. It works perfectly when I use JQuery selector inside the directive but fails when I try to use the directive element, can someone please check my code and tell me why it is not working with element and how I can possibly add/ remove class checked to the radio input parent while using element instead of the jquery selectors? Thanks
.directive('disInpDir', function() {
return {
restrict: 'A',
scope: {
inpflag: '='
},
link: function(scope, element, attrs) {
element.bind('click', function(){
//This code will not work
if(element.parent().hasClass("checked")){
scope.$apply(function(){
element.parent().removeClass("checked");
element.parent().addClass("checked");
});
}else{
scope.$apply(function(){
element.parent().addClass("checked");
});
}
//This code works perfectly
$('input:not(:checked)').parent().removeClass("checked");
$('input:checked').parent().addClass("checked");
});
}
};
});
HTML:
<div class="inpwrap" for="image1">
<input type="radio" id="image1" name="radio1" value="" inpflag="imageLoaded" dis-inp-dir/>
</div>
<div class="inpwrap" for="image2">
<input type="radio" id="image2" name="radio1" value="" inpflag="imageLoaded" dis-inp-dir/>
</div>
Your code actually works for me in Plnkr (more or less):
http://plnkr.co/edit/vJJRYQQxH7u2bKSc27UA?p=preview
When you run this, the 'checked' class gets correctly added to the parent DIVs using only the first code you included. (I commented out the jQuery mechanism - I didn't add jQuery to this page, as a test.)
However, I think what you're trying to accomplish isn't working out because you're only capturing click events. The radio button that loses its checked attribute doesn't get a click event, only the next one does. In jQuery your selector is really broad - you're hitting every radio button, so it does what you want. But since you only trap click on the radio button that receives the click, it doesn't do what you want using the other pattern. checked gets added, but never removed.
A more Angular-ish pattern would be something like this:
http://plnkr.co/edit/HN7tLxkRA0jUL5GPjk5V?p=preview
link: function($scope) {
$scope.checked = false;
$scope.$watch('currentValue', function() {
$scope.checked = ($scope.currentValue === $scope.imgNumber);
});
$scope.setValue = function() {
$scope.currentValue = $scope.imgNumber;
};
}
What you see here lets Angular do all the dirty work, which is kind of the point. You can actually go a lot further than this - you could probably cut half the code out and do it all with expressions. The point is that in Angular, you really want to focus on the DATA (the model). You wire all of your behaviors and events up (controller) to things that manipulate that data, and then wire up all your DOM styles, classes, templates (view), etc. up to conditionals against that same data. And that is the point of MVC!

AngularJS : Chrome hidden fields value don't update after page return

I have a form that is managed with AngularJS.
I use on ng-repeat dropdonw list to update a hidden field value.
Everything works fine until I use chrome and submit the code and then do a
"page return/Go back one page"
If i try to use the dropdonw list again it only updates the modal value in a print statement not the value of the hidden input.
<input type="hidden" name="postageId" value="{{intPostageId}}" ng-model="intPostageId" />{{intPostageId}}
{{intPostageId}} outside the input works, but the one inside doesn't update
Much appreciated
For some reason angular doesn't update hidden input values with ng-model. You will have to do a quite directive to get this to work.
You don't need the value binding you have ng-model will be enough.
module.directive('updateHidden', function () {
return function (scope, el, attr) {
var model = attr.ngModel;
scope.$watch(model, function (val) {
el.val(val);
});
};
});
I came across this problem myself. No idea why this happens but and an easy workaround I found for this is to just swap the hidden field for a text field with display:none
<input type="text" name="postageId" value="{{intPostageId}}" style="display:none">

Can I delay setting of ng-model until input has been blurred?

I have an angularjs application that does a bunch of calculations based on inputs from textboxes. I have it working pretty well, but if the user selects the numbers in the input box and deletes them, the result that's based on that number is immediately changed to undefined. In this case, angularjs is TOO fast. From a UX standpoint, I would prefer that the user is free to edit the textbox and only after they have blurred that box will the calculations update.
Is there way to make this happen right on an input field like this:
<input type="text" ng-model="model" custom-magical-directive>
I know I can create my own directive with an isolated scope and only update the parent model when I'm ready, but I'd prefer to keep it clean because if I end up with something like this:
<div custom-isolated-directive ng-model="model">
<input type="text" ng-model="isolatedModel">
</div>
styling is going to be a challenge.
If you don't want the immediate two way binding provided by ng-model, you can leave it out and use a directive to update the model when you want to:
<input type="text" update-on-blur="data.name" />
directive:
app.directive('updateOnBlur', function(){
return {
restrict: 'A',
scope: {
updateOnBlur: '='
},
link: function(scope, element, attr) {
scope.$watch('updateOnBlur', function(newVal, oldVal) {
element.val(newVal);
});
element.on('blur', function() {
scope.updateOnBlur = element.val();
scope.$apply();
});
}
}
})
Here is a demo: http://plnkr.co/dF9JbfPkgRQxWmGp57ap
A $watch function will make sure that programmatic changes are visible in the input. Then, an event handler updates the model on blur.

How to update an input's value after calling $setViewValue

I have a form input with a ng-model as well as my custom directive which reads cookie data and should set the input value:
<form>
<input type="text" id="name" ng-model="name" my-cookie-directive>
</form>
My directive:
angular.module('myApp.directives').directive('myCookieDirective', ['CookieService', function(CookieService) {
return {
require: 'ngModel',
link: function(scope, elem, attrs, ctrl) {
var cookieVal = CookieService.getCookie(attrs.ngModel);
if(cookieVal != '') {
ctrl.$setViewValue(cookieVal);
elem.val(cookieVal); //not very cool hum?
}
}
};
}]);
When logging ctrl.$modelValue I can see that the right cookie data was set to my controller variable name but the input stays blank. I know that $setViewValue does not trigger a $digest and therefore tried ctrl.$render() but nothing happens.
I ended up using jQuery to set the input's value which is not satisfying at all.
Thanks in advance :)
You are correct in not wanting to use jQuery to set the input's value. Why would you be using Angular if you are going to do that then?
You can see a new Plunker here, using a different approach to the ones being mentioned. My suggestion: use NgModelController when you want to handle validations and format the model value.
For your current situation, you can use an isolated scope in the directive, and pass to it the scope property you want to update with the cookie value. Then in the directive, you can simply do:
scope.cookieField = cookieVal;
And Angular will handle the data binding and update the view value to match the model value. Plus, this is completely reusable.
Use $render and wrap everything in a function passed to $evalAsync:
if(cookieVal !== '') {
scope.$evalAsync(function(){
ctrl.$setViewValue(cookieVal);
ctrl.$render();
});
}
Plunker demo

Resources