How to add custom data validator directly without specialized directive? - angularjs

In short I would like to have a custom data validator without writing entire directive for it.
Here is my case -- I have editor component/directive with several input fields. Let's say (this is content of the template of editor directive):
<input ng-model="phrase"></input>
<input ng-model="translation"></input>
The "classic" approach would be to write:
<input ng-model="translation" validator-1 validator-2 validator-3... ></input>
and add appropriate directives for those validators. But I don't like this approach, because those validators are not general purpose, they are tailored for this template only, and adding them suggests there is some kind of composition (that it would be almost OK to change something), and it is not true -- there is atomic entity "input + validators" = "translation".
Thus I would like to add custom validator directly into the code of my editor directive.
How to do it?
So far I was experimenting with finding out what is inside ngModel of my editor directive, but since the editor directive does not have model for entire editor, just for distinct fields (as shown above) I failed at requiring ngModel for my editor.

Related

Is there any way I can direct focus to a specific input field on a form?

I have a large form and I would like when the user clicks a "new" button for the focus to be placed in a specific input field. There's a grid on the form and every field has a known id. Note it might not be the first field so not easy to use the tab.
Would appreciate some advice if this is possible. Would save having to manually have the user move the cursor over and click in the input field.
Update: Changed "move cursor" to "change focus"
Here is one solution -
Angular js gives you an advantage of using some of the extra features so that you dont have to use the jquery.
Here is one way to implement the autofocus feature.
<div class="button" input-focus>{{element.idORname}}</div>
and the directive to be defined here.
.directive("inputfocus",function($timeout){
return {
link : function(element,attributes){
element.bind('click',function($timeout){
$timeout(function(){
element/*.parent() or.child()*/.find('type of the field you want to select')[0].focus();
);
);
);
Here you can use the javascript or jquery methods for the dom traversal if there are nested fields in your code.
$timeout is necessary to call for the focus after the browser renders when user has finished clicking the event
As you can see the find('')[0] is a replacement for find('').focus as the latter requires jquery to be used.
Place "autofocus" attribute on the element that you want to focus.
Example:
Name: <input type="text" name="name" autofocus />
If all the input ids are known, just use that.
$("#NewButton").on('click', function(){
//Other code.
$("#IdOfInputToBeFocused").focus();
});
Custom data attribute can be used with jQuery like this
<input data-field="special" />
And then that specific field can be called like this
jQuery('input').find("[data-field='special']").focus();

Using the same directive more than once in the same form

I'm having trouble with a task that seems trivial to me, but I just haven't got it right yet. I have a form and in that form I use a home-cooked directive called time-range. This directive has two input fields for start-date and end-date. Later on, in the view where the form is defined I want to validate each field using code similar to this:
<li ng-show="form.createAssignment.fromDateField.$error.dateinput">{{::$parent.lang.from_date | capitalize}} {{::$parent.lang.has_to_be_on_format}} {{::$parent.lang.yyyy_mm_dd}}</li>
Well, this works just fine, BUT only for validating the input field in the last directive in the form. If I enter something wrong in the first directive, the form is invalid (and thus the error list is shown) but the text that specifies what is wrong does not show up, presumably because the input in the last directive is correct.
So, somehow I would like to be able to qualify which directive I refer to, maybe something like:
<li ng-show="form.createAssignment.directive1.fromDateField.$error.dateinput">{{::$parent.lang.from_date | capitalize}} {{::$parent.lang.has_to_be_on_format}} {{::$parent.lang.yyyy_mm_dd}}</li>
However, I haven't yet succeeded with this. Does anyone have a suggestion how this can be done?
ng-form uses input name for validation, so it's hard to target specific input if you are using ng-repeat or repeated directives.
One option would be using ng-form directive, which means creating an inner form inside the form. There are plenty of guides on how to do this.
Another option would be passing a unique name (prefix/suffix) via your directive parameter. But unfortunately the variable name won't work automatically, you'll have to compile the template again after the name is passed in.

Seemingly unable to access content generated in the link() function while attempting to unit test an angular directive

I am working on an enterprise project, and as a part of that project we have a directive for creating form fields with appropriate document markup and validation/error handling. As an example, consider something like the following:
<form name="myForm">
<my-form-field label="My Field">
<input type="text" name="myField" id="myField" ng-model="myField" ng-required="true" />
</my-form-field>
</form>
The directive works exactly as expected, properly surrounding my input field with the common/expected markup, adding appropriate event handlers, etc.
One key piece of functionality is a convenience feature where the directive "notices" that the child form field has an attribute value for ng-required of true (either directly, as in this example, or because it is bound to something which is evaluated). This attribute does the "normal" angular thing (hooking into field-level validation in the angular workflow), but then my directive also notices it's presence in the child element, and appends " *" to the label value when taking the label attribute of the directive element and creating an HTML as part of the markup which is generated.
When attempting to unit test this in jasmine/karma, I see the logic in the template() portion of my directive firing (e.g., the tag is generated), but not the items in the link() portion (e.g., adding the asterisk to the label text, updating the for="" attribute of the label tag to match the id="" attribute of the child tag).
After spending the morning researching various demos/tutorials on unit testing angular directives, I found an example which didn't help me much...but did offer a smaller/more basic example of what I am seeing: http://plnkr.co/edit/tirhLwFEXLKSzukbsW1q?p=preview
In this example, seeing the attribute values set by the link() function of the direction is easily handled (see the expectations at lines 23-25), but the rendered html still doesn't seem to be compiled (outputting element.html() anywhere in that it() still shows the angular variables - like {{values.center}} - instead of the compiled values). I recognize that I could put computed values on the scope and then follow this pattern of validating by checking the directive's scope...but in my case, I am further modifying the html within the link() portion (quite successfully actually, despite what the unit tests appear to be showing).
What am I missing in terms of being able to "see"/validate the compiled HTML (e.g., validate that an asterisk was added to the label text when appropriate)? I thought about doing all of my markup work in the template() portion of my directive, but then I can't see how I could inspect the child input element (to get the id for the for="" attribute of the label, and to check if it is required or not so that I know if the asterisk is appropriate for the label text).
EDIT:
- This plunkr is most advanced of my three scenarios, and fails (basically a copy of my code, including trying to combine all the angular template generation/compilation into one common function, based on an example I had seen in a few different places): http://plnkr.co/edit/AxGAUKYFvm2yf1vPsWD3?p=preview
In the third plunkr (see my comment below for the first two, each of which works as expected), the error thrown is actually "TypeError: currentSpec.queue is undefined...", which is likely my culprit - but I can't seem to find a way around it. I actually wasn't seeing this error locally...because I had seen some advice in a few places to change this method within angular-mocks.js:
isSpecRunning = function() {
//return currentSpec && (window.mocha || currentSpec.queue.running);
return !!currentSpec;
};
The second line is the original, which was commented out/replaced with the third line. This removes the error shown in the Plunk, and instead my unit test runs just fine...except for it doesn't seem to show the final compiled version (e.g. the label's for attribute is never populated, and the asterisk is not added to the label text when appropriate). So with the suggested change, my test runs ok - but fails to see the
"final" markup which I see when I actually run my site. Without the change to angular-mock.js, I see the error you see in the plunkr.

Non-unique input names in Angular scope

I can refer to properties of inputs in an Angular scope like this:
<form name>.<input name>.$dirty
However this doesn't work if I have multiple inputs with the same name (e.g. in a sub-section of the form generated with ng-repeat). In that case <form name>.<input name> just holds a reference to the first input with that name.
I'm trying to DRY the logic around displaying error messages / classes. To do that I really need to be able to check validity, dirty state and so on. The only other way I can think to do it is to look for ng-(dirty|invalid) classes on the input element which feels like a dirty hack.
I also tried using the $index variable of ng-repeat in the input name (e.g. <input name="foo[{{$index}}]">) but then there's only a single property in the FormController with that literal string – not separate ones like foo[0], foo[1] etc.
Is there another way to handle this?

Setup bi-directional binding from an object defined in an html attribute in AngularJS

I have implemented a remote validation directive which queries a specified JSON API endpoint once an input is blurred. It expects the response { valid: true|false }.
I now have to extend it to allow for it to send a request involving multiple values from the parent scope.
My tag definition looks as follows:
<input remote-validate endpoint="/api/action/:value" ng-model="MyInput" />
where :value is substituted with a urlencoded value of $scope.MyInput.
This is working well.
What I require is given an endpoint like this /api/action/:value/:person/:thing, the :person and :thing substitutions are bound to the parent scope values.
My initial thought is to have a bindings attribute which maps the parent scope to the endpoint.
<input remote-validate endpoint="/api/action/:value/:person/:thing" bindings="{person: 'firstName', thing: 'thingName'}" ng-model="MyInput" />
(...)
<input ng-model="firstName" /> <input ng-model="thingName" />
given var bindings = scope.$eval(attrs.bindings); is there any way to loop through the bindings object and create a two way binding to the parent scope?
EDIT: A workaround may be to do this:
<input validate-remotely
endpoint="api/action/:value/:param1/:param2"
param1="person"
param2="thing" />
which obviously means I can only use the number of parameters I specify in the scope definition. Which is a good work around for me here. Would be nice to know if there was a way to create these bindings dynamically at compile/link time.
I can provide a fiddle but I don't have the time right now, so I'm hoping that someone will have a good idea if/how this is possible.
As I learned yesterday, you can use $parse on your 'binding' attributes. See this post. This is a good way to $watch an attribute for changes that come from parent or children
Example:
<div parse-test bindings="{person: 'firstName', thing: 'thingName'}"></div>
// in your directive link function:
scope.bindings = $parse(attrs.bindings)(scope);
scope.$watch('bindings', function(val){
for (i in scope.bindings){
scope[i] = scope.bindings[i];
}
}, true);
Experiment with this plunk
This is how I solved it in a not so ideal way.
See this plunker, which has a copy of the directive I use live and works well in my app but doesn't work in Plunker (I expect the randomly chosen plunker api endpoint to be called and fail, but it is not called due to some strange errors I don't care to debug).
The problem I had with the proposed workaround in the EDIT of this question is that it seems you cannot use scope and require in the same directive. I would love it if someone could elaborate on if this is the case and why.
The Workaround
I used a params attribute to specify additional data in object notation to be extended to go to the resource request. I used the handlebars syntax in the params attribute to dynamically change the object notation string.
<input ng-model="value2" ... />
<input validate-remotely
endpoint="/api/:value/?thing=:anotherValue"
params="{ anotherValue: '{{ value2 }}' }" ... />
Not so great :/ but it works.
Then on blur of the validated input, I re-$eval the object string (app.js:40 in the plunker) and extend the resources data to include this object, which using ngResources colon (:) notation, replaces the URL.
The validation has 3 states:
remoteValidityPending: field validation fails because it is still being checked - good for showing a spinner.
remoteValidityUnchecked: The field has changed since it has been checked but has not yet blurred - ensures that any `ng-disable="form.$invalid"' submit buttons stay disabled until we know that the backend has returned a response.
remoteValidity: If this passes, the fields endpoint has been called and '{valid: true}' has been returned from the server.
I'm almost certain there are better/different ways to solve this, and I'll be happy to change the answer if someone improves this directive. I hope this helps someone out there.

Resources