Is it possible to have ng-messages + ng-message display a default error message?
For instance:
<form name="myForm">
<input type="number" name="number" min="10" max="100" required ng-model="number" />
</form>
<div ng-messages="myForm.number.$error">
<div ng-message="required">Number is required</div>
<div ng-message>There's something wrong with number!</div>
</div>
I would like "There's something wrong with number!" to show up if there's any error besides required.
Is that possible?
<form name="myForm">
<input type="number" name="number" min="10" max="100" required ng-model="number" />
</form>
<div ng-show="myForm.number.$error">
<div ng-show="myForm.number.$error.required">Number is required</div>
<div ng-show="!myForm.number.$error.required && myForm.number.$error" >There's something wrong with number!</div>
</div>
Plnkr Demo:
http://plnkr.co/edit/aIJ1W8qTGiKHLJYJg0fe?p=preview
I wrote a directive that accomplishes a lot of the boiler plate stuff for validations. It includes allowing for a default message:
angular.module('validations').directive('showErrors', function() {
return {
restrict: 'A',
require: '^form',
link: function(scope, el, attrs, ngModelCtrl) {
var data = el.data();
var inputName = el.attr('name');
if (!inputName)
return;
var baseFormName = ngModelCtrl.$name;
var baseFieldName = baseFormName + '.' + inputName + '.$error.';
var validationMessages = '';
var validators = {};
var defaultValidationConditions = [];
var defaultValidationMessage = '';
angular.forEach(data, function (value, key) {
if (key.indexOf('se') === 0 && key[2] === key[2].toUpperCase()) {
var validationProperty = key.substring(2).toLowerCase();
// if this is the default message, just grab it and keep looping.
if (validationProperty === 'default') {
defaultValidationMessage = value;
return;
}
var condition = baseFieldName + validationProperty;
if (!validators[value]) {
validators[value] = [];
}
validators[value].push(condition);
// if we have a message for this error, we want to exclude it from the default
defaultValidationConditions.push('!' + condition);
}
});
defaultValidationConditions.push([baseFormName, '.', inputName, '.$invalid'].join(''));
angular.forEach(validators, function (conditions, message) {
var conditionString = conditions.join(' || ');
validationMessages += '<label ng-show="' + conditionString + '" class="error">' + message + '</label>';
});
// if a default validation message was specified, we need to create a label
if (defaultValidationMessage) {
validationMessages += '<label ng-show="' + defaultValidationConditions.join(' && ') + '" class="error">' + defaultValidationMessage + '</label>';
}
if (validationMessages) {
el.after(this.$compile('<div ng-show="' + baseFormName + '.' + inputName + '.$dirty">' + validationMessages + '</div>')(scope));
}
}
}
});
It's pretty simple to use:
<form name="myForm">
<input show-errors data-se-required="Field required"
data-se-default="Field Invalid"
name="myInput" ng-minlength="10" required />
</form>
The directive will automatically generate all of the html. It uses a div and labels, but can be adapted to use ngMessages if you want. JSFiddle is being a bit wonky at the moment. I'll try and add a working link tomorrow.
ngMessageExp is what you are after. This will show the 'other' error message when there is an error, but not the 'required' error.
<ng-messages for="myForm.number.$error">
<ng-message when="required">Number is required</ng-message>
<ng-message when-exp="['min', 'max']">There's something wrong with number!</ng-message>
</ng-messages>
You will have to add each error type to the list, but you should know what validations you are using. In this case, only min and max.
If you find yourself repeating that generic catch all error message then consider using ngMessageInclude that has the when-exp for all validation types that you use.
Related
I have an array of vertex values that I loop over and display to the user.
The values are stored as a space delimited string, like this:
vrtx = ["10.3 20.3 10.5", "13.2 29.2 3.0", "16.3 2.3 20.2", ...]
I want to provide a friendly interface to the user to edit the values.
This requires me to split the strings into three separate numbers and put them into three individual inputs.
I want to create a directive, as I am not able to store the values as separate values, but after the editing is done, I want to join the values back and update the scope to store the new values as strings.
This is the directive I have so far:
myApp.directive('fieldValue', function () {
return {
scope: {
field:'='
},
link: function (scope, element, attrs) {
var fields = scope.field.split(' ');
element.html("<input type='number' value='"+ fields[0] +"' class='vertex-input'/><input type='number' value='"+ fields[1] +"' class='vertex-input'/><input type='number' value='"+ fields[2] +"' class='vertex-input'/>");
}
}
});
This splits the value and shows the three inputs with the individual values, but my question is how do I join these values and save them back to the scope? Any suggestions would be greatly appreciated! TIA
You could use formatters/parsers to achieve what you want, but you may have to adjust your input to use ngModel, and I'm not quite sure how it would work with 3 separate input fields:
myApp.directive('fieldValue', function () {
return {
scope: {
field:'='
},
require: 'ngModel',
link: function (scope, element, attrs, ngModel) {
var fields = scope.field.split(' ');
element.html("<input type='number' value='"+ fields[0] +"' class='vertex-input'/><input type='number' value='"+ fields[1] +"' class='vertex-input'/><input type='number' value='"+ fields[2] +"' class='vertex-input'/>");
ngModel.$formatters.push(function(value){
//return the value as you want it displayed
});
ngModel.$parsers.push(function(value){
//return the value as you want it stored
});
}
}
});
This way gives you a lot more flexibility in my opinion to use any strategy you want. Read more about it here: https://alexperry.io/angularjs/2014/12/10/parsers-and-formatters-angular.html
And, more official docs here: https://docs.angularjs.org/api/ng/type/ngModel.NgModelController
I think that your question is the continuation of this one, so I would like to suggest you a solution without a directive, based on the answer of your precedent question.
When the value of an input is modified, call a function that merged all your inputs:
<input type="text" ng-model="arr[0]" ng-change="update()" />
<input type="text" ng-model="arr[1]" ng-change="update()" />
<input type="text" ng-model="arr[2]" ng-change="update()" />
$scope.update = function() {
$scope.myString = $scope.arr[0] + ' ' + $scope.arr[1] + ' ' + $scope.arr[2];
}
var myApp = angular.module('myApp', []);
myApp.controller('MyCtrl', ['$scope', function($scope) {
$scope.myString = 'My awesome text';
$scope.arr = $scope.myString.split(/[ ]+/);
$scope.update = function() {
$scope.myString = $scope.arr[0] + ' ' + $scope.arr[1] + ' ' + $scope.arr[2];
}
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="MyCtrl">
<input type="text" ng-model="arr[0]" ng-change="update()" />
<input type="text" ng-model="arr[1]" ng-change="update()" />
<input type="text" ng-model="arr[2]" ng-change="update()" />
<br/>Result: {{myString}}
</div>
</div>
Try it on JSFiddle.
I've been trying to use ngTagsInput with its Autocomplete feature (mbenford.github.io/ngTagsInput/demos) in a html form to submit recipes in Mongodb.
Basically, I'm using ngTagsInput with Autocomplete to query my ingredients db and display ingredients tags in the 'Ingredients in the recipe'.
It works fine, up until I save the recipe, and the ingredients are not saved.
I know where the problem is but I haven't found a solution yet.
Here is the 'ingredients' field of my add recipe page without ngTagsInput, just a normal text field:
<div class="form-group">
<label for="ingredients">List of ingredients</label>
<input type="text" class="form-control" id="ingredients" ng-model="form.ingredients">
</div>
And here is the 'ingredients' field using ngTagsInput (Working fine, but not saving):
<div class="form-group" ng-controller="recipeApiController">
<tags-input for="Ingredients" id="Ingredients" ng-model="tags" display-property="ingredientID" placeholder="Commence à taper le nom d'un ingrédient..." add-from-autocomplete-only="true">
<auto-complete source="loadTags($query)"></auto-complete>
</tags-input>
</div>
Because I'm replacing ng-model="form.ingredients" with ng-model="tags" required to use ngTagsInput, those ingredient tags are not saved when clicking my "Add recipe" button.
Here is the "save to db" part of my recipeApiController, used on the "add recipe" form page:
$scope.addToDatabase = function(){
RecipeApi.Recipe.save({}, $scope.form,
function(data){
$scope.recipe.push(data);
},
function(err){
bootbox.alert('Error: ' + err);
});
}
Do you have any idea how I could fix that, and save those tags?
Thanks in advance guys. I didn't want this post to be too long but if you need more info, code, I'll be super reactive to provide it. This would help me greatly.
I found a solution in another post:
https://stackoverflow.com/a/38383917/6858949
Basically, I couldn't get those tags to save because ng-model didn't work inside the <tags-input> tags. I therefore used this guy's directive to change the <tags-input> to an attribute:
<div elem-as-attr="tags-input"></div>
Here's the directive code:
app.directive('elemAsAttr', function($compile) {
return {
restrict: 'A',
require: '?ngModel',
replace: true,
scope: true,
compile: function(tElement, tAttrs) {
return function($scope) {
var attrs = tElement[0].attributes;
var attrsText = '';
for (var i=0; i < attrs.length; i++) {
var attr = attrs.item(i);
if (attr.nodeName === "elem-as-attr") {
continue;
}
attrsText += " " + attr.nodeName + "='" + attr.nodeValue + "'";
}
var hasModel = $(tElement)[0].hasAttribute("ng-model");
var innerHtml = $(tElement)[0].innerHTML;
var html = '<' + tAttrs.elemAsAttr + attrsText + '>' + innerHtml + '</' + tAttrs.elemAsAttr + '>';
var e = hasModel ? $compile(html)($scope) : html;
$(tElement).replaceWith(e);
};
}
}
});
I don't think this is optimal, but with my current knowledge of code, I'm thankful I found this solution. ✌🏼
EDIT:
I am now using ui-select: https://github.com/angular-ui/ui-select
And definitely recommend it
EDIT:
I put the code in the code box
I'm trying to create a custom directive to help me creating bootstrap forms and dealing with form validation using angular.
I've come up so far with the following directive:
directive('formGroup', ['$compile',function ($compile) {
var linker = function (scope, element, attrs) {
var fieldName = attrs.formGroup;
var formName = attrs.form;
if (formName === undefined || formName === null) {
//find form name
formName = $(element).closest('form').attr('name');
if (formName === undefined || formName === null) {
console.error('Could not find form name for error ' + fieldName);
}
}
var showErrorOn = formName + '.' + fieldName + '.$invalid';
$(element).addClass('form-group');
$(element).attr('ng-class', '{ \'has-error\' : ' + showErrorOn + ' }');
$(element).removeAttr('form-group');
$compile(element)(scope);
}
return {
restrict: 'A',
link: linker
}}]);
To be used with the following HTML:
<form name="testForm" novalidate>
<div class="row">
<div class="col-md-6" form-group="email">
<label>Email</label>
<input type="email" class="form-control" name="email" ng-model="email" required />
</div>
</div>
</form>
The point of the directive is a couple of things:
Finding the form name so I don't have to type it again for every field.
Add the class "form-group".
Add the class "has-error" whenever the field is invalid.
Everything seems to work just fine until I type a valid e-mail address and add a dot to the end (making the input invalid). After I do that, the input automatically clears out but I can't understand why.
Here's a fiddle: http://jsfiddle.net/kgwocdyu/2/
To reproduce type: "a#email" then type "." and the input will go blank.
my problem is that after change input value by code or any plugin new value not submitted to controller and old value of property is accessible.
but if change input value by typing new value is available! only by typing!
template :
<input class="form-control" id="ng-taskLineBackColor"
type="text" ng-model="data.preference.lineBackColor"/>
<input type="submit" ng-click="update()" class="btn btn-primary" value="save"/>
controller :
.controller('taskCtrl', ['$scope', function ($scope) {
$scope.getRef = function () {
return //any code
};
$scope.save = function () {
var newValue = $scope.data.preference.lineBackColor;
//!!!-->newValue Contain old Value
};
}])
Any code which changes the value of ng-taskLineBackColor needs to trigger a special event called "input". This will notify AngularJS
$(function() {
$('#ng-taskLineBackColor').val('new value').trigger('input');
});
To do this only with jQlite and without jQuery try:
angular.element(document.querySelector('#ng-taskLineBackColor')).triggerHandler('input')
And here's the API you have available on an angular.element-wrapped HTML element:
https://docs.angularjs.org/api/ng/function/angular.element
I create working JSfiddle for your case.
JSfiddle
<input class="form-control" id="ng-taskLineBackColor"
type="text" ng-model="data.preference.lineBackColor"/>
<input type="submit" ng-click="update()" class="btn btn-primary" value="save"/>
I renamed function "save", declared in controller on "update".
UPDATE:
function MyCtrl($scope) {
$scope.update = function () {
var value = $scope.data.preference.lineBackColor;
alert("Typing value = '" + value + "'");
$scope.data.preference.lineBackColor = "Value from code";
var newValue = $scope.data.preference.lineBackColor;
alert("Typing value = '" + newValue + "'");
};
}
I have a list of inputs, created by:
<div ng-controller="MainCtrl">
<div ng-repeat="variable in variables">
<label>{{ variable.slug }}</label>
<input type="text" ng-model="variable.value" ng-change="variableChange()" />
</div>
</div>
And a controller:
function MainCtrl($scope) {
$scope.variables = [
{'slug':'background', 'value':'#666'},
{'slug':'foreground', 'value':'#999'}
]
}
I'm using the less.js to compile the less in the browser, and I want to be able to re-compile it when the variable changes - something like inside the controller:
$scope.variableChange = function() {
less.modifyVars({ variable.slug : variable.value });
};
But I get the error:
ParseError: Unrecognised input in preview-style.less on line 102, column 1:
102#variable.slug: variable.value;
But if I remove the apostrophes for the variables, I get an angular error:
Bad Argument: Argument 'MainCtrl' is not a function, got undefined
Can anyone help with this?
Edit: here's the less.modifyVars() function if it helps:
less.modifyVars = function (a) {
var b = "";
for (var c in a) b += ("#" === c.slice(0, 1) ? "" : "#") + c + ": " + (";" === a[c].slice(-1) ? a[c] : a[c] + ";");
less.refresh(!1, b)
}
If you are writing inside the controller, you must address a scoped property with $scope:
$scope.variableChange = function() {
less.modifyVars({ $scope.variable.slug : $scope.variable.value });
};
But that will not get this example to work because of the ng-repeat.
It would be better to pass the object to the function:
<div ng-controller="MainCtrl">
<div ng-repeat="variable in variables">
<label>{{ variable.slug }}</label>
<input type="text" ng-model="variable.value" ng-change="variableChange(variable)" />
</div>
</div>
And then call less like this:
$scope.variableChange = function(selectedVariable) {
less.modifyVars({ selectedVariable.slug : selectedVariable.value });
};