Use ngMessages with Angular 1.2 - angularjs

Does anyone know if there is a fork of Angular 1.2 that supports ngMessages?
I'd love to use this but I have a requirement for IE8.
Thanks in advance for your help.

Here is my directive I use:
/**
* Ui-messages is similar implementation of ng-messages from angular 1.3
*
* #author Umed Khudoiberdiev <info#zar.tj>
*/
angular.module('uiMessages', []).directive('uiMessages', function () {
return {
restrict: 'EA',
link: function (scope, element, attrs) {
// hide all message elements
var messageElements = element[0].querySelectorAll('[ui-message]');
angular.forEach(messageElements, function(message) {
message.style.display = 'none';
});
// watch when messages object change - change display state of the elements
scope.$watchCollection(attrs.uiMessages, function(messages) {
var oneElementAlreadyShowed = false;
angular.forEach(messageElements, function(message) {
var uiMessage = angular.element(message).attr('ui-message');
if (!oneElementAlreadyShowed && messages[uiMessage] && messages[uiMessage] === true) {
message.style.display = 'block';
oneElementAlreadyShowed = true;
} else {
message.style.display = 'none';
}
});
});
}
};
});
I've used ui-messages instead of ng-messages to avoid conflicts.
<div ui-messages="form.name.$error">
<div ui-message="minlength">too short</div>
<div ui-message="required">this is required</div>
<div ui-message="pattern">pattern dismatch</div>
</div>

I don't know for sure if a fork exists but it would be easy enough to roll your own ng-message (or something that serves the same purpose). I think the following would do it:
Controller
app.controller("Test", function ($scope) {
$scope.messages = {
"key1": "Message1",
"key2": "Message2",
"key3": "Message3"};
$scope.getMessage = function (keyVariable) {
return $scope.messages[keyVariable.toLowerCase()];
};
$scope.keyVariable = 'key1';
});
HTML (example)
ENTER A KEY: <input type="text" ng-model="keyVariable" />
<h1 ng-bind="getMessage(keyVariable)" ng-show="getMessage(keyVariable) != ''"></h1>
See It Working (Plunker)

I've updated pleerock's answer to handle element directives having for and when attributes like ngMessages and ngMessage. You can find the same in this github repo
angular.module('uiMessages', []).directive('uiMessages', function() {
return {
restrict: 'EA',
link: function(scope, element, attrs) {
// hide all message elements
var messageElements = element.find('ui-message,[ui-message]').css('display', 'none');
// watch when messages object change - change display state of the elements
scope.$watchCollection(attrs.uiMessages || attrs['for'], function(messages) {
var oneElementAlreadyShowed = false;
angular.forEach(messageElements, function(messageElement) {
messageElement = angular.element(messageElement);
var message = messageElement.attr('ui-message') || messageElement.attr('when');
if (!oneElementAlreadyShowed && messages[message] && messages[message] === true) {
messageElement.css('display', 'block');
oneElementAlreadyShowed = true;
} else {
messageElement.css('display', 'none');
}
});
});
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js"></script>
<form name="userForm" ng-app="uiMessages" novalidate>
<input type="text" name="firstname" ng-model="user.firstname" required />
<ui-messages for="userForm.firstname.$error" ng-show="userForm.firstname.$dirty">
<ui-message when="required">This field is mandatory</ui-message>
</ui-messages>
<br />
<input type="text" name="lastname" ng-model="user.lastname" required />
<div ui-messages="userForm.lastname.$error" ng-show="userForm.lastname.$dirty">
<div ui-message="required">This field is mandatory</div>
</div>
</form>

Related

How do I integrate Stripe credit card field with `ng-disabled` attribute of a `button`?

I have a form in my page with AngularJS and Stripe JS.
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"> <script type="text/javascript">
var app= angular.module("app",[]);
app.config(function($interpolateProvider){
$interpolateProvider.startSymbol("[[[");
$interpolateProvider.endSymbol("]]]");
});
app.controller("Ctrl",function(stripeService, $scope, $rootScope){
$scope.name= "World";
$scope.stripeCompleted= false;
stripeService.start();
$rootScope.on("stripedone", function(e,stripeEvent){
$scope.stripeCompleted= stripeEvent.complete;
$scope.$apply();
});
});
app.service("stripeService", function($window,$rootScope){
function start(){
var btn= document.querySelectorAll("#test")[0];
var displayError= document.getElementById('card-errors');
var stripe= Stripe("{{ stripe_key }}");
var elements= stripe.elements();
var style= {
base: {
fontSize: "1.1875rem",
fontSmoothing: "always",
fontWeight: "600"
}
};
var card= elements.create("card", {style:style});
card.mount("#card-element");
card.addEventListener('change', function(event) {
if (event.error) {
displayError.textContent = event.error.message;
} else {
displayError.textContent = '';
}
if (event.complete) {
$rootScope.$broadcast("stripedone",event);
} else {
$rootScope.$broadcast("stripedone",event);
}
});
var formID= "register-form";
var form= document.getElementById(formID);
form.addEventListener("submit",function(event){
event.preventDefault();
stripe.createToken(card).then(function(result){
if(result.error) {
displayError.textContent= result.error.message;
} else {
stripeTokenHandler(result.token, formID);
}
});
});
return {"start":start};
}
});
// tut https://stripe.com/docs/stripe-js/elements/quickstart#create-form
function stripeTokenHandler(token, formID) {
// Insert the token ID into the form so it gets submitted to the server
var form = document.getElementById(formID);
var hiddenInput = document.createElement('input');
hiddenInput.setAttribute('type', 'hidden');
hiddenInput.setAttribute('name', 'stripeToken');
hiddenInput.setAttribute('value', token.id);
form.appendChild(hiddenInput);
// Submit the form
form.submit();
}
</script> <form id="register-form" name="regForm" method="post>
<input ng-model="reg.email" type="email" name="username">
<div id="stripe-wrapper">
<div id="card-element"></div>
</div>
<small id="card-errors" class="text-danger" role="alert">{{ ccErrMsg }}</small>
<br>
<button type="submit" ng-model="reg.btn" ng-disabled="!regForm.username.$valid>Register</button>
</form>
I want for my button to be un-clickable unless the user fills out the Stipe credit card section correctly. How do I make it so my button is disabled unless the the Stripe credit card fields are filled out correctly?
Update: Following karthick's answer gives me a new error:
angular.js:13642 TypeError: stripeService.start is not a function
at Object.<anonymous> ((index):135)
at Object.invoke (angular.js:4708)
at P.instance (angular.js:10177)
at n (angular.js:9096)
at g (angular.js:8459)
at angular.js:8339
at angular.js:1782
at m.$eval (angular.js:17378)
at m.$apply (angular.js:17478)
at angular.js:1780
You should use a AngularJS directive in this case. This is nothing to put in a controller, service, factory or component. Once you use a directive your code will be much smarter and become fully supported by AngularJS diggest cycles and DOM bindings. This is how the documentation introduces directives:
What are Directives? At a high level, directives are markers on a DOM element (such as an attribute, element name, comment or CSS class) that tell AngularJS's HTML compiler ($compile) to attach a specified behavior to that DOM element (e.g. via event listeners), or even to transform the DOM element and its children.
> Demo fiddle
Your solution could be smart like this one by using a nice directive:
View
<body ng-app="angularjs-starter">
<script src="https://js.stripe.com/v3/"></script>
<div ng-controller="MainCtrl">
<form name="regForm" id="register-form">
<label>Mail</label>
<input ng-model="reg.email" type="email" name="username">
<div stripe-validator
stripe-complete="stripeCompleted"
stripe-form-id="register-form"></div>
<br>
<button ng-model="reg.btn" ng-disabled="stripeCompleted === false || !regForm.username.$valid">Register</button>
</form>
</div>
</body>
AngularJS Application / Stripe.js card validation directive
var app = angular.module('angularjs-starter', []);
app.controller('MainCtrl', function($scope, $rootScope) {
//Init stripe state via controller
$scope.stripeCompleted = false;
});
app.directive('stripeValidator', function() {
return {
restrict: 'A',
template: `
<div id="stripe-wrapper">
<div id="card-element"></div>
</div>
<small id="card-errors" class="text-danger" role="alert">{{ ccErrMsg }}</small>
<input type="hidden" name="stripeToken" ng-value="stripeToken" />`,
scope: {
"stripeComplete": '=',
"stripeFormId": '#',
"stripeError": '=',
"stripeToken": '=',
},
link: function(scope, element, attrs) {
//Init
var stripe = Stripe("pk_test_6pRNASCoBOKtIshFeQd4XMUh");
var elements = stripe.elements();
var card = elements.create("card");
var form = document.getElementById(scope.stripeFormId);
//mount card element https://stripe.com/docs/stripe-js/reference#element-mount
card.mount("#card-element");
//add event listener
card.addEventListener('change', function(event) {
//check for errors
if (event.error) {
scope.ccErrMsg = event.error.message;
} else {
scope.ccErrMsg = '';
}
//check for complete
scope.stripeComplete = event.complete ? true : false;
//apply scope
scope.$apply();
});
//inject form submit event
form.addEventListener("submit", function(event) {
//prevent form submit
event.preventDefault();
//handle token, handle error and form submit forward
stripe.createToken(card).then(function(result) {
if (result.error) {
scope.ccErrMsg = event.error.message;
scope.stripeToken = '';
} else {
scope.ccErrMsg = '';
scope.stripeToken = result.token;
}
//apply scope
scope.$apply();
//forward submit
form.submit();
})
});
}
}
});

Passing Information to Directive to Match Passwords

I'm trying to add an errors to my floating placeholder labels when certain conditions are met in my controller
However, I'm not sure the best way to go about this and my current implementing doesn't seem to be detecting the attribute change in the directive (custom-error stays set to "test").
Here's what I've got right now:
HTML:
<input type="password" float-placeholder
custom-error="test" placeholder="Confirm password"
required name="passwordSecond" id="passwordSecond"
ng-model="vs.PasswordSecond" />
Directive:
angular.module('myApp').directive('floatPlaceholder', function ($window) {
return {
restrict: 'A',
scope: {
customError: '#'
},
link: function (scope, element, attrs) {
element.after("<label class='floating-placeholder'>" + attrs.placeholder + "</label>");
var label = angular.element(ele.parent()[0].getElementsByClassName('floating-placeholder'));
element.on('blur', function() {
if (ele.val().length > 0) {
if (scope.customError) {
label.text(attrs.placeholder + ' - ' + scope.customError);
}
}
}
}
};
});
Controller:
angular.module('myApp').controller('SignupController', function factory() {
_this.confirmPassword = () => {
if(_this.PasswordFirst !== _this.PasswordSecond){
angular.element(signupForm.passwordSecond).attr('custom-error', _this.Error);
}
}
});
I'm using Angular 1.6
Validator Directive which Matches Passwords
To have a form match password inputs, create a custom directive that hooks into the ngModelController API ($validators):
app.directive("matchWith", function() {
return {
require: "ngModel",
link: postLink
};
function postLink(scope,elem,attrs,ngModel) {
ngModel.$validators.match = function(modelValue, viewValue) {
if (ngModel.$isEmpty(modelValue)) {
// consider empty models to be valid
return true;
}
var matchValue = scope.$eval(attrs.matchWith);
if (matchValue == modelValue) {
// it is valid
return true;
}
// it is invalid
return false;
};
}
})
For more information, see AngularJS Developer Guide - Forms - Modifying Built-in Validators
The DEMO
angular.module("app",[])
.directive("matchWith", function() {
return {
require: "ngModel",
link: postLink
};
function postLink(scope,elem,attrs,ngModel) {
ngModel.$validators.match = function(modelValue, viewValue) {
if (ngModel.$isEmpty(modelValue)) {
// consider empty models to be valid
return true;
}
var matchValue = scope.$eval(attrs.matchWith);
if (matchValue == modelValue) {
// it is valid
return true;
}
// it is invalid
return false;
};
}
})
<script src="//unpkg.com/angular/angular.js"></script>
<body ng-app="app">
<form name="form1">
<input type="password" name="password1" required
placeholder="Enter password"
ng-model="vm.password1" />
<br>
<input type="password" name="password2" required
placeholder="Confirm password"
ng-model="vm.password2"
match-with="vm.password1"
ng-model-options="{updateOn: 'blur'}" />
<br>
<p ng-show="form1.password2.$error.match">
Passwords don't match
</p>
<input type="submit" value="submit" />
</form>
</body>
Had a look at your code. Have you defined the scope variables in the SignUpController
_this.PasswordFirst and _this.PasswordSecond
Also this line in your controller
angular.element(signupForm.passwordSecond).attr('custom-error', _this.Error);
good suggestion would be to implement this in the directive as attributes can be accessed correctly in the directive
(I'm basing this on you saying 'custom-error stays set to "test"')
custom-error is looking for a variable of "test", not a string value of "test". Have you tried setting a variable test in your controller and updating that?

NgModel not found while create directive

Here is my directive fileUpload for validation of image extenstion. I am not getting what is missing here. (Missing Required Controller Controller 'ngModel', required by directive 'fileUpload', can't be found! ) Please if any one can look in to this.
I found so many reference to the same question but no luck . please dont mark this as repeat question.
App.directive('fileUpload', function () {
return {
scope: true, //create a new scope
require: 'ngModel',
link: function (scope, el, attrs,ctrl) {
ctrl.$validators.images = function(modelValue, viewValue) {
console.log(modelValue);
console.log(viewValue);
};
ngModel.$setValidity('extension', true);
el.bind('change', function (event) {
var files = event.target.files;
var validFormats = ['jpg', 'jpeg', 'png', 'gif'];
var validSize = 500000;
for (var i = 0;i<files.length;i++) {
var ext = files[i].name.split('.');
var size = files[i].size;
if (ext !== undefined) {
var isExist = validFormats.indexOf(ext[1].toLowerCase()) >= 0 ? 1 : 0;
if (isExist == true && validSize >= (size)) {
ngModel.$setValidity('extension', true);
} else {
ngModel.$setValidity('extension', false);
}
}
//emit event upward
scope.$emit("fileSelected", { file: files[i] });
}
});
}
};
});
Input :
<div class="form-group has-feedback" style="margin-left: 10px;">
<input name="file" type="file" ng-model="images" accept="image/*" multiple="true" file-upload />
<div style="color:grey; font-size: 12px;">
Extensions : jpg , jpeg , png , gif<br>
Size : 500KB
</div>
<div ng-messages="frmSupportPanelist.file.$error" ng-if="frmSupportPanelist.file.$touched">
<label id="message-required-error" class="validation-error-label" for="file" ng-message="extension">Please select valid Image </label>
</div>
</div>

error message with AsyncValidator is not visible

The required directive shows the red error message that works!
The uniqueschoolclassnumberValidator directive shows NOT the red error message!
From the server I always return exists => true, but I also tried it with false.
What do I wrong? The custom directive is triggered for sure!
Directive
'use strict';
angular.module('TGB').directive('uniqueschoolclassnumberValidator', function (schoolclassCodeService) {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ngModel) {
ngModel.$asyncValidators.unique = function (schoolclass) {
var schoolclassNumber = "0a";
var schoolyearId = 1;
return schoolclassCodeService.exists(schoolyearId, schoolclassNumber);
};
}
};
});
Service
this.exists = function (schoolyearId, schoolclassNumber) {
var path = 'api/schoolyears/' + schoolyearId + '/schoolclasses/' + schoolclassNumber;
return $http.get(path).then(function (response) {
if (response.data == true) {
$q.reject("schoolclass number has already been taken");
}
else {
return $q.resolve();
}
});
};
Html
<form name="myForm">
<div class="col-sm-8">
<input type="text" unique-schoolclasnumber-Validator name="myInput"
ng-model-options="{ updateOn: 'default blur', debounce: {'default': 300, 'blur': 0} }"
ng-model="schoolclassNumber" class="form-control"
required placeholder="Enter schoolclass">
</div>
<div ng-messages="myForm.myInput.$error" style="color:red" role="alert">
<div ng-message="required">You did not enter anything.</div>
<div ng-message="unique">That schoolclass number already exists.</div>
</div>
</form>
In your service's exists method there should be return keyword before $q.reject:
if (response.data == true) {
return $q.reject("schoolclass number has already been taken");
}
else {
return $q.resolve();
}
Directive should be named uniqueSchoolclassnumberValidator instead of uniqueschoolclassnumberValidator (AngularJS change dash-delimited format to camelCase).
There is also a misspell in html code, in word "class". It should be unique-schoolclassnumber-Validator instead of unique-schoolclasnumber-Validator.

ng-minlength in directive not populated when invalid

I'm creating an input directive that encapsulates a label, and some error messaging but I'm getting stuck with ng-minlength and ng-maxlength. The issue is that they don't populate the model value until it's a valid value, so my parent scope can display a value while my directive doesn't, and vice versa. View this plunk for an example of what I mean.
Is the only way around this to define my own minlength and maxlength validators? Is there some way configure this behaviour so the model is always populated? I want to use all the built in validators in my directive, so no doubt this will be an issue with all of them and I'd rather not redefine them all.
HTML:
<!DOCTYPE html>
<html>
<head>
<script data-require="angular.js#*" data-semver="1.3.0-beta.5" src="https://code.angularjs.org/1.3.0-beta.5/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body ng-app="app" ng-controller="Controller">
<label>Outer scope 1</label>
<input name="input1" type="text" ng-model="model1" ng-minlength="4"/>
<br/>
<label>Directive scope 1</label>
<input-dir ng-model="model1" ng-minlength="0"></input-dir>
<br/>
<br/>
<br/>
<label>Outer scope 2</label>
<input name="input3" type="text" ng-model="model2"/>
<br/>
<label>Directive scope 2</label>
<input-dir ng-model="model2" ng-minlength="4"></input-dir>
</body>
</html>
Javascript:
var app = angular.module('app', []);
app.controller('Controller', function($scope){
$scope.model1 = "Model1";
$scope.model2 = "Model2";
});
app.directive('inputDir', function(){
return {
restrict: 'E',
template: '<input type="text" ng-model="model" ng-minlength="{{ minlength }}" />',
scope: {
model: '=ngModel',
minlength: '=ngMinlength'
}
};
});
For anyone else with the same issue, I solved this by redefining all the validators, except now they return the value when invalid so the model is populated. Just include the valuedValidators module. These validators are available on the $error object as vvminlength etc.
vv-validators.js
(function () {
'use strict';
var validators = {};
validators.vvUrl = function(value){
var urlRegex = new RegExp(/[-a-zA-Z0-9#:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9#:%_\+.~#?&//=]*)?/gi);
return urlRegex.test(value);
}
validators.vvEmail = function(value){
var emailRegex = new RegExp(/^(([^<>()[\]\\.,;:\s#\"]+(\.[^<>()[\]\\.,;:\s#\"]+)*)|(\".+\"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/);
return emailRegex.test(value);
}
validators.vvMinlength = function(value, min){
var huh = value.length >= parseInt(min);
return min && value && huh;
}
validators.vvMaxlength = function(value, max){
return max && value && value.length <= parseInt(max);
}
validators.vvMin = function (value, min) {
return min && parseFloat(value) >= parseFloat(min);
}
validators.vvMax = function (value, max) {
return max && parseFloat(value) <= parseFloat(max);
}
validators.vvPattern = function (value, pattern) {
return pattern && new RegExp(pattern).test(value);
}
validators.vvFloat = function (value) {
var floatRegex = new RegExp(/^\-?\d+((\.|\,)\d+)?$/);
return floatRegex.test(value);
}
validators.vvInteger = function (value) {
var integerRegex = new RegExp(/^\-?\d+$/);
return integerRegex.test(value);
}
var app = angular.module('valuedValidators', []);
var names = Object.keys(validators);
names.forEach(function(name){
app.directive(name, function(){
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, controller) {
controller.$parsers.unshift(function (viewValue) {
var valid = true;
var attrValue = attrs[name];
var func = validators[name];
if (attrValue !== 'false') {
valid = func(viewValue, attrValue);
controller.$setValidity(name.toLowerCase(), valid);
}
return viewValue;
});
}
};
});
});
}());
There is an even easier solution: use two textareas.
The first serves as a draft, the second is the model where the verification is to take place.
<textarea name="message_body_draft" cols="47" rows="15" style="width: 100%;"
ng-model="message_body_draft"
ng-change="message_body=message_body_draft"></textarea>
<textarea name="message_body" style="width: 100%;" disabled ng-model="message_body"
ng-minlength="100" required></textarea>
When the user writes in the first one, it automatically will populates the right model (the second one).

Resources