how to get event when user scroll to top in angular js? - angularjs

could you please tell me how to get event when user scroll to top .Actually I am using ng-repeat in my example .I want to get event when user scroll to bottom and scroll to top .I have one div in which I used ng-repeat can we get event of top when user move to top after scrolling.Actually I need to show alert when user scroll to bottom and top of div in angular .here is my code
<body ng-controller="MyController">
<div style="width:90%;height:150px;border:1px solid red;overflow:auto">
<div ng-repeat="n in name">{{n.name}}</div>
</div>

You could put directives on your scrollable div that listen to the scroll event and check for the top or bottom being reached.
So, using your HTML, your div would look like this:
<div exec-on-scroll-to-top="ctrl.handleScrollToTop"
exec-on-scroll-to-bottom="ctrl.handleScrollToBottom"
style="width:90%;height:150px;border:1px solid red;overflow:auto">
<div ng-repeat="n in name">{{n.name}}</div>
</div>
With new directives exec-on-scroll-to-top and exec-on-scroll-to-bottom added. Each specifies a function in your controller that should execute when the particular event the directive is checking for occurs.
exec-on-scroll-to-top would look like this, just checking for the scrollable div's scrollTop property to be 0:
myapp.directive('execOnScrollToTop', function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var fn = scope.$eval(attrs.execOnScrollToTop);
element.on('scroll', function (e) {
if (!e.target.scrollTop) {
console.log("scrolled to top...");
scope.$apply(fn);
}
});
}
};
});
And exec-on-scroll-to-bottom would look like this (keeping in mind that an element is fully scrolled when its (scrollHeight - scrollTop) === clientHeight):
myapp.directive('execOnScrollToBottom', function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var fn = scope.$eval(attrs.execOnScrollToBottom),
clientHeight = element[0].clientHeight;
element.on('scroll', function (e) {
var el = e.target;
if ((el.scrollHeight - el.scrollTop) === clientHeight) { // fully scrolled
console.log("scrolled to bottom...");
scope.$apply(fn);
}
});
}
};
});
Here's a plunk. Open the console to see messages getting logged when the scrolling reaches the top or bottom.

This is a non angular way, but you can wrap it up in a directive which also allows reuse:
Use Javascript event listener:
div.addEventListener('scroll', function(){
if(this.scrollTop===0)
//do your stuff
});
Make sure to use $apply if you make any changes to the scope variables inside this listener.

Related

Scroll Bottom Angularjs Directive

I am working on chat app, and the problem seems to be that directive for scroll bottom, to the last item in ng-repeat is not working properly when there are images in messages.
So it just doesn't scroll all the way bottom.
I think the reason is that directive scrolls before the image is fully loaded.
// Scroll to bottom directive
.directive('schrollBottom', function ($timeout) {
return {
scope: {
schrollBottom: "="
},
link: function (scope, element) {
scope.$watchCollection('schrollBottom', function (newValue) {
if (newValue)
{
$timeout(function() {
$(element[0]).scrollTop($(element)[0].scrollHeight);
}, 0, false);
}
});
}
}
})
Check here to see how to wait for an image to load before doing something. You can add a class to all images and wait for all images with that class to load before scrolling.
I would keep what logic you have, and put it inside of an img.onload callback handler. Then any scrolling to the bottom will only occur once all images have loaded.

How to use a different combination of triggers for uib-popover?

The official documentation at :https://angular-ui.github.io/bootstrap/#/popover says that the following trigger combos can be passed as param to the popover-trigger attribute :
mouseenter: mouseleave
click: click
outsideClick: outsideClick
focus: blur
none
I want to use a combination of
mouseenter: outsideClick
How to achieve this without using the popover-is-open attribute?
You can't, the docs state
The outsideClick trigger will cause the popover to toggle on click, and hide when anything else is clicked.
"anything else" includes the element itself, so toggeling the element using outsideClick on or off and will interfere with the natural behavior of other triggers.
for example if state your triggers like so popover-trigger="mouseleave outsideClick"
, the trigger mouseleave will hide the popover instead of showing it if you have already clicked the element, otherwise it will just show it on leave. (plunk).
If you can hack it using popover-is-open then continue doing so, if it bothers you too much you can always request a feature.
popover-trigger="mouseenter outsideClick" for the uib-popover directive does not seem to work as one would think.
Initially, I thought it meant the following:
On mouse enter show the popover
On mouse leave hide the popover
On click keep popover open in an active state
On outside click close popover if it is in an active state
Since it does not I needed a manual approach, the following is stated in the documentation.
For any non-supported value, the trigger will be used to both show and hide the popover. Using the 'none' trigger will disable the internal trigger(s), one can then use the popover-is-open attribute exclusively to show and hide the popover.
So I created some HTML like:
<span class="glyphicon glyphicon-info-sign"
ng-class="{'text-primary' : isInfoPopoverClicked}"
ng-click="toggleInfoPopoverClicked()"
ng-mouseenter="enterInfoPopover()"
ng-mouseleave="leaveInfoPopover()"
custom-click-outside="closeInfoPopover()"
uib-popover-template="'info.html'"
popover-trigger="'none'"
popover-is-open="isInfoPopoverOpen()"
popover-placement="auto top"
popover-append-to-body="true" >
</span>
The JS in the controller:
// Toggle popover's clicked active state
$scope.toggleInfoPopoverClicked = function() {
$scope.isInfoPopoverClicked = !$scope.isInfoPopoverClicked;
};
// Close the popover, used for outside click and close action inside the template
$scope.closeInfoPopover = function() {
delete $scope.isInfoPopoverClicked;
};
// On mouse enter, show the popover
$scope.enterInfoPopover = function() {
$scope.isInfoPopoverMouseEnter = true;
};
// On mouse leave, close the popover.
// If clicked active state is false set to undefined.
// This supports when the user clicks the icon to close,
// that mouse enter does not immediately display the popover again.
$scope.leaveInfoPopover = function() {
$scope.isInfoPopoverMouseEnter = false;
if(false === $scope.isInfoPopoverClicked) {
delete $scope.isInfoPopoverClicked;
}
};
// Expression used in the popover-is-open attribute
$scope.isInfoPopoverOpen = function() {
if($scope.isInfoPopoverClicked) {
return true;
} else if(false === $scope.isInfoPopoverClicked){
return false;
}
return $scope.isInfoPopoverMouseEnter;
};
The template for the uib-popover-template I used:
<div custom-stop-event="click" class="pull-right">
<span ng-click="closeInfoPopover()" class="glyphicon glyphicon-remove"></span>
<section>{{info}}</section>
</div>
Now the trickier part was that this solution required me to create two more directives.
One to close the popover when clicking outside the element.
Another to stop the click event fired inside the pop-up. Preventing it from closing the popover.
The custom-click-outside directive:
angular.module('LSPApp').directive('customClickOutside', ['$document', function ($document) {
return {
restrict: 'A',
scope: {
clickOutside: '&customClickOutside'
},
link: function (scope, element) {
var handler = function (event) {
if (element !== event.target && !element[0].contains(event.target)) {
scope.$applyAsync(function () {
scope.clickOutside();
});
}
};
// Might not work on elements that stop events from bubbling up
$document.on('click', handler);
// Clean up event so it does not keep firing after leaving scope
scope.$on('$destroy', function() {
$document.off('click', handler);
});
}
};
}]);
The custom-stop-event directive called from the template's HTML:
angular.module('LSPApp').directive('stopEvent', function () {
return {
restrict: 'A',
link: function (scope, element, attr) {
element.on(attr.stopEvent, function (e) {
e.stopPropagation();
});
}
};
});
Hopefully, this helps someone, my final solution had all this encapsulated in it's own directive to promote reuse.

Directive link function not called

I have a portion of view that refreshes itself, say the div hides when an API call is in progress and shows up when the response is obtained.
This portion of view (div) has a angular directive.
View
<div ng-controller="myCtrl>
<input type="button" ng-click="callAPI()">
<div ng-show="isAPICallComplete">
<p data-my-directive="something" ng-repeat="name in names">{{name}}</p>
</div>
</div>
Directive
angular.module('myModule')
.controller('myCtrl', function ($scope, $http) {
$scope.callAPI = function () {
$http.get('someURL').then(function (response) {
$scope.isAPICallComplete = true;
$scope.names= response.names;
});
}
})
.directive('myDirective', function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
console.log('reached directive');
}
}
});
With the above code, on page load the API call is already complete and hence the div shows up which then invokes the angular directive and I could see the log in console. But when on other conditions the API is called, the div hides itself and shows up again. In this case, the angular directive is not invoked (I don't see the console log message).
You can Do:
Just change the ng-show to ng-if it will work,
As the DOM will be created again on using ng-if
Just thought it is worth mentioning
ng-if removes or adds the element to the DOM whereas ng-show only hides or shows the element using css properties.

AngularJS scroll directive - How to prevent re-rendering whole scope

I have an AngularJS Application with a scroll directive implemented as the following:
http://jsfiddle.net/un6r4wts/
app = angular.module('myApp', []);
app.run(function ($rootScope) {
$rootScope.var1 = 'Var1';
$rootScope.var2 = function () { return Math.random(); };
});
app.directive("scroll", function ($window) {
return function(scope, element, attrs) {
angular.element($window).bind("scroll", function() {
if (this.pageYOffset >= 100) {
scope.scrolled = true;
} else {
scope.scrolled = false;
}
scope.$apply();
});
};
});
The HTML looks the following:
<div ng-app="myApp" scroll ng-class="{scrolled:scrolled}">
<header></header>
<section>
<div class="vars">
{{var1}}<br/><br/>
{{var2()}}
</div>
</section>
</div>
I only want the class scrolled to be added to the div once the page is scrolled more than 100px. Which is working just fine, but I only want that to happen! I don't want the whole scope to be re-rendered. So the function var2() should not be executed while scrolling. Unfortunately it is though.
Is there any way to have angular only execute the function which is bound to the window element without re-rendering the whole scope, or am I misunderstanding here something fundamentally to AngularJS?
See this fiddle:
http://jsfiddle.net/un6r4wts/
Edit:
This seems to be a topic about a similar problem:
Angularjs scope.$apply in directive's on scroll listener
If you want to calculate an expression only once, you can prefix it with '::', which does exactly that. See it in docs under One-time binding:
https://docs.angularjs.org/guide/expression
Note, this requires angular 1.3+.
The reason that the expressions are calculated is because when you change a property value on your scope, then dirty check starts and evaluates all the watches for dirty check. When the view uses {{ }} on some scope variable, it creates a binding (which comes along with a watch).

JQuery UI event callbacks in Directives

I'm building an AngularJS app and would like some pointers on the way I'm structuring my Angular code.
For this module, I'd like to be able to click a button that will add a Div to a specific "parent div" that has JQuery UI draggable and resizable interactions attached to it.
They could add more than one Div that is draggable and resizable.
Right now, I have one directive that generates the whole Div. The event callback for when resizing is stopped is placed in the Button that has the directive attached to it.
Directive code gist:
var app = angular.module('myApp', []);
app.controller('IpadCtrl', function($scope) {
$scope.someFunc = function () {
console.log('resized');
alert('resized');
};
$scope.test = 'sup';
});
app.directive('addImageCont', function () {
return {
restrict: 'A',
link: function (scope, element, attributes) {
console.log(attributes);
element.on("click", function() {
var div = $("<div />").css({
'width': '100px',
'height': '100px',
'background-color': 'purple'
});
div.draggable();
div.resizable();
div.on('resizestop', function(event, ui) {
console.log(ui);
var cBack = scope[attributes.callback];
cBack();
});
element.parent().find('.ipad').append(div);
})
}
};
});
JSFiddle that has an example:
http://jsfiddle.net/GA2M8/3/
Is this the right way to handle callback events? I'd like to be able to have a callback for when resizing begins too.
I know there's a better way to handle this.
It has been suggested to me that I break up my directives.
One directive that just creates the Div and appends it to the Dom.
Another directive that attaches the JQuery UI draggable interaction to it.
Another directive that attaches the JQuery UI resizable interaction to it.
I'm not sure how to chain directives if that's how I should proceed.

Resources