angular JS dynamic tooltip creation - angularjs

EDIT:
It seems like the Error or wrong handling is because the scope does not gets updated when the email field is not valid... is there a way to chang that?
this is my first question on stackoverflow so i hope i will do it right.
I am pretty new to angular js and i am creating some basics at the moment.
in my demo app i created a normal form in a bootstrap style and i was planing to create a directive to show the errors in a bootstrap way. so far so good. that was working and my next step was to create a angular js bootstrap tooltip directive when the form is not valid. the thing is, that i wanna do this dynamic.
i post some code to explain it better:
<b>HTML:</b>
<div class="container" ng-controller="LoginCtrl as vm">
<form id="login" name="vm.loginForm" class="form-signin" ng-submit="vm.login()" novalidate>
<div show-errors>
<input type="email" name="username" ng-model="vm.credentials.username" class="form-control" placeholder="Email address" required autofocus>
</div>
<div show-errors>
<input type="password" name="password" ng-model="vm.credentials.password" class="form-control" placeholder="Password" required>
</div>
<button class="btn btn-lg btn-primary btn-block" id="submit" type="submit">
Login
<span class="spinner"><i class="fa fa-refresh fa-spin"></i></span>
</button>
</form>
</div>
<b>showError Directive:</b>
(function () {
angular.module('testapp.Validate', []).directive('showErrors', validationDirective);
/**
* #name validate directive
*/
function validationDirective($compile) {
return {
require: '^form',
restrict: 'A',
link: function (scope, el, attrs, formCtrl) {
var inputNgEl = angular.element(el[0].querySelector("[name]"));
var inputName = inputNgEl.attr('name');
inputNgEl.bind('blur', function () {
toogle(inputNgEl, el, formCtrl[inputName]);
});
scope.$on('show-errors-check', function () {
toogle(inputNgEl, el, formCtrl[inputName]);
});
}
}
}
function toogle(inputNgEl, fromGroup, inputField) {
fromGroup.toggleClass('has-feedback has-error', inputField.$invalid);
if (inputField.$invalid && !fromGroup[0].querySelector(".glyphicon-remove")) {
inputNgEl.after('<span class="glyphicon form-control-feedback glyphicon-remove"></span>');
} else if (!inputField.$invalid) {
if (fromGroup[0].querySelector(".glyphicon-remove") != null) {
fromGroup[0].querySelector(".glyphicon-remove").remove();
}
}
}
})();
That is working so far . it just adds a has-feedback,has-error class to the parent div and a span with an error-icon after the input.
but back to my plan, now I also want to create a dynamic tooltip for the input. so I planed to add something like that in the "toogle" function
inputNgEl.attr('uib-tooltip',"aaaa");
inputNgEl.attr('tooltip-placement',"right");
inputNgEl.attr('tooltip-trigger',"mouseenter");
inputNgEl.attr('tooltip-class',"testTool");
But that is not working because the input field got already compiled before.
so I asked google about it and there I found some solutions with $compile
$compile(inputNgEl)(scope);
But when I am using it, and I type in a valid email address the field gets reset. aaa#aaa (still working) but after I add the aaa#aaa. (the field gets reseted - I guess compiled again). the tooltip would work btw.
Can anybody help me with that or is there a better solution to create a dynamic angular bootstrap tooltip?

Maybe you need to add a $watch on the input element, and see if it changes, add a tooltip in the input element and compile it

Related

AngularJS form validation: indicate required fields to user

I would like my form labels to display a red asterisk next to the label when the corresponding form control has a required attribute.
Instead of hard coding the asterisk, I desire a way to append the asterisk to the label dynamically during page load if the label's corresponding input, select or textarea is required (the element the label corresponds to).
I created the directive below, and the directive works. But is there a better, more native way to accomplish my goal? This directive finds all the div.form-group containers and adds a red * character after the label if the corresponding form control inside the div.form-group has a required attribute.
myApp.directive('labelsRequired',function(){
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attrs){
elem.find('div.form-group').each(function(i, formGroup){
var formControls = $(formGroup).find('input, select, textarea');
console.log(formControls)
if (0 !== formControls.length && undefined !== $(formControls[0]).attr('required')){
jLabel = $(formGroup).find('label');
jLabel.html(jLabel.html()+ "<span class='red-color'>*</span>");
}
})
}
}
});
The directive assumes all inputs, selects, and textareas are inside a div.form-group container.
<div class='form-group'>
<label>First Name</label><!-- this label gets an asterisk -->
<input name='fname' required />
</div>
<div class='form-group'>
<label>Favorite Food</label><!-- this label does not get an asterisk -->
<input name='favFood' />
</div>
You don't need a directive, there are built-in form properties you can use with filters like ng-show, look:
<div ng-app="myApp" ng-controller="myCtrl">
<form name="userForm" novalidate>
<div class='form-group'>
<label>First Name</label>
<input name='fname' ng-model="fname" required />
<label ng-show="userForm.fname.$dirty && userForm.fname.$error.required">* Required field</label>
</div>
<button type="submit">Submit</button>
</form>
</div>
If you define an ng-model for the input you can deal with it looking if it is filled or not. You can also check it only after the user "dirty" it with userForm.fname.$dirty, so the label will be shown only after a user try to input something but then clear it. Try playing with it here JSFiddle
Building off of Corey's answer:
I just used compile rather than link, as I saw that my required attribute was not being applied to my input elements. I also included a select tag for any dropdowns that I had.
app.directive('inputRequired', function () {
return {
restrict: 'A',
compile: function (elem) {
elem.find('label').append("<sup><i class='fa fa-asterisk'></i></sup>");
elem.find('input').attr('required', 'required');
elem.find('select').attr('required', 'required');
}
};
});
If you're not using the built-in Angular validation, you could restructure your directive and attach it to your .form-group element. Like this:
app.directive('inputRequired', function() {
return {
restrict: 'A',
link: function(scope, elem, attr) {
elem.find('label').append('<span>*</span>');
elem.find('input').attr('required', 'required');
}
};
});
Your HTML would then look like:
<div class="form-group" input-required>
<label>Name</label>
<input name="name" />
</div>
<div class="form-group">
<label>Food</label>
<input name="food" />
</div>
However, if you haven't looked into the built-in validation with Angular, I would recommend using it.
This might come too late and it might not be too elegant but it works, if anyone needs it:
<label ng-show="userForm.fname.$validators.hasOwnProperty('required')">* Required field</label>

Fire a method when click an Enter key

<form novalidate name="frm1" autocomplete="off">
//UI elements
<div class="col-sm-3 col-sm-offset-6">
<button ng-click="MyFunc()">Next Step</button>
</div>
</form>
Can you tell me how to fire MyFunc() method when click the enter key.On the above form where there is no submit button. Thanks in advance.
Try this:
<input ng-keyup="$event.keyCode == 13 ? MyFunc() : null" >
At form level you can use this:
<form ng-submit="myFunc()" ...>
I have written below mentioned directive and it works.
Directive :
angular.module('app.directives')
.directive('ngEnter', function () { //a directive to 'enter key press' in elements with the "ng-enter" attribute
return function (scope, element, attrs) {
element.bind("keydown keypress", function (event) {
if (event.which === 13) {
scope.$apply(function () {
scope.$eval(attrs.ngEnter);
});
event.preventDefault();
}
});
};
})
HTML
<form novalidate name="frm1" autocomplete="off">
//UI elements
<input name="userName" type="text" ng-enter="MyFunc()"/>
<div class="col-sm-3 col-sm-offset-6">
<button ng-click="MyFunc()">Next Step</button>
</div>
</form>
Simply write (keyup.enter)="your_function_name()" in your html file in same way you write (click).
Below is a code snippet.
<input type="text" [(ngModel)]="modelSearchedData" (keyup.enter)="getModelList()">
What you should do is binding your function on submit event instead of enter key. You should not focus on enter, because for exemple on the iPhone there is a keyboard button to execute form, which behave like enter but is not enter event ... and the iPhone is just one exemple :D
So you should change your <button> by an input <type="submit"/>
This way enter key will automatically fire the form submit event.
Then in your submit event, return false; to prevent HTML action (which send the form) and execute your code.
HTML
<form novalidate name="frm1" autocomplete="off">
//UI elements
<div class="col-sm-3 col-sm-offset-6">
<input type="submit" value="Next Step" />
</div>
</form>
JS
$('form').submit(function () {
MyFunc();
return false;
});
I hope this answer your question.
PS : you can use ng-submit instead of jQuery selector, if you do not want to use jQuery.
I found a solution that does not require a directive. I was already using ng-change to capture each keypress and perform a search, but clicking Enter would throw my SharePoint page into Edit mode. SharePoint doesn't let you access the form tag, so most of these solutions didn't work for me.
This solution was much simpler and kept my code in the same place, I have an ng-change AND an ng-keypress event that point to the same event handler, vm.txtSearchChange():
HTML
<input id="txtSearch" type="text" style="width: 400px;" ng-change="vm.txtSearchChange()"
ng-keypress="$event.keyCode == 13 ? vm.txtSearchChange($event) : null"
ng-model="vm.Search" ng-model-options="{debounce: 200}"/>
Note the ng-change event does not pass the $event attribute, and handles the legitimate key presses, while the ng-keypress event is only for the enter key.
SCRIPT
vm.txtSearchChange = function ($event) {
if ($event) {
$event.preventDefault();
return;
}
console.log("Search: " + vm.Search);
vm.showResults();
} // end vm.txtSearchChange
When $event is not null, it's the enter key, we call preventDefault() and don't process further. When $event is null, it's a valid key, and we pass it along to vm.showResults() for processing.
Most of the answers here involve additional workarounds that simply are not needed, you can work with the standard form submission by making these two small changes and the Enter key will function as desired.
Move the ng-click from the button to the form, and make it an ng-submit
Add a type="submit" to the button
For more complex forms with multiple buttons you might need to try some of the workarounds, but for the majority of cases this will work.
<form novalidate name="frm1" autocomplete="off" ng-submit="MyFunc()">
//UI elements
<div class="col-sm-3 col-sm-offset-6">
<button type="submit">Next Step</button>
</div>
</form>
<input type="text" name="Inputvalue" id="Inputvalue" ng-change="EnableDisableCheckButton()" ng-model="enteredInputValue" ng-disabled="isDisabledInputvalueTextbox" ng-blur="" data-ng-keypress="onEnterKeyPress($event)" />
<button type="button" class="btn btn-primary" ng-disabled="isDisabledCheckButton" ng-click="ValidateInputvalue()">Check</button>
And in your JavaScript file, add below:
$scope.onEnterKeyPress = function (event) {
if (event.charCode == 13) //if enter is hot then call ValidateInputvalue().
$scope.ValidateInputvalue();
}
This example worked for me:
HTML Code:
<input matInput type="text" [(ngModel)]="searchString" ng-change="startSearch()" ng-keypress="$event.keyCode == 13 ? startSearch($event) : null">
Typescript:
#HostListener('document:keypress', ['$event'])
startSearch(event: KeyboardEvent) {
if (event.code === "Enter") {
//Code that you need to run
}
}
https://go.tiny.cloud/blog/angular-5-tutorial-step-step-guide-first-angular-5-app/

Angular directive with custom / conditional actions

I have questions about Angular directives. The following is my code:
main controller & the directive:
<div ng-controller='ShopsController'>
<update-createform shop="shop" action='update()'></update-createform>
</div>
directive js:
(this way the direction action will take the 'action' input argument)
angular.module('app')
.directive('updateCreateform', function(){
return {
templateUrl: '/form.html',
restrict : 'E',
scope: {
shop: '=',
action: '&'
}
}
})
form.html template:
<form name="shopForm" ng-submit='action(shopForm.$valid)' novalidate>
<input type='text' name='name' required/>
<input type='text' name='description' required/>
</form>
ShopsController has a method:
exports.update = function(isValid) {
if (isValid) { /* update the shop*/ }
}
What I am doing is I am passing the shop data I get from the server, send it into the form so I can view and/or update the shop info.
It's also that I want to create shop info using the same form. In this case I just send in shop = [] and action='create()' instead.
My controller has an update method that takes the argument isValid. I don't know how to pass the directive shopForm.$valid outside and send it to server.
Two questions:
how do I get isValid variable from the directive?
Following Ari Lerner's ng-book: He said it's possible to do the following:
http://www.scribd.com/doc/215682987/NG-Book-The-Complete-Book-on-AngularJS-2013
instead of using directive above we use
<update-createform shop="shop" on-update='update()' on-create='create()'></update-createform>
and the directive 'action' will change to 'update' when shop is not empty otherwise action equals to 'create'? I tried his code but I cannot get it to work..
Any help would be greatly appreciated!
You can add an argument to action=update(isValid). This then gets resolved on the form submit.
So your html would look like this
<div ng-controller='ShopsController as shopCtrl'>
<update-createform shop="shop" action='shopCtrl.update(isValid)'></update-createform>
</div>
And your form would look like like this
<form name="shopForm" ng-submit='action({isValid:shopForm.$valid})' novalidate>
<input type='text' name='name' required/>
<input type='text' name='description' required/>
<button type="submit">Submit</button>
</form>
and controller would be
.controller('ShopsController', function() {
var exports = this;
exports.update = function(isValid) {
console.log(isValid)
if (isValid) { /* update the shop*/ }
}
})
http://plnkr.co/edit/Qh3HzKGnOo1NTP9Pfsmh?p=preview
OR
There's another way, although personally i find the syntax a little odd. Not that the first solution feels that intuitive either.
http://plnkr.co/edit/CRN9ruRekJiozJIBTe80?p=preview
Found that one in an excellent post about directives by Dan Wahlin
http://weblogs.asp.net/dwahlin/creating-custom-angularjs-directives-part-3-isolate-scope-and-function-parameters

Form Validation error while using angularjs

I'm trying to add a simple validation for my input text box using angularjs and trying to show an inline error message. I have tried a lot, but still I'm not able to get the $valid, $ error, $invalid etc. These attributes are getting as undefined. I have given the reference to angularjs.js file. All other functionalities are working, but don't know what's happening. I'm new to angularjs, hope someone will help me in this regard. My code snippet is given below.
<sp-modal-dialog show='modalShown'>
<label for="title">Title <span>*</span></label>
<input type="text" name="txtTitle" ng-model="Model.Title" ng-maxlength="20" required>
<div ng-show="popupForm.txtTitle.$invalid">Invalid:
<span ng-show="popupForm.txtTitle.$error.required">Title is mandatory.</span>
<span ng-show="popupForm.txtTitle.$error.maxlength">Title should contain atleast 20 characters.</span>
</div>
EDIT:
I'm using a directive to show the popup form. Please see the code below.
//Directive to link the modalDialog as Element
contextualHelpModule.directive('spModalDialog', function () {
return {
restrict: 'E',
scope: {
show: '='
},
replace: true,
transclude: true,
link: function (scope, element, attrs) {
scope.hideModal = function () {
scope.show = false;
};
},
template: '<form id="popupForm" name="popupForm" novalidate><div ng-show="show"><div class="popup-main" ng-show="show"><div class="popup-container"><div class="popup-content" ng-transclude><div ng-click="hideModal()" class="popup-close">X</div></div></div></div><div class="popup-overlay"></div></div></form>'
};
you need to wrap that into a form:
<form name="popupForm">
<label for="title">Title <span>*</span></label>
<input type="text" name="txtTitle" ng-model="Model.Title" ng-maxlength="20" required>
<div ng-show="popupForm.txtTitle.$invalid">Invalid:
<span ng-show="popupForm.txtTitle.$error.required">Title is mandatory.</span>
<span ng-show="popupForm.txtTitle.$error.maxlength">Title should contain atleast 20 characters.</span>
</div>
</form>
I hope that it helps.
Yes, as jvrdelafuente explains, you will need to wrap it in a
<form name="popupForm">
tag. Once you do that, then you will have access to the properties via popupForm.txtTitle.$error.required, etc. Otherwise, they are not defined as you have observed. Unfortunately, I have recently learned that if you are placing your form tag in an aspx page using a SharePoint master page, the master page places its own form tag without a name attribute. ASP.NET will then strip your form out because it does not allow a form within a form. I am still searching for the rest of the story.
Update....
To be able to get past the issue with ASP.NET stripping out the AngularJS form tag, use an ng-form tag instead:
<ng-form name="popupForm">
<label for="title">Title <span>*</span></label>
<input type="text" name="txtTitle" ng-model="Model.Title" ng-maxlength="20" required>
<div ng-show="popupForm.txtTitle.$invalid">Invalid:
<span ng-show="popupForm.txtTitle.$error.required">Title is mandatory.</span>
<span ng-show="popupForm.txtTitle.$error.maxlength">Title should contain atleast 20 characters.</span>
</div>
</form>
Use ng-form at place of form, it will work fine, as see code in below
<ng-form name="popupForm">
<label for="title">Title <span>*</span></label>
<input type="text" name="txtTitle" ng-model="Model.Title" ng-maxlength="20" required>
<div ng-show="popupForm.txtTitle.$invalid">Invalid:
<span ng-show="popupForm.txtTitle.$error.required">Title is mandatory.</span>
<span ng-show="popupForm.txtTitle.$error.maxlength">Title should contain atleast 20 characters.</span>
</div>
</ng-form>

AngularJS: Fields added dynamically are not registered on FormController

I have the following static form in AngularJS:
<form name="myForm" class="form-horizontal">
<label>First Name:</label>
<input type="text" name="first_name" ng-model="entity.first_name">
<label>Last Name:</label>
<input type="text" name="last_name" ng-model="entity.last_name">
</form>
Angular creates a FormController for me and publishes it into the scope (under the form name). Which means I have access to properties like the following:
$scope.myForm.first_name.$error
$scope.myForm.last_name.$invalid
...
This is super useful!
But in my case I'm building a form dynamically, using directives:
<form name="myForm" class="form-horizontal">
<field which="first_name"></field>
<field which="last_name"></field>
</form>
The <field> directives don't resolve to actual <input> elements until after a while (after I've fetched some data from the server, linked the directives, etc.).
The problem here is that no field properties are defined on the form controller, as if dynamic fields didn't register with the FormController:
// The following properties are UNDEFINED (but $scope.myForm exists)
$scope.myForm.first_name
$scope.myForm.last_name
Any idea why? Any solution/workaround?
You can see the entire code in this jsFiddle:
http://jsfiddle.net/vincedo/3wcYV/
Update 7/31/2015 This has been fixed since 1.3, see here: https://github.com/angular/angular.js/issues/1404#issuecomment-125805732
Original Answer
This is unfortunately a short coming of AngularJS at the moment. Angular's form validation doesn't work with dynamically named fields. You can add the following at the bottom of your HTML to see exactly what's going on:
<pre>{{myForm|json}}</pre>
As you can see, Angular isn't getting the dynamic input name right. There's currently a work around involving nested forms that can get kind of nasty, but it does work and (with a little extra work) will submit the parent form without trouble.
If you want, you can go drum up more support for the issue: GitHub Issue - dynamic element validation. Either way, here's the code:
http://jsfiddle.net/langdonx/6H8Xx/2/
HTML:
<div data-ng-app>
<div data-ng-controller="MyController">
<form id="my_form" name="my_form" action="/echo/jsonp/" method="get">
<div data-ng-repeat="field in form.data.fields">
<ng-form name="form">
<label for="{{ field.name }}">{{ field.label }}:</label>
<input type="text" id="{{ field.name }}" name="{{field.name}}" data-ng-model="field.data" required>
<div class="validation_error" data-ng-show="form['\{\{field.name\}\}'].$error.required">Can't be empty.</div>
</ng-form>
</div>
<input type="submit" />
</form>
</div>
</div>
JavaScript:
MyController.$inject = ["$scope"];
function MyController($scope) {
$scope.form = {};
$scope.form.data = {};
$scope.form.data.fields = []
var f1 = {
"name": "input_1",
"label": "My Label 1",
"data": ""
};
var f2 = {
"name": "input_2",
"label": "My Label 2",
"data": ""
};
$scope.form.data.fields.push(f1);
$scope.form.data.fields.push(f2);
}
I ran into a similar problem myself and what i did to work around it was to place the name of the field before calling $compile on the template. A simple string.replace did the trick. Then again that was only possible because i was getting the field templates in through http and had access to the template text.
update: here is a fiddle with a little hack to make your example work
app.directive('field', function($compile) {
var linker= function(scope, element){
var template = '<input type="text" name="{{fname}}" ng-model="model">'
.replace('{{fname}}', scope.fname);
element.html(template)
$compile(element.contents())(scope)
}
return {
restrict: 'E',
scope: {
fname: '=',
model: '='
},
replace: true,
link: linker
};
});
http://jsfiddle.net/2Ljgfsg9/4/

Resources