animating number change in directive in angular - angularjs

I have a directive which I have included jquery's animate functionality in to. I'd like for a particular variable's number to change with easing animation. The issue is that then the directive loads, the initial number is shown but doesn't show the number changing with the animation effect.
I have created a similar version in Plunkr to make it easy to see what's going on.
If I trigger $apply() from elsewhere the final numbers show, skipping the whole animated sqeuqnce of numbers. Also, in the code when I try to do apply on each step, it throws an 'in progress' error.
This plugin almost does what I need it to, except that it doesn't increment over decimal places and doesn't use easing. http://sparkalow.github.io/angular-count-to/
scope.$watch('difference', function(newVal, oldVal) {
jQuery({someValue: oldVal}).animate({someValue: newVal}, {
duration: 1000,
easing:'swing',
step: function(e) {
scope.display = e.toFixed(2);
scope.$parent.$apply();
}
});
});
and..
template: function(scope, element, attrs) {
return '<h3>' +
'<i class="fa progress-arrow" ng-class="[{\'fa-caret-up\': direction_up}, {\'fa-caret-down\': direction_down}]" aria-hidden="true"></i> ' +
'{{ display }}' +
'</div>' +
'</h3>' +
'<label>{{ label }} (lbs)</label>';

The answer was to use the angular $timeout function in conjunction with scope.$apply().
Here's the updated code that does in fact work:
scope.$watch('difference', function(newVal, oldVal) {
jQuery({someValue: oldVal}).animate({someValue: newVal}, {
duration: 500,
easing:'swing',
step: function(e) {
$timeout(function () {
scope.$apply(function () {
scope.display = e.toFixed(2);
});
});
}
});
And here it is in Plunkr

create directive
export class IncrementCounterDirective implements AfterViewInit {
#Input('appIncrementCounter') to: number = 0;
constructor(private elRef: ElementRef, private renderer: Renderer2) {}
ngAfterViewInit(): void {
this.counterFunc(this.to, 2000);
}
private counterFunc(end: number, duration: number = 3000) {
let range, current: number, step, timer: any;
range = end - 0;
current = end - 150;
step = Math.abs(Math.floor(duration / range));
// console.log(`step`, step);
timer = setInterval(() => {
current += 1;
this.setText(current);
if (current >= end) {
clearInterval(timer);
}
}, step);
}
setText(n: number) {
this.renderer.setProperty(this.elRef.nativeElement, 'innerText', `${n}`);
}
}
To use
<h3 class="stat-count" [appIncrementCounter]="607">000</h3>

Related

how to create custom editor provider in ignite ui grid with angular js

I am using Ignite UI grid directive with angular js.
In that I am creating custom editor provider by extending $.ig.EditorProvider
and using that editor in html markup as
<column-setting column-key="comments" editor-provider="new $.ig.EditorProviderNumber()">
</column-setting>
but when I edit grid its showing error
provider.createEditor is not a function
plz help me
Written this way the "editor-provider" value will be evaluated as string. In order for the expression to be parsed to an object you need to enclose it in {{}} (double curly braces). However the statement "new $.ig.EditorProviderNumber()" will not be parsed by the Angular 1 expression parser, so you need to use a scope function to create the object.
Here is the code:
// This editor provider demonstrates how to wrap HTML 5 number INPUT into editor provider for the igGridUpdating
$.ig.EditorProviderNumber = $.ig.EditorProviderNumber || $.ig.EditorProvider.extend({
// initialize the editor
createEditor: function (callbacks, key, editorOptions, tabIndex, format, element) {
element = element || $('<input type="number" />');
/* call parent createEditor */
this._super(callbacks, key, editorOptions, tabIndex, format, element);
element.on("keydown", $.proxy(this.keyDown, this));
element.on("change", $.proxy(this.change, this));
this.editor = {};
this.editor.element = element;
return element;
},
keyDown: function(evt) {
var ui = {};
ui.owner = this.editor.element;
ui.owner.element = this.editor.element;
this.callbacks.keyDown(evt, ui, this.columnKey);
// enable "Done" button only for numeric character
if ((evt.keyCode >= 48 && evt.keyCode <= 57) || (evt.keyCode >= 96 && evt.keyCode <= 105)) {
this.callbacks.textChanged(evt, ui, this.columnKey);
}
},
change: function (evt) {
var ui = {};
ui.owner = this.editor.element;
ui.owner.element = this.editor.element;
this.callbacks.textChanged(evt, ui, this.columnKey);
},
// get editor value
getValue: function () {
return parseFloat(this.editor.element.val());
},
// set editor value
setValue: function (val) {
return this.editor.element.val(val || 0);
},
// size the editor into the TD cell
setSize: function (width, height) {
this.editor.element.css({
width: width - 2,
height: height - 2,
borderWidth: "1px",
backgroundPositionY: "9px"
});
},
// attach for the error events
attachErrorEvents: function (errorShowing, errorShown, errorHidden) {
// implement error logic here
},
// focus the editor
setFocus: function () {
this.editor.element.select();
},
// validate the editor
validator: function () {
// no validator
return null;
},
// destroy the editor
destroy: function () {
this.editor.remove();
}
});
sampleApp.controller('sampleAppCtrl', function ($scope) {
$scope.getProvider = function () {return new $.ig.EditorProviderNumber()};
});
<column-setting column-key="ProductNumber" editor-provider="{{getProvider()}}"></column-setting>

Angular ui-bootstrap typeahead suggestion - scroll

According to this link :
up/down arrow key issue with typeahead control (angular bootstrap UI)
i have added these line in my js:
.directive('shouldFocus', function(){
return {
restrict: 'A',
link: function(scope,element,attrs){
scope.$watch(attrs.shouldFocus,function(newVal,oldVal){
element[0].scrollIntoView(false);
});
}
};
})
while scrolling, we get some more disturb, scroll was not smoothy.
Before adding this code, scroll was normal and smoothy.
Please anyone help me?
Hi here is another code only for keyup event to adjust the scroll height while up/down keypress
you need to add a function only to keyup in the typeahead directive in angular ui
search directive('typeahead' in the angular ui js file
find fireRecalculating function before it paste the following function
function makeSureVisible(){
$timeout(function(){
$el = popUpEl.find('.active');
if($el.length>0)
{
var elTop, elBottom, nodeScrollTop, nodeHeight;
elTop = $el.position().top;
console.log(elTop);
elBottom = elTop + $el.outerHeight(true);
nodeScrollTop = popUpEl.scrollTop();
nodeHeight = popUpEl.height() + parseInt(popUpEl.css("paddingTop"), 10) + parseInt(popUpEl.css("paddingBottom"), 10);
if (elTop < 0) {
popUpEl.scrollTop(nodeScrollTop + elTop);
} else if (nodeHeight < elBottom) {
popUpEl.scrollTop(nodeScrollTop + (elBottom - nodeHeight));
}
}
});
}
now find keyup function attached. call the above function
element.bind('keydown', function(evt) {
//typeahead is open and an "interesting" key was pressed
if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) {
return;
}
// if there's nothing selected (i.e. focusFirst) and enter or tab is hit, clear the results
if (scope.activeIdx === -1 && (evt.which === 9 || evt.which === 13)) {
resetMatches();
scope.$digest();
return;
}
evt.preventDefault();
if (evt.which === 40) {
scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
scope.$digest();
makeSureVisible();
} else if (evt.which === 38) {
scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1;
scope.$digest();
makeSureVisible();
} else if (evt.which === 13 || evt.which === 9) {
scope.$apply(function () {
scope.select(scope.activeIdx);
});
} else if (evt.which === 27) {
evt.stopPropagation();
resetMatches();
scope.$digest();
}
});
function makeSureVisible() {
$timeout(function () {
$el = popUpEl[0].querySelector('.active');
if ($el) {
var elTop, elBottom, nodeScrollTop, nodeHeight;
elTop = $el.offsetTop;
elBottom = elTop + $el.offsetHeight;
nodeScrollTop = popUpEl[0].scrollTop;
nodeHeight = popUpEl[0].offsetHeight - (parseInt(window.getComputedStyle(popUpEl[0], null).getPropertyValue('padding-top')) + parseInt(window.getComputedStyle(popUpEl[0], null).getPropertyValue('padding-bottom')));
if (elTop < nodeScrollTop) {
popUpEl[0].scrollTop = elTop;
} else if (nodeHeight < elBottom) {
popUpEl[0].scrollTop = nodeScrollTop + elBottom - nodeHeight;
}
}
});
}
I bumped into the same issue with latest version of angular bootstrap 0.14.3 as of 27/11/2015.
Comments:
I placed the function in the element.bind("keydown") since it didn't work where you suggested. (function wasn't in proper scope and was undefined)
The first "if" wasn't triggered for me so i changed the logic a bit. When user reaches the end the dropdown scrolls to top properly in my case.
Modified it to work for plain javascript.
Thank you for your solution!
I had previously resolved this issue using the "shouldFocus" directive but I had to make more tweaks to get this to work. Perhaps this version will work for you.
.directive('shouldFocus', function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
scope.$watch(attrs.shouldFocus, function (newVal, oldVal) {
if (newVal && element.prop("class").indexOf("active")) {
var par = element.parent("ul");
var cellHeight = element.children().innerHeight();
var maxHeight = par.height();
var startIndex = Math.floor(maxHeight / cellHeight);
if (scope.$index > startIndex) {
var scroll = (scope.$index - startIndex) * cellHeight;
par.scrollTop(scroll);
}
if (scope.$index === 0) {
par.scrollTop(0);
}
}
});
}
}
})
Here is the modified template for those who don't know where to add the directive:
angular.module("template/typeahead/typeahead-popup.html", []).run(["$templateCache", function($templateCache) {
$templateCache.put("template/typeahead/typeahead-popup.html",
"<ul class=\"dropdown-menu\" ng-show=\"isOpen() && !moveInProgress\" ng-style=\"{top: position().top+'px', left: position().left+'px'}\" style=\"display: block;\" role=\"listbox\" aria-hidden=\"{{!isOpen()}}\">\n" +
" <li ng-repeat=\"match in matches track by $index\" should-focus=\"isActive($index)\" ng-class=\"{active: isActive($index) }\" ng-click=\"selectMatch($index)\" role=\"option\" id=\"{{::match.id}}\">\n" +
" <div typeahead-match index=\"$index\" match=\"match\" query=\"query\" template-url=\"templateUrl\"></div>\n" +
" </li>\n" +
"</ul>\n" +
"");
}]);

angularjs ui-router syntax

I have a datatable, when I select an element, I have an edit button that appears that need to have the correct edit url, here what I have done:
// this is triggered on select AND unselect (multiselect = false)
function onRowSelectionChanged(row, evt) {
if (row.isSelected) {
vm.feed = row.entity;
} else {
delete vm.feed;
}
vm.selected = row.isSelected;
}
// this is the watcher that updates my button
$scope.$watch(function () {
return vm.selected;
}, function (newValue, oldValue) {
if (newValue === oldValue) {
return;
}
if (newValue) {
vm.actions.push({state: 'app.feeds.edit({feedId: ' + vm.feed.id + '})', icon: 'pencil'});
} else {
vm.actions.splice(-1);
}
});
My question is about this:
vm.actions.push({state: 'app.feeds.edit({feedId: ' + vm.feed.id + '})', icon: 'pencil'});
Is this a clean way of interpolating the value, or can I do better ? as the Feed is bound to the vm, how comes I cannot do this:
vm.actions.push({state: 'app.feeds.edit({feedId: vm.feed.id})', icon: 'pencil'});
If I write this last piece of code, I always get feeds//edit
Because the handlebars {{ }} are used in the html or templates.
if you want to add a value in a string with js you need to escape it with and concatenate it.
This is just how you can inject values into strings

AngularJS ngClass not working in ngRepeat in custom directive inside ngRepeat

Here's a plunker:
http://plnkr.co/edit/hJsZFCBZhFT5rRo1lgSZ?p=preview
It's a simple directive that is supposed to show a given number of dots and highlight a subset of them based on the value, min, and max given, similar to a star rating. It works great when on its own (as you can see in the last row of the plunkr), but not in an ngRepeat. The problem is the ngClass on the directive's own ngRepeat seems to fail, even though the expression used in the ngClass seems to be evaluating correctly outside of the ngClass. I've been tearing my hair out over this for the last few hours, and I just can't figure it out.
Here's the actual code:
HTML:
<div ng-repeat="row in [1,2,3,4,5]">
<match-dots value="{{ $index+1 }}" min="0" max="10">{{ isDotActive($index) }}</match-dots>
</div>
JS:
angular.module('app', [])
.directive('matchDots', function() {
return {
restrict: 'EA',
replace: true,
transclude: true,
template:
"<span class='match-dots-group'>" +
"<span class='match-dots-dot' ng-repeat='dot in dotsList()' ng-class='{ active: isDotActive($index) }' ng-transclude></span>" +
"</span>",
scope: {
dots: '#',
min: '#',
max: '#',
value: '#'
},
link: function(scope, elem) {
// save the previous class applied so we can remove it when the class changes
var previousClass = '';
/**
* set the class for the dots-group based on how many dots are active (n-active)
*/
scope.setDotsClass = function() {
if(previousClass != '') {
elem.removeClass(previousClass);
}
previousClass = "active-"+scope.numActiveDots();
elem.addClass(previousClass);
}
},
controller: function($scope) {
$scope.dots = angular.isUndefined($scope.dots)?5:parseInt($scope.dots);
$scope.min = angular.isUndefined($scope.min)?0:parseInt($scope.min);
$scope.max = angular.isUndefined($scope.max)?100:parseInt($scope.max);
/**
* create a list of numbers
* #returns {Array}
*/
$scope.dotsList = function() {
var arr = [];
for(var i=1; i<=$scope.dots; i++) {
arr.push(i);
}
return arr;
};
/**
* should the given dot be lit up?
* #param {number} dot
* #returns {boolean}
*/
$scope.isDotActive = function(dot) {
return dot < $scope.numActiveDots();
};
/**
* How many dots are active?
* #returns {number}
*/
$scope.numActiveDots = function() {
var activeDots = Math.ceil(($scope.value-$scope.min) / (($scope.max-$scope.min) / $scope.dots));
return isNaN(activeDots)?0:activeDots;
};
// make sure the value is an number
$scope.$watch('value', function(newValue) {
$scope.value = parseFloat(newValue);
$scope.setDotsClass();
});
// make sure the number of dots is actually a positive integer
$scope.$watch('dots', function(newDots) {
if(angular.isUndefined(newDots) || !newDots || newDots < 1) {
$scope.dots = 5;
} else {
$scope.dots = parseInt(newDots);
}
$scope.setDotsClass();
});
// make sure the min is not greater than the max
$scope.$watch('max', function(newMax) {
if(angular.isUndefined(newMax)) {
$scope.max = newMax = 100;
}
if(angular.isString(newMax)) {
$scope.max = parseFloat(newMax);
} else if(newMax < $scope.min) {
$scope.max = $scope.min;
$scope.min = newMax;
}
$scope.setDotsClass();
});
// make sure the max is not less than the min
$scope.$watch('min', function(newMin) {
if(angular.isUndefined(newMin)) {
$scope.min = newMin = 0;
}
if(angular.isString(newMin)) {
$scope.min = parseFloat(newMin);
} else if(newMin > $scope.max) {
$scope.min = $scope.max;
$scope.max = newMin;
}
$scope.setDotsClass();
});
}
}
});
The first thing that looked suspicious to me is that you were reassigning the $scope.dots property and your numActiveDots function, quite often was ending up with NaN. That property is passed into your directive as a string, then you are parsing/reassigning it as number. I tried renaming your parsed value to $scope.dotsinternal. That seems to make it work.
http://plnkr.co/edit/STwuts2D169iBLDoTtE2?p=preview
Change this:
$scope.dots = angular.isUndefined($scope.dots)?5:parseInt($scope.dots);
to this:
$scope.dotsinternal = angular.isUndefined($scope.dots)?5:parseInt($scope.dots);
then updated all the references to $scope.dots to $scope.dotsinternal.

AngularJS setting default values for directive

In my parent controller:
//soemtimes view invoice has taxtotal defined
$scope.viewinvoice = {taxtotal:4}
//sometimes viewinvoice does not have taxtotal defined
$scope.viewinvoice = {}
//sometimes it is defined but equal to 0
$scope.viewinvoice = {taxtotal:0}
In my parent view:
<div class="span6">
<invoice invoice='viewinvoice'></invoice>
</div>
My directive:
.directive('invoice', [ function () {
return {
restrict: 'E',
scope: {
invoice:'='
},
replace: true,
template: '<div>
<input type="checkbox" ng-model="taxflag">
<div> {{ calculate_grand_total() }} </div>
</div>',
link: function($scope, element, attrs) {
}
};
}]);
In my directive I want to set $scope.taxflag true based on the property: $scope.invoice.taxtotal, the problem is if $scope.invoice.taxtotal is undefined I want to set the $scope.taxflag to false, and if the $scope.invoice.taxtotal is greater than 0 and is defined I want the $scope.taxflag set to true.
i.e.
if($scope.invoice.hasOwnProperty('taxtotal')){
if($scope.invoice.taxtotal > 0 ) {
$scope.taxflag = true;
} else {
$scope.taxflag = false;
}
} else {
$scope.invoice.taxtotal = 0;
$scope.taxflag = false;
}
I want this (above code) to act like 'initialization' code so whenever my 'viewinvoice' changes in the parent the $scope.taxflag and $scope.invoice.taxtotal will both be initially be set up correctly
I also want to trigger a change whenever the checkbox is checked:
i.e.
$scope.$watch('taxflag',function(newValue){
if(newValue) {
$scope.invoice.taxtotal = 5
} else {
$scope.invoice.taxtotal = 0;
}
});
I am also using this $scope.invoice.taxtotal elsewhere in a function {{ calculate_grand_total() }}
(in my directives view)
ie.
$scope.calculate_grand_total = function() {
return $scope.invoice.taxtotal + 5;
}
but this is unable to render because $scope.invoice.taxtotal is not defined (at least initially) !!
Does this make sense? I have tried so many different combinations but I can't seem to get it working as I want it to.
I've created this plunkr that tries to capture your question:
http://plnkr.co/edit/02QAC8m9xyF4pSyxnfOf
Basically, any code that depends on a value that can change should be in a watch. This means that your initialization code for setting taxflag belongs in a watch, so that it can update the tax flag if things change. This looks something like:
$scope.$watch('invoice.taxtotal', function(taxTotal) {
if (!angular.isNumber(taxTotal)) {
$scope.taxflag = false
return;
}
if (taxTotal > 0) {
$scope.taxflag = true;
} else {
$scope.taxflag = false;
}
});
Keep in mind watches are always executed the first time to initialize the values, so they essentially serve as both initialization code and update code.
As far as your calculate_grand_total function, if it is up to you to define what it should return if tax total or invoice are undefined. Simply check whether it is undefined and return the appropriate value, for example in my plunkr I return empty string:
$scope.calculate_grand_total = function() {
if ($scope.invoice && angular.isNumber($scope.invoice.taxtotal)) {
return $scope.invoice.taxtotal + 5;
} else {
return "";
}
}
I'm not sure if my plunkr works exactly like you want it or not, but it should get you started in the right direction. Or serve as a starting point for further clarification.
related:
if you only want to set first-run defaults you can use angular.merge
inside your link:
link: function(scope, element, attr, ctrl) {
// ....
var settings_default = {
setting1: true,
setting2: {
x: 0,
y: 10
},
};
// order is important. last item overwrites previous
scope.settings = angular.merge({}, settings_default, scope.settings);
// ....
})

Resources