I'm trying to build a characterCounter attribute directive for input fields. My thoughts are to require: 'ngModel' to get the length of the modelValue in ng-model and to pass a max-length in the scope of the directive.
<input ng-model="inputModel" max-character-counter max-length="10"/>
I have my directive most of the way there I'm just struggling with how I get the view to update. Any help with this is greatly appreciated.
angular.module('app').directive('maxCharacterCounter', [function(){
return {
restrict: 'A',
require: '?ngModel',
scope: {
maxLength: "="
},
link: function (scope, elem, attrs, ngModel) {
if (!ngModel) return;
console.log(ngModel);
ngModel.$render = function() {
console.log('render');
var el = angular.element(attrs.$$element);
el.after('<span class="input-group-addon">' + scope.charactersLeft + '</span>');
}
elem.on('blur keyup change', function() {
scope.$evalAsync(read);
});
read();
function read() {
scope.charactersLeft = ngModel.$modelValue.length == undefined ? scope.maxLength : scope.maxLength - ngModel.$modelValue.length;
console.log('Characters Left:', scope.charactersLeft);
console.log('View Value: ', ngModel.$viewValue);
console.log('Model Value: ', ngModel.$modelValue);
updateViewValue();
}
function updateViewValue() {
//How do I update the view for scope.charactersLeft
//ngModel.$viewValue(scope.charactersLeft);
console.log('scope: ', scope);
}
}
}}]);
Plunker: http://plnkr.co/edit/F0PzE6?p=preview
Final Solution:
angular.module('app').directive('maxCharacterCounter', ['$timeout', '$log', function($timeout, $log) {
return {
restrict: 'A',
require: '?ngModel',
scope: {
maxLength: "#"
},
link: function (scope, elem, attrs, ngModel) {
if (!ngModel) {
$log.warn('ngModel doesn\'t exist. There is no way to calculate characters left');
return;
}
elem.wrap('<div class="input-group"></div>');
elem.after('<span class="input-group-addon"></span>');
elem.on('blur keyup keydown change', function() {
scope.$eval(updateCharacterCount);
updateViewValue();
});
$timeout(function(){
scope.maxLength = scope.maxLength || 140;
scope.$eval(updateCharacterCount);
updateViewValue();
});
function updateCharacterCount() {
scope.charactersLeft = !ngModel.$viewValue ? scope.maxLength : scope.maxLength - ngModel.$viewValue.length;
}
function updateViewValue() {
var element = elem.next('span');
element.text(scope.charactersLeft);
element.toggleClass('redText', scope.charactersLeft <= 0 ? true : false);
}
}
}}]);
Try like below. add the second parameter of module.
angular.module('app', [])
.directive('maxCharacterCounter', function() {
return {
restrict: 'A',
require: '?ngModel',
scope: {
maxLength: "="
},
link: function(scope, elem, attrs, ngModel) {
if (!ngModel) return;
ngModel.$render = function() {
console.log('render');
var el = angular.element(attrs.$$element);
el.after('<span ng-bind="charactersLeft" class="input-group-addon">' + scope.charactersLeft + '</span>');
}
elem.on('blur keyup change', function() {
scope.$evalAsync(read);
});
read();
function read() {
scope.charactersLeft = ngModel.$modelValue.length == undefined ? scope.maxLength : scope.maxLength - ngModel.$modelValue.length;
updateViewValue();
}
function updateViewValue() {
angular.element(attrs.$$element).next('span').text(scope.charactersLeft);
}
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<body ng-app="app">
<input ng-model="inputModel" max-character-counter max-length="10"/>
</body>
In Angular you can literally bind to the length of your input model, like so:
{{inputModel.length}}
Unless I'm missing something, isnt this all you need?
Related
I'm using this directive to limit my input to 5 caracters for percentage
monApp.directive('awLimitLength', function () {
return {
restrict: "A",
require: 'ngModel',
link: function (scope, element, attrs, ngModel) {
attrs.$set("ngTrim", "false");
var limitLength = parseInt(attrs.awLimitLength, 10);// console.log(attrs);
scope.$watch(attrs.ngModel, function(newValue) {
if(ngModel.$viewValue.length>limitLength){
ngModel.$setViewValue( ngModel.$viewValue.substring(0, limitLength ) );
ngModel.$render();
}
});
}
};
})
and using it like this :
<input name="name" type="text" ng-model="nameVar" aw-limit-length="5"/>
BUt when the input is empty, firebug notice several errors like this :
Error: ngModel.$viewValue is undefined
Error: ngModel.$viewValue is null
How could i avoid theses errors ? Thank you.
I'd like the directive not to be started when an input is empty.
I've tried this : But it doesn't work :
monApp.directive('awLimitLength', function () {
return {
restrict: "A",
require: 'ngModel',
link: function (scope, element, attrs, ngModel) {
attrs.$set("ngTrim", "false");
var limitLength = parseInt(attrs.awLimitLength, 5);// console.log(attrs);
if(attrs.ngModel!=null){
scope.$watch(attrs.ngModel, function(newValue) {
if(ngModel.$viewValue.length>limitLength){
ngModel.$setViewValue( ngModel.$viewValue.substring(0, limitLength ) );
ngModel.$render();
}
});
}
}
};
})
Check ngModel.$viewValue exist before taking its length
scope.$watch(attrs.ngModel, function(newValue) {
if(ngModel.$viewValue && ngModel.$viewValue.length>limitLength){
ngModel.$setViewValue( ngModel.$viewValue.substring(0, limitLength ) );
ngModel.$render();
}
});
You have used
require: 'ngModel' in directive. so you can't use this directive without value in model variable.
Remove require: 'ngModel' & it will work.
In this plunk I have directive dir1 calling a method in directive dir2 as described here.
The problem is that the control object (scope.dir2Ctl) is empty in dir1 and I get TypeError: scope.dir2Ctl.call2 is not a function. Any ideas how to fix this?
HTML
<body ng-app="myModule" ng-controller="ctl">
<dir1 x1="1"></dir1>
</body>
Javascript
angular.module("myModule", [])
.controller('ctl', function($scope) {})
.directive('dir1', function ($timeout) {
return {
restrict: 'EA',
scope: {
x1: '='
},
template: '<p>x2 should be 2 = {{x2}} </p>' +
'<dir2 control="dir2Ctl"></dir2>',
link: function (scope, element, attrs) {
scope.dir2Ctl = {};
$timeout(function(){
console.log(scope.dir2Ctl)
scope.x2 = scope.dir2Ctl.call2();
},1000);
}
}
})
.directive('dir2', function () {
return {
restrict: 'EA',
scope: {
control: '='
},
template: '<p>some text in dir2</p>',
link: function (scope, element, attrs) {
scope.control = scope.control || {};
scope.control.call2 = function(){
return 2;
};
}
}
});
I have 2 directives: calculatorForm and calculatorAttribute. CalculatorForm is the parent directive, specifically a form which contains input tags which are calculatorAttribute directives.
I want the calculatorAttribute call calculatorForm function that changes a scope variable and trigger a watcher.
Here's my code:
angular
.module('calculator')
.directive('calculatorForm', ['CalculatorDataModel', 'CalculatorPriceModel',
function(CalculatorDataModel, CalculatorPriceModel) {
return {
restrict : 'A',
replace : true,
templateUrl : function(element, attrs) {
return attrs.templateUrl;
},
link : function(scope, element, attrs) {
scope.model = CalculatorDataModel;
scope.price = CalculatorPriceModel;
scope.model.initialize(calculator_data);
scope.updateSelectedSpecs = function(attribute_id, prod_attr_val_id) {
var selected_specs = JSON.parse(JSON.stringify(scope.model.selected_specs));
selected_specs[attribute_id] = prod_attr_val_id;
scope.model.selected_specs = selected_specs;
}
scope.$watch('model.selected_specs', function(selected_specs, previous_selected_specs) {
if (selected_specs != previous_selected_specs) {
scope.model.setCalculatorData();
scope.price.computePrice();
}
});
}
}
}
])
.directive('calculatorAttribute', [
function() {
return {
restrict : 'A',
template : "<input type='radio' name='attr{{attribute_id}}' ng-value='prod_attr_val_id'/>",
replace : true,
link : function(scope, element, attrs) {
scope.attribute_id = attrs.attributeId;
scope.prod_attr_val_id = attrs.prodAttrValId;
element.on('click', function() {
scope.$parent.updateSelectedSpecs(scope.attribute_id, scope.prod_attr_val_id);
});
}
}
}
]);
My problem is updateSelectedSpecs in the parent is called but watcher has never been triggered when I use element.on click in the child directive.
Please help everyone Thank you!!!
Okay, after wrestling with this for a bit, I managed to produce a working version of a slimmed-down example:
angular.module('myApp', [])
.directive('calculatorForm', function() {
return {
restrict: 'A',
replace: true,
transclude: true,
template: '<div ng-transclude></div>',
link: function(scope, element, attrs) {
scope.model = {};
scope.price = {};
scope.updateSelectedSpecs = function(attribute_id, prod_attr_val_id) {
scope.$apply(function() {
console.log('update selected specs');
var selected_specs = {};
selected_specs[attribute_id] = prod_attr_val_id;
scope.model.selected_specs = selected_specs;
});
}
scope.$watch('model.selected_specs', function(selected_specs, previous_selected_specs) {
console.log('new selected specs', selected_specs, previous_selected_specs);
if (selected_specs != previous_selected_specs) {
console.log("and they're different");
}
});
}
};
})
.directive('calculatorAttribute', function() {
return {
restrict: 'A',
template: "<input type='radio' name='attr{{attribute_id}}' ng-value='prod_attr_val_id'/>",
replace: true,
link: function(scope, element, attrs) {
scope.attribute_id = attrs.attributeId;
scope.prod_attr_val_id = attrs.prodAttrValId;
element.on('click', function() {
scope.$parent.updateSelectedSpecs(scope.attribute_id, scope.prod_attr_val_id);
});
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<form calculator-form ng-app="myApp">
<input calculator-attribute attribute-id=1 prod-attr-val-id=1>
</form>
Just look at the console to see it getting into the $watch. The problem seemed to be the fact that you didn't trigger a $digest cycle in your updateSelectedSpecs function. Usually a $timeout, $http call, or ngClick or other event would start the $digest cycle for you, but in this case you have to start it yourself using scope.$apply().
I have a directive that is designed to be assigned to a normal text input.
<input type="text" ng-model="fooModel" foo-input size="30"
placeholder="insert value"></input>
I have lots of validation functions for it like testing the precision of the numbers and I use a $parsers to control the value that is submitted.
myApp.directive('fooInput', function () {
return {
restrict: 'A',
require: 'ngModel',
controller: function ($scope, $element, $attrs) {
this.errorMessage = ""
},
link: function (scope, element, attrs, ctrl)
return ctrl.$parsers.push(function (inputValue) {
var originalVal = element.val();
if (!testForOverPrecision(numericVal)) {
//do something here to set the directive as invalid
}
if (originalVal != inputValue) {
ctrl.$setViewValue(res);
ctrl.$render();
}
});
I have 2 questions:
How do I get this to work with the isValid service and do I have to have a controller scope for the error message
Is it correct for me to push the $parser inside a return statement
I am using Angular 1.2x and I created a directive to determine if the text contains the # symbol.
.directive('noAt', function() {
return {
require: 'ngModel',
link: function(scope, elm, attrs, ctrl) {
ctrl.$parsers.unshift(function(viewValue) {
if (/#/.test(viewValue)) {
ctrl.$setValidity('noAt', false);
return undefined;
} else {
ctrl.$setValidity('noAt', true);
return viewValue;
}
});
}
};
})
I've got following script
// Code goes here
angular.module('default', [])
.directive('webAbc', function($log) {
return {
restrict: 'A',
controller: function($scope, $element, $attrs, $transclude) {
this.checkboxes = [];
this.updateLinkedCheckboxes = function(value) {
angular.forEach(this.checkboxes, function(checkbox, index) {
checkbox.setChecked(value);
});
};
}
};
})
.directive('webDef', function($log) {
return {
restrict: 'C',
require: '^webAbc',
link: function (scope, iElement, iAttrs, webAbc, transcludeFn) {
iElement.bind('change', function () {
webAbc.updateLinkedCheckboxes(iElement.prop('checked'));
scope.$apply();
});
}
};
})
.directive('webGhi', function($log) {
return {
restict: 'A',
require: '^webAbc',
link: function (scope, iElement, iAttrs, webAbc, transcludeFn) {
scope.setChecked = function(value) {
$log.log('This element ' + iAttrs.name + ' cheked: ' + (!value ? 'checked' : ''));
$log.log(value);
if (value)
{
iElement.attr('checked', 'checked');
}
else
{
iElement.remoteAttr('checked');
}
};
webAbc.checkboxes.push(scope);
}
};
});
it should select or deselect all checkboxes in table in marked column, but I can't make it work with following solution.
First of all it seems, that only last webGhi is visible due to print out in console. And even more, it seems, that I can't uncheck checkbox for some reason.
Link to an example: http://jsbin.com/difihabe/1/
Thank you.
Use an isolated scope in the webGhi directive or all four instances of it will push the same scope (the parent):
.directive('webGhi', function($log) {
return {
restict: 'A',
require: '^webAbc',
scope: {},
link: ...
Also instead of adding/removing the checked attribute either use:
jQuery's prop() function: iElement.prop('checked', value);
Directly setting the DOM element's checked property:
iElement[0].checked = value;
Demo: http://jsbin.com/tudotugi/2/