Using ng-currency for input validation on the UI - angularjs

I'm using the ng-currency directive (found here) to fulfill some input validation on a project and would like to make some changes to the source code.
Currently, while the directive takes any form of input from the user, it does all the filtering on the data model only. E.g. even if the input field has "ABC" in it, the model value remains unchanged because it is not real currency input, but the view takes all alphanumeric characters and symbols. This is almost perfect but only half the job for my case, as I want to block any illegal characters from being keyed on the actual input UI (where illegal characters are anything other than 0-9 digits, a period ('.'), and a dash ('-')). Demo of directive here
I've made a Regex which I believe handles the cases I need (an optional '-' sign only at the beginning of the input that can only appear once to denote negative currency values; and restriction to 0-2 decimal points after an optional '.', which also can only appear once)
[-]?[\d]+[.]?[\d]{0,2}
Now my issue is that I am fairly new to Angular and JS so I am unsure where to place this regex in the directive I am using, let alone how to achieve this desired result. I would also like to set the maxlength attribute to 11 characters within this directive but again, it's been overwhelming for a beginner who only recently learned about ng-repeat and other angular patterns to navigate through this directive. I have a hunch that I would add an element.on('keypress') function that would test the regex pattern, but again it's only a hunch and I'd like to consult the experienced before I attempt to spend more time figuring this out.
Any and all help is greatly appreciated, thanks for your attention.

You should use a directive to restrict characters on keydown.
Based on this directive you can customize it to:
angular.module('app').directive('restrictTo', function() {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var re = RegExp(attrs.restrictTo);
var exclude = /Backspace|Enter|Tab|Delete|Del|ArrowUp|Up|ArrowDown|Down|ArrowLeft|Left|ArrowRight|Right/;
element[0].addEventListener('keydown', function(event) {
var v=element[0].value + event.key;
if (!exclude.test(event.key) && !re.test(v)) {
event.preventDefault();
}
});
}
}
});
And this to your input
<input type="text" ng-model="value" ng-currency restrict-to="[-0-9.]$">
You can see the demo here
Edit: Thanks to Bang for new regexp. It's much better than original.
Edit: Use this instead restrict-to="[-0-9.]$" as the goal is to restrict to 0 - 9 and the . (and - perhaps; not sure how that is a valid currency)

Related

Format model value as date angularjs

I'm improving and automating certain things in an old web app. One of them is the date format. We used to handle these with jquery functions and now we are doing it with angularjs.
For the input we use a directive and it works perfect. The problem occurs when it is not used, the directive is not executed and the value of the model is left without the proper value.
Directive:
app.directive('formatDate', function($filter) {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, modelCtrl) {
// format text (model to view)
modelCtrl.$formatters.push(function(value) {
if(value !== "" && typeof value !== "undefined" && value !== null){
return value.split("-").reverse().join("/");
}
});
// format text (view to model)
modelCtrl.$parsers.push(function(value) {
if(value !== "" && typeof value !== "undefined" && value !== null){
var date = new Date(value.split("/").reverse().join("-"));
date.setMinutes(date.getMinutes() + date.getTimezoneOffset());
return date;
}
});
}
};
});
Issue:
When you load the value from webservice, for example: "invoice.date" comes from the database with the format "yyyy-mm-dd". It is loaded in the input with format "dd/mm/yyyy" and if the input is edited the model value is a "Date object" thanks to the directive. But if no field is edited, the value remains "yyyy-mm-dd" and that string causes errors. (Note: they use webservices with jpa and I can not change anything in backend)
How to format the value before sending it without doing a manual verification and analyzing the value in each function? Can I use $watch over each of the variables without causing a conflict with the directive or an infinite loop? Angular has some other way to automate this?
Thanks for any help.
I have seen this many times, it is quite common and you are using the time because jpa or the database changes the date because Timezone, right?
ok, then it comes from a webserice so it probably comes in json format. You can simply convert to a date object before assigning it to the model value and thus always use this format. Another way is to convert it before sending it and you've already said that's what you want to avoid.
There are many things that can be done. To sum up:
(choose one)
Convert the format upon receipt and before assigning it. either
manual, by simple javascript or any other method.
On the reverse, before sending, make the format change.
To avoid doing it manually, look for it with functions or events, if
$ watch can be used to detect in which format it comes and change it
correctly. So if it is assigned programmatically, it will also work.
There are other methods to filter and elaborate the response but I
think it would be too much for this case. As Jorge said, there are some plugins, tools and more that can be added. But I always try to avoid overloading with so many things.
The problem is to try to automate with angularjs, because there are many ways and finding the "right" way is difficult since each project is different and each person has a different way of thinking.

AngularJS decimal input: change comma to dot

I'm using FCSA Number to handle decimal input in AngularJS.
If I use the maxDecimals option it almost works as expected. For two decimals, inputs like "100.333" are transformed to "100.33".
In my region comma is used as decimal separator, but the input on my website should use dot as decimal separator -- like this plugin does. That's fine. However, I would like that input like "100,33" are converted to "100.33".
How can I do that?
I am afraid I am unfamiliar with FCSA number :(
However, if your goal is to understand how to implement a simple, angular-oriented solution that may well suit your needs, read on...
You will find below the price directive I implemented in a plunker. It is quite straightforward, and I recommend you take the time to study it, then implement your own solution, inspired by both the price directive and the FCSA source code.
a filter to convert comma numbers to decimal numbers:
app.filter('comma2decimal', [
function() { // should be altered to suit your needs
return function(input) {
var ret=(input)?input.toString().trim().replace(",","."):null;
return parseFloat(ret);
};
}]);
This filter will automatically convert data from view format (with a comma) to model format (your scope, with a decimal).
a filter to convert decimal numbers to comma numbers:
app.filter('decimal2comma', [
function() {// should be altered to suit your needs
return function(input) {
var ret=(input)?input.toString().replace(".",","):null;
if(ret){
var decArr=ret.split(",");
if(decArr.length>1){
var dec=decArr[1].length;
if(dec===1){ret+="0";}
}//this is to show prices like 12,20 and not 12,2
}
return ret;
};
}]);
This filter will automatically convert data from model format (your scope, with a decimal) to view format (your view, with a comma).
a directive named price that uses those two filters:
app.directive('price', ['$filter',
function($filter) {
return {
restrict:'A',
require: 'ngModel',
link: function(scope, element, attrs, ngModelController) {
ngModelController.$parsers.push(function(data) {
//convert data from view format to model format
data=$filter('comma2decimal')(data);
return data;
});
ngModelController.$formatters.push(function(data) {
//convert data from model format to view format
data=$filter('decimal2comma')(data);
return data;
});
}
};}]);
See this working plunker, showing how everything works together.
Initially, a number (e.g. coming from database) has a decimal value in controller scope ($scope.price=25.36;);
In the view, it appears like: 25,36 in the input, and 25.36, as in the database, below.
Now enter any comma number: it is automatically converted to decimal number for insertion in database.
Hoping this answers your question.
Advantage of using a directive: it is reusable, everywhere you need it on your website.
Advantage of using two filters: separation of concerns.
This approach lets users use numbers they are most used to in your views.
However, the numbers entered can be altered in the background before insertion into database, so they are formatted properly.
And when you fetch prices/numbers from the database, they are automatically altered, before showing in the view in a way better suited to the reader.
If you need other options like in the FCSA Number directive, you can easily add them in your filters.
You will probably need custom functions and model validation, using ngModelCtrl.$setValidity.
Add your corresponding locale:
<script type="application/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/angular-i18n/1.5.0/angular-locale_en-us.min.js"></script>
You can check the list of available locales here: https://cdnjs.com/libraries/angular-i18n
You just need to use angular number filter and change angular's default separator and group characters.
app.run(["$locale", function ($locale) {
$locale.NUMBER_FORMATS.GROUP_SEP = " ";
$locale.NUMBER_FORMATS.DECIMAL_SEP = ".";
}]);
Then in your HTML:
<p>{{ the_number | number:0 }}</p>
I used an unorthodox solution. In the .js of Angular I looked for "DECIMAL_SEP" and changed:
DECIMAL_SEP:".",GROUP_SEP:","
to
DECIMAL_SEP:",",GROUP_SEP:"."
And it works! isn't the best solution but it works... I Just want to help.
PS. I'm from Colombia, sorry if my English is not well.
Shorter way:
app.filter('commaToDecimal', function(){
return function(value) {
return value ? parseFloat(value).toFixed(2).toString().replace('.', ',') : null;
};
});
Solution:
.filter('dot2comma', [
function() {
return function(value) {
return value.toString().replace(/\./g,',');
};
}]);

Custom Form Validation for AngularJS- how to check for specific words in a textarea the angular way

I am creating an app using AngularJS where a user is given a list of words he must include in a form he will submit. For example, if the list of words is "Shnur" and "Bdur" the form will be valid if the user enters: "Shnur and Bdur were walking down the street when..."
but not valid if the write "Shnur went off in search of some grub." because only one of the two required words was included. I would also like to be able to dynamically change the color of the word/phrases once the user enters it.
Currently, I have this in my controller:
function ($scope, $routeParams, $location, Global, Submissions, Phrases){
$scope.Global = Global;
$scope.phrases = _(Phrases).shuffle().slice(0, 5);
I am injecting the Phrases service and am randomly selecting a few phrases the user must incorporate into his submission. Based on my research, it seems like the next step will be to write a directive which does the custom form validation, but there seems to be so many ways to do that and I'm really having trouble getting a clear sense of the structure of the directive that will be appropriate for this specific case. (This is my first real angular app)
I'm not totally sure it's the best solution, but here is what I would have done for something like this : http://jsfiddle.net/DotDotDot/K2S4v/
As you thought, I made a directive which is kind of complicated, so I will try to explain it here :
The structure of the directive is something like :
Passing the list of word to compare in argument
Adding an input field to the template
Listening to modification in this field
Checking new words, and counting the number of matching ones
Displaying and stuff like this
.directive('validate', function(){
return {
restrict:'A',
template:see below,
replace:false,
the template looks like this for the example, but actually it could be empty, it was a lot easier to use this for the display
<p><span ng-bind-html-unsafe="theDisplayedText"></span><br/>{{count}} words corresponding</p>
Then, the important part, I used the compile function, to add the input field, and then to specify the behavior in the postLink function
Adding the field (this could be done directly in the template too, not the main point)
compile:function(elt,attr){
var inp=new angular.element('<input>')
inp.attr('type','text')
inp.attr('ng-model','theText');
inp.attr('ng-change','changed()');
elt.append(inp)
then to specify how the directive has to react, the linking function :
return {
pre: function preLink(scope, iElement, iAttrs, controller) {
},
post: function postLink(scope, iElement, iAttrs, controller) {
scope.count=0;
var copyOfWords= new Array(iAttrs['list'])//the list to compare with
scope.changed=function(){
var alreadyIn=Array();
scope.theDisplayedText=angular.copy(scope.theText);
var sliced=scope.theText.split(" ");//we get each word
scope.count=0
for(var i=0;i<sliced.length;i++){
if(sliced[i].length>3 && copyOfWords.indexOf(sliced[i])!=-1 && alreadyIn.indexOf(sliced[i])==-1)//the word exists and isn't counted yet
{
scope.count+=1//incrementing the counter
alreadyIn.push(sliced[i])//avoiding doubles
sliced[i]="<span class='hi'>"+sliced[i]+"</span>"//display in red
}
}
scope.theDisplayedText=sliced.join(" ");
}
}
}
The function is really naive and not really optimized, this might be the part you will have to change, but it works for the example. It's quite simple, you take the whole text, split it in words, then compare them with the list of words in argument. It has some issues (it appears that indexOf() on string arrays return also a result for every substring, so if you begin to type a word, it will be counted) but it's only for the example, in your application, you may have to perform better checks, or whatever you want (it's your app :) )
I hope this will help you in your application, if you have some issues concerning directives, there are many information in this site : http://amitgharat.wordpress.com/2013/06/08/the-hitchhikers-guide-to-the-directive/
Have fun =)

New to Angular - Computed Variables

I am moving to Angular from Knockout, and I have a few issues. I'm assuming that I must be doing something a non-angular type of way.
http://jsfiddle.net/LostInDaJungle/BxELP/
I linked to jsfiddle so I didn't have to include my code here
Stack Overflow will not let me post my question without a code block.
Here is a very basic fiddle that outlines two of my major problems...
Problem 1: val1 and val2 are initialized as 3 and 4, and add up to 7 properly. However, if you change either of the values in the text boxes, the new value is treated as a string and I get concatenation instead of addition. Change val1 to 4 and you get 44 when it should be 8. What is the best way around this behaviour?
Problem 2: Calculated fields. I can get a calculated field by using the curly brackets like {{val1 + val2}} and have the calculated fields auto update when the underlying model changes, but this is totally unacceptable. In my full fledged app, we generate a "cost" that is used several times throughout and having to put in the cost calculation each and every time is a pain. Not to mention that when this calculation changes, I now have the unenviable task of finding 15 different places that use the cost calculation and updating them all.
In addition, if I try to put an ng-model="cost" on the input with the curly brackets, then the curly brackets don't work. So nothing jumps out at me as a way to bind cost.
http://jsfiddle.net/LostInDaJungle/QNVwe/
This example is more like the structure I desire. However, unlike a ko.observable, the calculated fields do not update when the values that generate them change. The boilerplate solution everyone has foisted on me is to write a bunch of ng-change handlers... But that is awful. If width changes change the cost and change the payback calculations, etc... It quickly becomes a tangled mess.
Both of these methods fail as far as separating logic from presentation. Method one has my business logic embedded in my HTML. Method two puts a whole bunch of ng-change handlers in my code which isn't that much different from having to write a whole mess of onChange handlers in plain ol' HTML. If I HAVE to do a bunch of ng-change handlers, I would just as soon do an onChange handler in Javascript because I can at least declare them outside of my presentation layer.
Here's a knockout version of the same:
http://jsfiddle.net/LostInDaJungle/eka4S/2/
This is more like what I would expect... Nothing but data-binds on my inputs, all program logic nicely contained within the view model. Also, since my computable is a Javascript function, I don't have to scratch my head about how to ensure my two values are numeric.
So....
Computed variables: Is there a way to watch the underlying variables and update the computed amount automatically? Without having to bury my program logic in my HTML?
Is there a good way to keep my numbers from turning into strings?
Thank you for your help.
FYI, also posted to Google Groups: https://groups.google.com/forum/#!topic/angular/0dfnDTaj8tw
For a calculated field, add a method to your controller . . .
$scope.cost = function() { return $scope.val1 + $scope.val2 };
and then bind to it directly. It will know when it needs to recalculate as its constituent values change.
<div>{{cost()}}</div>
Ok,
A few hours later and I think I have my answer.
Using $scope.$watch.
$scope.$watch('(height * width) * 40', function(v) {$scope.cost = v;});
or
$scope.$watch('height + width', function() {$scope.cost = (Number(height) * Number(width)) * 40;});
This auto-updates any computables for watched variables. And it gives me a way to work with these without having to live inside curly brackets.
Also, the computed values can be reused and tracked for cascading updates:
$scope.$watch('height * width', function(v) {$scope.dim = v;});
$scope.$watch('dim * 40', function(v) {$scope.cost = v;});
So if height and/or width change, dim is updated, and since dim has changed, cost is updated.
I changed your third input to:
<input type="text" value="{{val1 * 1 + val2}}" />
which causes Angular.js to treat the values as numbers, not strings.
Here is the fiddle. I gleaned the answer from here.
About problem 1:
You should use input type="number" if possible. That would take care of parsing numbers properly. Even if you have an older browser angular would take care of formatting them as numbers.
About problem 2:
Your answer is good Jason if you just need to show plain text on the screen. However if you would like to bind an input with a model to an arbitrary expression, you need something else.
I wrote a directive you can use to bind an ng-model to any expression you want. Whenever the expression changes the model is set to the new value.
module.directive('boundModel', function() {
return {
require: 'ngModel',
link: function(scope, elem, attrs, ngModel) {
scope.$watch(attrs.boundModel, function(newValue, oldValue) {
if(newValue != oldValue) {
ngModel.$setViewValue(newValue);
ngModel.$render();
}
});
}
};
})
You can use it in your templates like this:
<input type="text" ng-model="total" bound-model="value1 + value2">
Or like this:
<input type="text" ng-model="total" bound-model="cost()">
Where cost() is a simple function of the scope like this:
$scope.cost = function() { return $scope.val1 + $scope.val2 };
The good thing is that you keep using a model for your input and you don't have to dinamically update your value attribute, which doesn't work well in angular.
I'm new to AngularJS but I think that $parse could be used:
http://docs.angularjs.org/api/ng/service/$parse
This is interesting if you have the expression as a string. You can use a path of properties and that string can be generated dynamically. This works if you don't know the expression at compile time, a lot like eval() but probably a lot faster and maybe more secure(?).
Here's an example:
function Ctrl($scope,$parse) {
var expression = 'model.val1 + model.val2';//could be dynamically created
$scope.model = {
val1: 0,
val2: 0,
total: function() {
return ($parse(expression))($scope);
}
};
}
u can bind to a function
function CTRL ($scope) {
$scope.val1 = 3;
$scope.val2 = 4;
$scope.sum = function(){
return ($scope.val1 *1 + $scope.val2 *1);
};
}
it will work the same
the binding expression will work but in much more complex cases we need functions
The $watch function that is made available through the $scope variable is best for this job in my opinion.
$scope.$watch(function(scope) { return scope.data.myVar },
function(newValue, oldValue) {
document.getElementById("myElement").innerHTML =
"" + newValue + "";
}
);
The $watch function takes in a:
value function
& a listener function
The above example is taken from this awesome article: http://tutorials.jenkov.com/angularjs/watch-digest-apply.html
After reading through it, I learnt a lot and was able to implement the solution I was looking for.

Angular - Form validation issues when using form input directive

I have been trying to build a form input directive which will generate a form input based on the model and the model attributes .
For example,
if the field is name and type is text, the directive will return a input html control,
if the field is a list, then it will return a select box
and so on
These inputs are generated using ng-repeat in the view. The inputs are bound to the model in the scope. This is working fine. However, the form validation fails; i.e if the input controls are invalid, the main form still shows the form is valid.
I have put up a simple plunkr to illustrate the issue - http://plnkr.co/edit/R3NTJK?p=preview
NOTE : I have actually nested the form, as the input name field is also dynamically generated from the scope model.
I have been trying to a get hold on this from the past 2 days and this is really driving me nuts.
I m not sure if I m missing something.
I would really appreciate if some one could help me out with this.
Update:
Use the following link function:
link: function linkFn(scope,elem,attr){
var jqLiteWrappedElement =
angular.element('<input type="url" name="socialUrl" ng-model="social.url">');
elem.replaceWith(jqLiteWrappedElement);
$compile(jqLiteWrappedElement)(scope);
}
Plunker.
For reasons I don't understand, the replaceWith() must be executed before the call to $compile. If someone can explain why this is so, we'd appreciate it!
Update2: in the comments below, Artem mentioned that the DOM must be modified before the linking function is called, so this also works:
var myElem = angular.element('some html');
var linkFn = $compile(myElem);
element.replaceWith(myElem);
linkFn(scope);
Original answer:
Instead of the link function, just use a template in your directive:
template: '<input type="url" name="socialUrl" ng-model="social.url">'

Resources