Format model value as date angularjs - 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.

Related

AngularJS: how to avoid the nonassign error when providing default values to missing attributes?

This is a question & answer case, because I struggled a bit with the issue, so I wish to keep track of it and its solution.
Hoping it can help somebody, and perhaps other people can provide alternative solutions.
My case: I had to work on a large AngularJS 1.4.6 project (I will name it prj), written in TypeScript, defining a number of directives (prj-ddd) with various attributes (prj-aaa), some of them being boolean.
They implemented these boolean attributes with string binding: prjEnabled: "#" or prjRequired: "#".
So they tested the values like so: if ($scope.prjEnabled === "false"), or in templates: <div ng-show="prjRequired === 'true'"> (yeah, no controllerAs usage as well...).
These attributes are optional: obviously, here, prjEnabled defaults to "true", and prjRequired defaults to "false", among others, because the tests are false when these values are undefined.
This is verbose, inelegant (IMO), prone to errors (lot of raw strings, implicit default value), and not very efficient (although probably not in a perceptible way).
So I started to replace these bindings with expression / two-way binding: prjEnabled: "=" and prjRequired: "=".
Thus, when AngularJS sees <prj-component prj-enabled="false">, it provides directly a boolean in the directive's scope.
Nice touch: with such literal values, it creates no binding, so no performance hit.
I have to cope with absent attribute, so I added in the link function of the directives something like:
if (scope.prjEnabled === undefined) {
scope.prjEnabled = true;
}
And the usages become: if (!$scope.prjEnabled), or in templates: <div ng-show="prjRequired">.
So far, so good. But we also have explicit bindings: <prj-component prj-enabled="{{someScopeValue}}"> or even <prj-component prj-enabled="{{foo.canEdit && bar.valid}}">.
Since we have two-way bindings, I just replaced them with: <prj-component prj-enabled="someScopeValue"> or <prj-component prj-enabled="foo.canEdit && bar.valid">.
Well, if someScopeValue is undefined, the directive will detect that and provide the default value, which will go up to the parent scope. Annoying, but in most cases, probably harmless. The real value is generally provided later (eg. after a network request).
But sometime, I have the following error in the console:
https://code.angularjs.org/1.4.6/docs/error/$compile/nonassign?p0=foo.canEdit%20%26%26%20bar.valid&p1=prjComponent
I followed the advice given in the corresponding page, replacing the bindings with "=?", but I still have the error.
Ideally, I would replace these bindings with "<?", avoiding the first issue (overwriting the value in the parent's scope) and the second one (no assignment to an expression).
But this needs AngularJS 1.5+, and I cannot upgrade the version in the current project.
So how can I solve this problem?
It was tricky and I had to try various solutions, until I saw the light and remembered that with "=?" binding, there is a way to distinguish an absent attribute from one with an undefined value.
I rewrote the default value assignment as such:
if (!("prjEnabled" in scope)) { scope.prjEnabled = true; }
If you are not familiar with the in operator, it is roughly equivalent to scope.hasOwnProperty("prjEnabled"), but it also checks the prototype hierarchy. I use it mostly because it is nicer / terser than the function call...
I made a Plunkr application (out of an official AngularJS one) to reproduce the error, to see when it happens, and to explore various cases: http://plnkr.co/edit/Z2AIag?p=preview
It shows that with "=" binding and assigning default value if undefined, the error appears when the attribute is empty (an unlikely case) or when AngularJS silently captures an exception (here wrong.scopeValue && wrong.unknown where $scope.wrong doesn't exist, causing the ReferenceError exception) and provides undefined as value.
With "=?", we still have the second issue (the empty attribute value case doesn't throw).
If we test with the "x" is scope method, we get rid of this annoying case, quite common when wrong is a variable filled later (network update, etc.).
The only case where the error can still happen is if we assign a value to the bound attribute, and only in the wrong case.
How to manually create one-way binding with default value:
app.directive("myDirective", function() {
return {
scope: {
//oneWay: "<?"
},
link: postLink
};
function postLink (scope, elem, attrs) {
if (!attrs.oneWay) {
scope.oneWay = defaultValue;
} else {
scope.$watch(attrs.oneWay, function(value) {
scope.oneWay = value;
});
};
}
})
For versions of AngularJS (<1.5) that do not have one-way binding, it can be implemented with a watcher.

Using ng-currency for input validation on the UI

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)

AngularJS typeahead select on blur

I'm using typeahead through in my AngularJS project and I would like to have it select the entry if I type the full value and click out of the field.
I've put together an example of what I mean
http://plnkr.co/edit/NI4DZSXofZWdQvz0Y0z0?p=preview
<input class='typeahead' type="text" sf-typeahead options="exampleOptions" datasets="numbersDataset" ng-model="selectedNumber">
If I type in 'two' and click on 'two' from the drop down then I get the full object {id: 2, name: 'two'}. This is good, if however I type 'two' and click to the next field without selecting is there a way to accept the top of the list on loss of focus on a text field?
I'm not sure if I'd want to have that sort of functionality in my app. The user hasn't actually selected anything. So selecting something for them would introduce frustrations.
But I do understand that often odd requirements are needed. In this case, I'd attack it using ngBlur. Assign a function to be called on blur. You can grab the contents of ng-model and then loop through your data (assuming static & not being sent via server) to find a match.
You can most likely just look at the source code of your typeahead directive and strip out the part does the comparison and then choose the first item in the array.
Unfortunately the underlying component does not emit any events for this condition. This will make the solution more complex. However when the value is being entered and the Typehead magic has happened you can supplement those events and catch them to update your ngModel.
I have created a plnkr based on your plnkr and although have not cleaned up but it is a working plnkr doing by far what you need.
The gist of this is following code however you can put this code wherever best suited
The explanation below:
//Crux - this gets you the Typeahead object
var typeahead = element.data('ttTypeahead');
//This gets you the first
var datum = typeahead.dropdown.getDatumForTopSuggestion();
if (datum){
//you can do lot of things here however
//..I tried to - fill in the functionality best suited to be provided by Typeahead
//for your use case. In future if Typeahead gets this
//..feature you could remove this code
typeahead.eventBus.trigger("hasselections", datum.raw, datum.datasetName);
}
In the above code you can also save the datum somewhere in the scope for doing whatever you like with it later. This is essentially your object {num: 'Six'} Then you may also use ngBlur to set it somewhere (however the plnkr I created doe snot need these gimmicks.)
Then further down - ngModel's value is set as below
element.bind('typeahead:hasselections', function(object, suggestion, dataset) {
$timeout(function(){
ngModel.$setViewValue(suggestion);
}, 1);
//scope.$emit('typeahead:hasselections', suggestion, dataset);
});
I'm with EnigmaRM in that ngBlur seems to be the way to do what you want. However, I agree with the others that this could be somewhat strange for the end users. My implementation is below (and in plnkr). Note that I trigger on ngBlur, but only apply the model if and only if there is only one match from Bloodhound and the match is exact. I think this is probably the best of both worlds, and hope it should give you enough to go on.
$scope.validateValue = function() {
typedValue = $scope.selectedNumber;
if(typedValue.num !== undefined && typedValue.num !== null)
{
return;
}
numbers.get(typedValue, function(suggestions) {
if(suggestions.length == 1 && suggestions[0].num === typedValue) {
$scope.selectedNumber = suggestions[0];
}
});
};

Angular JS ngModel: Transform data

So we have a model with a 'time' in it, formatted like '14:00:00'. We are using angular-boostrap's timepicker, but it expects a JS date object or an ISO datetime string.
My first thought was to do a directive that would add $parser/$formatter to ngModel controller that would convert it on the way in and way out to the format we need. This works smashingly for output, but not for input.
I've made a fiddle here http://jsfiddle.net/HB7LU/1820/
myApp.directive('timespan',function($filter){
var dateFilter = $filter('date');
function parser(data){
return dateFilter(data,'HH:mm:ss');
};
function formatter(data){
var converted = '2000-01-01T' + data + '-06:00';
return converted;
};
return {
require: 'ngModel',
link:function(scope,element,attrs,ctrl){
ctrl.$parsers.unshift(parser);
ctrl.$formatters.unshift(formatter);
}
};
});
As you cans when you open the fiddle the 12:00:00 isn't reflected by the timepicker. If you look at the console its complaining that ngModel is in the wrong format. If you change the time with the picker you'll see that $scope.myModel.time is in the correct format.
How do I get the ngModel to the correct format before it gets to timepicker?
timepicker in angular-bootstrap expects the $modelValue to be a date (it passes it to the Date() constructor and then parses hours and minutes from the object). Since it writes its own $render() function, based on $modelValue, your formatters aren't being used.
I'm afraid you're not going to get what you want without doing some surgery on the angular-bootstrap code. It would likely be easier to write a controller with two input fields (for hours and minutes).
A possible solution is to create directive for your Model Value input. This directive would
receive date in the same format as timepicker - timestamp or Date object and display it any way you want.
I think timepicker must use $viewValue in render method. Therefore the best solution is this surgery on the angular-bootstrap code.
Change $modelValue to $viewValue in render function.

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.

Resources