Word counter in angularjs - angularjs

I'm a newbie in angular so please bear with me. I have a character counter and word counter in my textarea. My problem is that everytime I press the space, it is also being counted by getWordCounter function. How can I fix this? Thank you in advance.
HTML:
<textarea id="notesContent" type="text" class="form-control" rows="10" ng-model="notesNode.text" ng-trim="false" maxlength="5000"></textarea>
<span class="wordCount">{{getWordCounter()}}</span>
<span style="float:right">{{getCharCounter()}} / 5000</span>
JS:
$scope.getCharCounter = function() {
return 5000 - notesNode.text.length;
}
$scope.getWordCounter = function() {
return $.trim(notesNode.text.split(' ').length);
}

It seems like you need to call 'trim' before calling split, like this:
$scope.getWordCounter = function() {
return notesNode.text.trim().split(' ').length;
}
If you want to support multiple spaces between words, use a regular expression instead:
$scope.getWordCounter = function() {
return notesNode.text.trim().split(/\s+/).length;
}
Filter implementation
You can also implement wordCounter as a filter, to make it reusable among different views:
myApp.filter('wordCounter', function () {
return function (value) {
if (value && (typeof value === 'string')) {
return value.trim().split(/\s+/).length;
} else {
return 0;
}
};
});
Then, in the view, use it like this:
<span class="wordCount">{{notesNode.text|wordCounter}</span>
See Example on JSFiddle

This is a more advanced answer for your problem, since it can be reusable as a directive:
var App = angular.module('app', []);
App.controller('Main', ['$scope', function($scope){
var notesNode = {
text: '',
counter: 0
};
this.notesNode = notesNode;
}]);
App.directive('counter', [function(){
return {
restrict: 'A',
scope: {
counter: '='
},
require: '?ngModel',
link: function(scope, el, attr, model) {
if (!model) { return; }
model.$viewChangeListeners.push(function(){
var count = model.$viewValue.split(/\b/g).filter(function(i){
return !/^\s+$/.test(i);
}).length;
scope.counter = count;
});
}
};
}]);
And the HTML
<body ng-app="app">
<div ng-controller="Main as main"></div>
<input type="text" ng-model="main.notesNode.text" class="county" counter="main.notesNode.counter">
<span ng-bind="main.notesNode.counter"></span>
</body>
See it in here http://plnkr.co/edit/9blLIiaMg0V3nbOG7SKo?p=preview
It creates a two way data binding to where the count should go, and update it automatically for you. No need for extra shovelling inside your scope and controllers code, plus you can reuse it in any other input.

Related

How to append a character to a number in an input field using AngularJS?

I have an input field of type text and bound to a property in a scope.
<input type=text= ng-model="vm.someProperty">
If someProperty has a value of 4.32, I want the input to display 4.32%. I don't want someProperty to equal 4.32%. The value stays the same. Only the display changes. When the page renders, the % character shows up after the value. When the user is changing the input value, only the number shows and when the input loses focus, the % shows again.
I know this can be done using a directive but I am not sure how. I am not proficient in AngularJS.
Use this
angular
.module("docsSimpleDirective", [])
.controller("Controller", [
"$scope",
function($scope) {
$scope.text = '';
}
])
.directive("percent", function() {
return {
restrict: "A",
transclude: true,
scope: {},
link: function(scope, element, attr) {
element.on("focusout", function() {
var str = element.val();
element.val(str + "%");
});
element.on("focus", function() {
var str = element.val();
element.val(str.substring(0, str.length - 1));
});
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="docsSimpleDirective">
<input type "text" id="someProperty" percent ng-model="text"> {{text}}
</div>
vm.percentSymbol = function (type) {
vm.someProperty = vm.replace(/\%/g, '') + '%';
};
<input type=text= ng-model="vm.someProperty" ng-blur = "vm.percentSymbol()">
In your html
<input ng-focus="onFocus(someProperty)" ng-blur="onBlur(someProperty)" ng-model="someProperty" />
in your controller
$scope.someProperty= '';
$scope.onFocus = function(someProperty) {
$scope.someProperty = (someProperty.length >0)? someProperty.split("%")[0] : '';
}
$scope.onBlur = function(someProperty) {
$scope.someProperty = (someProperty.length >0)? someProperty+'%' : '';
}
This working fine.. Hope this will help.

custom currency filter to the input field in angularjs

How to formatting the value in to indian currency type using angularjs.
for example, 454565 to 4,54,565.00
I have input field like this:
<input type="text" ng-model="item.cost />
function FormatMyNumber(yourNumber) {
// Limit to two decimal places
yourNumber = parseFloat(yourNumber).toFixed(2);
//Seperates the components of the number
var n = yourNumber.toString().split(".");
//Comma-fies the first part
n[0] = n[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
//Combines the two sections
return n.join(".");
}
FormatMyNumber(454565); //yields 454,565.00
You can create directive for it as follows
HTML
<div ng-app="myApp">
<div ng-controller="myCtrl">
{{amount}}
<input format-to-currency amount="amount">
</div>
</div>
JS
angular.module('myApp', [])
.controller('myCtrl', function($scope) {
$scope.ampunt = 2;
})
.directive('formatToCurrency', function($filter) {
return {
scope: {
amount: '='
},
link: function(scope, el, attrs) {
el.val($filter('currency')(scope.amount));
el.bind('focus', function() {
el.val(scope.amount);
});
el.bind('input', function() {
scope.amount = el.val();
scope.$apply();
});
el.bind('blur', function() {
el.val($filter('currency')(scope.amount));
});
}
}
});
Link http://jsfiddle.net/moL8ztrw/6/

Format decimal points in angularjs

I have some 10 textboxes and if user enters 10.3691 then it should be changed to 10.37
<input ng-model='data.value1' ng-blur="formatDecimals1()">
<input ng-model='data.value2' ng-blur="formatDecimals2()">
......
In controller,
$scope.formatDecimals1 = function() {
$scope.data.value1= Number($scope.data.value1).toFixed($scope.data.value1Points);
}
$scope.formatDecimals2 = function() {
$scope.data.value2= Number($scope.data.value2).toFixed($scope.data.value2Points);
}
I feel this is not a right way... any other solution to achieve this. Or, how we can have single common function to format all textbox inputs.
You could write a custom directive, something like:
angular.module('app', []).
directive('toPrecision', function(){
return {
replace: false,
restrict: 'A',
link: function(scope, element) {
var e = angular.element(element);
e.on('keyup', function(){
var n = parseFloat(e.val());
if (parseInt(n, 10) !== n && String(n).split('.')[1].length > 2) {
e.val(n.toFixed(2));
}
});
}
}
});
then in the markup:
<div ng-app="app">
<input type="number" to-precision="2" />
</div>
You can try the working code here: http://jsfiddle.net/ctgxd4va/
ps. I wrote it in 2 minutes, it's far from perfection... but it should give you an idea ;)

AngularJS directive with Complexify

I need help to create a AngularJS directive with Complexify. It's a password meter. I needed this:
<input type="password" ng-model="password" />
<password-meter value="password" complexity="complexity"></passwordMeter>
<span class="label label-important" ng-show="complexity > 60">STRONG</span>
In my example, when password complexity is high, my <span> is display.
My code:
app.directive('passwordMeter', function() {
return {
restrict: 'E',
link: function link(scope, element, attrs) {
var minLengthPassword = 6;
var strengthScaleFactor = 1;
$(element).complexify({
minimumChars: minLengthPassword,
strengthScaleFactor: strengthScaleFactor
}, function(valid, complexity) {
if(valid) {
scope.complexity = complexity;
} else {
scope.complexity = 0;
}
});
scope.$watch('password', function() {
if(!scope.password) {
scope.complexity = 0;
}
});
}
};
});
Your code has some errors you need to fix:
If you define a directive name which is camel-case (passwordMeter) it maps to password-meter in your templates
To use the directive as an element like you do you need to set restrict: 'E' and not 'A'
jquery-complexify works on inputs so you need to include the input you define outside the password-meter directive inside your directive and reference it.
I've created a working plunker for you http://plnkr.co/edit/PdK8U7q3GzWVVUX5alrS?p=preview

Angular : how to re-render compiled template after model update?

I am working on an angular form builder which generate a json.
Everything works fine except one thing.
You can find an example here : http://jsfiddle.net/dJRS5/8/
HTML :
<div ng-app='app'>
<div class='formBuilderWrapper' id='builderDiv' ng-controller="FormBuilderCtrl" >
<div class='configArea' data-ng-controller="elementDrag">
<h2>drag/drop</h2>
<form name="form" novalidate class='editBloc'>
<div data-ng-repeat="field in fields" class='inputEdit'>
<data-ng-switch on="field.type">
<div class='labelOrder' ng-class='{column : !$last}' drag="$index" dragStyle="columnDrag" drop="$index" dropStyle="columnDrop">{{field.type}}
</div>
<label for="{{field.name}}" data-ng-bind-html-unsafe="field.caption"></label>
<input data-ng-switch-when="Text" type="text" placeholder="{{field.placeholder}}" data-ng-model="field.value" />
<p data-ng-switch-when="Text/paragraph" data-ng-model="field.value" data-ng-bind-html-unsafe="field.paragraph"></p>
<span data-ng-switch-when="Yes/no question">
<p data-ng-bind-html-unsafe="field.yesNoQuestion"></p>
<input type='radio' name="yesNoQuestion" id="yesNoQuestion_yes" value="yesNoQuestion_yes" />
<label for="yesNoQuestion_yes">Oui</label>
<input type='radio' name="yesNoQuestion" id="yesNoQuestion_no" value="yesNoQuestion_no"/>
<label for="yesNoQuestion_no">Non</label>
</span>
<p data-ng-switch-when="Submit button" class='submit' data-ng-model="field.value">
<input value="{{field.name}}" type="submit">
</p>
</data-ng-switch>
</div>
</form>
</div>
<div id='previewArea' data-ng-controller="formWriterCtrl">
<h2>preview</h2>
<div data-ng-repeat="item in fields" content="item" class='templating-html'></div>
</div>
</div>
</div>
The JS :
var app = angular.module('app', []);
app.controller('FormBuilderCtrl', ['$scope', function ($scope){
$scope.fields = [{"type":"Text/paragraph","paragraph":"hello1"},{"type":"Yes/no question","yesNoQuestion":"following items must be hidden","yes":"yes","no":"no"},{"type":"Text/paragraph","paragraph":"hello2"},{"type":"Submit button","name":"last item"}] ;
}]);
app.controller('elementDrag', ["$scope", "$rootScope", function($scope, $rootScope, $compile) {
$rootScope.$on('dropEvent', function(evt, dragged, dropped) {
if($scope.fields[dropped].type == 'submitButton' || $scope.fields[dragged].type == 'submitButton'){
return;
}
var tempElement = $scope.fields[dragged];
$scope.fields[dragged] = $scope.fields[dropped];
$scope.fields[dropped] = tempElement;
$scope.$apply();
});
}]);
app.directive("drag", ["$rootScope", function($rootScope) {
function dragStart(evt, element, dragStyle) {
if(element.hasClass('column')){
element.addClass(dragStyle);
evt.dataTransfer.setData("id", evt.target.id);
evt.dataTransfer.effectAllowed = 'move';
}
};
function dragEnd(evt, element, dragStyle) {
element.removeClass(dragStyle);
};
return {
restrict: 'A',
link: function(scope, element, attrs) {
if(scope.$last === false){
attrs.$set('draggable', 'true');
scope.dragStyle = attrs["dragstyle"];
element.bind('dragstart', function(evt) {
$rootScope.draggedElement = scope[attrs["drag"]];
dragStart(evt, element, scope.dragStyle);
});
element.bind('dragend', function(evt) {
dragEnd(evt, element, scope.dragStyle);
});
}
}
}
}]);
app.directive("drop", ['$rootScope', function($rootScope) {
function dragEnter(evt, element, dropStyle) {
element.addClass(dropStyle);
evt.preventDefault();
};
function dragLeave(evt, element, dropStyle) {
element.removeClass(dropStyle);
};
function dragOver(evt) {
evt.preventDefault();
};
function drop(evt, element, dropStyle) {
evt.preventDefault();
element.removeClass(dropStyle);
};
return {
restrict: 'A',
link: function(scope, element, attrs) {
if(scope.$last === false){
scope.dropStyle = attrs["dropstyle"];
element.bind('dragenter', function(evt) {
dragEnter(evt, element, scope.dropStyle);
});
element.bind('dragleave', function(evt) {
dragLeave(evt, element, scope.dropStyle);
});
element.bind('dragover', dragOver);
element.bind('drop', function(evt) {
drop(evt, element, scope.dropStyle);
var dropData = scope[attrs["drop"]];
$rootScope.$broadcast('dropEvent', $rootScope.draggedElement, dropData);
});
}
}
}
}]);
app.controller('formWriterCtrl', ['$scope', function ($scope){
}]);
app.directive('templatingHtml', function ($compile) {
var previousElement;
var previousIndex;
var i=0;
var inputs = {};
var paragraphTemplate = '<p data-ng-bind-html-unsafe="content.paragraph"></p>';
var noYesQuestionTemplate = '<p data-ng-bind-html-unsafe="content.yesNoQuestion"></p><input id="a__index__yes" type="radio" name="a__index__"><label for="a__index__yes" />{{content.yes}}</label><input id="a__index__no" class="no" type="radio" name="a__index__" /><label for="a__index__no">{{content.no}}</label>';
var submitTemplate = '<p class="submit"><input value="{{content.name}}" type="submit" /></p>';
var getTemplate = function(contentType, contentReplace, contentRequired) {
var template = '';
switch(contentType) {
case 'Text/paragraph':
template = paragraphTemplate;
break;
case 'Yes/no question':
template = noYesQuestionTemplate;
break;
case 'Submit button':
template = submitTemplate;
break;
}
template = template.replace(/__index__/g, i);
return template;
}
var linker = function(scope, element, attrs) {
i++;
elementTemplate = getTemplate(scope.content.type);
element.html(elementTemplate);
if(previousElement == 'Yes/no question'){
element.children().addClass('hidden');
element.children().addClass('noYes'+previousIndex);
}
if(scope.content.type == 'Yes/no question'){
previousElement = scope.content.type;
previousIndex = i;
}
$compile(element.contents())(scope);
}
return {
restrict: "C",
link: linker,
scope:{
content:'='
}
};
});
On the example there are 2 areas :
- the first one does a ngRepeat on Json and allow to reorder items with drag and drop
- the second area also does a ngRepeat, it is a preview templated by a directive using compile function. Some elements are hidden if they are after what I called "Yes/no question"
Here is an example of Json generated by the form builder :
$scope.fields =
[{"type":"Text/paragraph","paragraph":"hello1"},{"type":"Yes/no question","yesNoQuestion":"following items must be hidden","yes":"yes","no":"no"},
{"type":"Text/paragraph","paragraph":"hello2"},{"type":"Submit button","name":"last item"}] ;
When the page load everything is ok, Hello1 is visible and Hello2 is hidden.
But when I drop Hello1 after "Yes/no question", dom elements are reorganised but Hello1 is not hidden.
I think it comes from $compile but I don't know how to resolve it.
Could you help me with this please?
Thank you
I only see you setting the 'hidden' class on the element based on that rule (after a yes/no) in the link function. That's only called once for the DOM element - when it's first created. Updating the data model doesn't re-create the element, it updates it in place. You would need a mechanism that does re-create it if you wanted to do it this way.
I see three ways you can do this:
In your linker function, listen for the same dropEvent that you listen for above. This is more efficient than you'd think (it's very fast) and you can re-evaluate whether to apply this hidden class or not.
Use something like ngIf or literally re-creating it in your collection to force the element to be recreated entirely. This is not as efficient, but sometimes is still desirable for various reasons.
If your use case is actually this simple (if this wasn't a redux of something more complicated you're trying to do) you could use CSS to do something like this. A simple rule like
.yes-no-question + .text-paragraph { display: none; }
using a sibling target could handle this directly without as much work. This is much more limited in what it can do, obviously, but it's the most efficient option if it covers what you need.

Resources