AngularJS How to watch offset top position of elements? - angularjs

How to watch offset top position (of each element) and if the distance to top screen border lower than 100 px, i need to hide this div. (each div can move on page). Help please write Angular directive.
I can't watch each element top offset.
var app = angular.module('app', []);
app.app.directive('blah', function() {
return {
restrict: 'A',
link: function(scope, element) {
element.show();
scope.$watch(function() {
return element.offset().top > 120
}, function(outOfBorder) {
if (outOfBorder) {
element.hide()
console.log(outOfBorder)
} else {
element.show()
console.log(outOfBorder)
}
});
}
}
});
.all {
height: 100px;
width: 300px;
border:1px solid;
overflow: scroll;
}
.elements {
height: 30px;
width: 300px;
border:1px solid red;
float: left;
clear: both;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div class="all" ng-app="app">
<div class="elements" blah></div>
<div class="elements" blah></div>
<div class="elements" blah></div>
<div class="elements" blah></div>
<div class="elements" blah></div>
<div class="elements" blah></div>
<div class="elements" blah></div>
<div class="elements" blah></div>
</div>

You simply add a watcher with a function (not tested + probably need some renaming):
angular.module('blah').directive('blah', function() {
return {
restrict: 'A',
link: function(scope, element) {
scope.$watch(function() {
return element.offset().top < 100
}, function(outOfBorder) {
if (outOfBorder) {
element.hide()
} else {
element.show()
}
});
}
}
});
The reason why your code is not working is that value you passed to a $watch is evaluated before $watch is executed. As a result, you watch top offset on the element at the time link is executed - this is a constant number.
EDIT:
If your elements are not absolutely positioned, calling hide will cause all the remaining elements to move up which eventually will make all elements hidden. In this case use element.addClass('invisible') and element.removeClass('invisible') instead of hiding and showing and define css class as:
.invisible { visibility: hidden; }
Changed visibility will not affect other element position.

Related

how to add style in angularjs based on condition in html itself

I have the above page structure . i want to check a conditon to apply style in div with id if the buttonlayout has a class proceed.
<div>
<div id="main">
</div>
<div id='buttonlayout" class="proceed">
</div>
I am displaying the <div id='buttonlayout" class="proceed"></div> based on some conditions. so if the div doesnt exist I dont want to apply sty;e. How can I check this in angular js?
So the logic I am looking for is
if (.div-proceed) exist add an extra style to div (with id main). Is it possible to check in my html page itself (ng-class)?
You can set conditions in ng-class, as sample $scope.proceed default is false our condition is when $scope.proceed is true add class proceed to the element.
This mean if $scope.proceed = false the class not exist, and if it true class is exist.
var app = angular.module("app", []);
app.controller("ctrl", ["$scope", "$filter", function($scope, $filter) {
$scope.change = function() {
$scope.proceed = !$scope.proceed;
}
}]);
body {
padding-top: 50px;
}
a{cursor: pointer}
#buttonlayout {
width: 100px;
height: 100px;
transition: all 0.5s;
background-color: #ccc;
}
#buttonlayout.proceed {
width: 200px;
height: 200px;
background-color: green;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<a ng-click="change()">click to remove/add 'proceed' class</a>
<div id="buttonlayout" ng-class="{'proceed': proceed}"></div>
</div>

Angular - add class if there are scroll bars

I have a jsfiddle here - https://jsfiddle.net/r6Lff67n/
There is no Angular here but just an example of the structure.
I have an outer div with a max-height inside of which there is content that could scroll if the content is bigger than the surrounding div.
My question is is there a simple way in Angular to add a class if the scroll bars are used so I can style it different if it will scroll
<div class="scroll-outer">
<div class="scroll-inner">
<div class="block"></div>
<div class="block"></div>
<div class="block"></div>
<div class="block"></div>
<div class="block"></div>
<div class="block"></div>
<div class="block"></div>
<div class="block"></div>
<div class="block"></div>
<div class="block"></div>
</div>
</div>
Finding whether a DOM object has scrollbar or not can be easily done by comparing scrollHeight with clientHeight of an element.
So, I pulled up a directive that can compare these heights and add a class to the element with which it is bound to. Something like:
var myApp = angular.module('myApp', []);
myApp.directive("testDirective", function() {
return {
link: function(scope, elem, attrs) {
if (elem[0].clientHeight < elem[0].scrollHeight) {
// scrollbar available
// adding a custom class with sample CSS
elem[0].classList += " mytest"
}
}
}
})
Also, custom class name(s) can be externalized using directive's attrs.
Here's the working runnable code snippet and updated fiddle.
Notice that one of the two lists of blocks has mytest class's CSS (border) based on whether it has scrollbar or not. You can add your custom CSS there (in mytest class)
var myApp = angular.module('myApp', []);
myApp.directive("testDirective", function() {
return {
link: function(scope, elem, attrs) {
if (elem[0].clientHeight < elem[0].scrollHeight) {
// scrollbar available
// adding a custom class with sample CSS
elem[0].classList += " mytest"
}
}
}
})
.block {
background: red;
height: 50px;
margin-bottom: 2px;
}
.scroll-outer {
overflow-y: auto;
max-height: 200px;
width: 250px;
display: inline-block
}
.mytest {
border: 3px solid black /* change this as you wish */
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="myApp">
<div class="scroll-outer" test-directive>
<div class="scroll-inner">
<div class="block"></div>
<div class="block"></div>
</div>
</div>
<div class="scroll-outer" test-directive>
<div class="scroll-inner">
<div class="block"></div>
<div class="block"></div>
<div class="block"></div>
<div class="block"></div>
<div class="block"></div>
<div class="block"></div>
<div class="block"></div>
<div class="block"></div>
</div>
</div>
</body>

toggling one directive effects other dircetive inside ngrepeat

I wrote an angularjs directive to show and hide ajax spinners. The visibility of the spinner is toggled by show and hide buttons whose functionality is written inside the MainController. There is a variable inside the controller which is set to true and false based on the button click. This variable is passed to the directive using isolate scope. When I try to toggle one spinner, all the other spinners are also visible. How can I change my code to only toggle the particular spinner.
https://plnkr.co/edit/AFmBVbHaBPk66T7UjPC5?p=preview
// Code goes here
angular.module('app',[])
.controller('MainController',[MainController])
.directive('loadingDirective',[loadingDirective]);
function MainController(){
var mc = this;
mc.showMe = showMe;
mc.hideMe = hideMe;
mc.loading = false;
function showMe(){
mc.loading = true;
}
function hideMe(){
mc.loading = false;
}
}
function loadingDirective() {
return {
restrict: 'E',
replace:true,
scope:{
loading:"=loading"
},
template: '<span class="spinner">Loading…</span>',
link: function (scope, element, attr) {
scope.$watch('loading', function (val) {
if (val)
$(element).show();
else
$(element).hide();
});
}
};
}
/* Styles go here */
.spinner {
position: relative;
/* [1] */
display: inline-block;
width: 1em;
/* [2] */
height: 1em;
/* [2] */
font-size: 32px;
/* [3] */
border-bottom: 1px solid;
/* [4] */
vertical-align: middle;
overflow: hidden;
/* [5] */
text-indent: 100%;
/* [5] */
-webkit-animation: 0.5s spinner linear infinite;
animation: 0.5s spinner linear infinite;
/**
* 1. Make the spinner a circle.
*/
/**
* The (optically) non-spinning part of the spinner.
*
* 1. Border around entire element fills in the rest of the ring.
* 2. Paler than the part that appears to spin.
*/
}
.spinner, .spinner:after {
border-radius: 100%;
/* [1] */
}
.spinner:after {
content: "";
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
border: 1px solid;
/* [1] */
opacity: 0.5;
/* [2] */
}
/**
* Size variants (built by adjusting `font-size`).
*/
.spinner--small {
font-size: 16px;
}
.spinner--large {
font-size: 64px;
}
/**
* Color overrides.
*/
.spinner--light {
color: #fff;
}
.spinner--dark {
color: #333;
}
#-webkit-keyframes spinner {
to {
-webkit-transform: rotate(360deg);
}
}
#keyframes spinner {
to {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
<!DOCTYPE html>
<html ng-app="app">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
</head>
<body>
<h1>Hello Plunker!</h1>
<div ng-controller="MainController as mc">
<div ng-repeat="i in [1,2,3,4,5]">
<loading-directive loading="mc.loading"></loading-directive>
<button ng-click="mc.showMe()">show</button>
<button ng-click="mc.hideMe()">hide</button>
</div>
</div>
</body>
</html>
If you want the spinners to have their own states, then they should be controlled by different variables.
In your example it is achievable by using an array to hold the variables
<div ng-repeat="i in [1,2,3,4,5]">
<loading-directive loading="mc.loading[i]"></loading-directive>
<button ng-click="mc.show(i)">show</button>
<button ng-click="mc.hide(i)">hide</button>
</div>
mc.loading = {};
function show(i){
mc.loading[i] = true;
}
function hide(i){
mc.loading[i] = false;
}
In a more real case example where you have some data and you use ng-repeat over them, you should assign the loading states inside the elements themselves.
This is a common technique to assign state to each items in ng-repeat
mc.fruits = [
{name:"apple"},
{name:"orange"},
{name:"starfruit"}
]
function load(fruit) { fruit.loading = true; }
function noLoad(fruit) { fruit.loading = false; }
<div ng-repeat="fruit in mc.fruits">
<loading-directive loading="fruit.loading"></loading-directive>
{{fruit.name}}
<button ng-click="mc.load(fruit)">show</button>
<button ng-click="mc.noLoad(fruit)">hide</button>
</div>
Working Plunkr: https://plnkr.co/edit/peGDxYJzKJgiHuPp4zmQ
You needed to define the isolated scope in the directive correctly. Essentially, your directive was still dependent on the controller as you were using the same variable mc.loading to determine the state of all directive instances.
By moving the deterministic variable $scope.loading as well as the buttons inside the directive, we are completely isolating each directive instance and making them all completely independent units.
HTML:
<div ng-controller="MainController as mc">
<div ng-repeat="i in [1,2,3,4,5]">
<loading-directive></loading-directive>
</div>
</div>
JS:
angular.module('app',[])
.controller('MainController',[MainController])
.directive('loadingDirective',[loadingDirective]);
function MainController(){
}
function loadingDirective() {
return {
restrict: 'E',
replace:true,
scope:{},
template: '<div><span ng-if="loading" class="spinner">Loading…</span>'
+ '<button ng-click="showMe()">show</button>'
+ '<button ng-click="hideMe()">hide</button></div>',
controller: function($scope) {
$scope.showMe = showMe;
$scope.hideMe = hideMe;
function showMe(){
$scope.loading = true;
}
function hideMe(){
$scope.loading = false;
}
}
};
}
The loading variable watched is common for all the directives used, hence when the model is changed the watch condition runs 5 times in your case, removing all the spinners.
I used the index to see what is being hidden or shown,
Updated fiddle: https://plnkr.co/edit/Jjfk6v7TJZHlQicM45ln?p=preview
HTML
<div ng-repeat="i in [1,2,3,4,5]">
<loading-directive data-index="{{$index}}" loading="mc.loading" offset="mc.offset"></loading-directive>
<button ng-click="mc.showMe($index)">show</button>
<button ng-click="mc.hideMe($index)">hide</button>
</div>
Angular
angular.module('app',[])
.controller('MainController',[MainController])
.directive('loadingDirective',[loadingDirective]);
function MainController(){
var mc = this;
mc.showMe = showMe;
mc.hideMe = hideMe;
mc.loading = false;
mc.offset =-1;
function showMe(offset){
mc.loading = true;
mc.offset = offset;
}
function hideMe(offset){
mc.loading = false;
mc.offset = offset;
console.log(offset);
}
}
function loadingDirective() {
return {
restrict: 'E',
replace:true,
scope:{
loading:"=loading",
offset:"=offset"
},
template: '<span class="spinner">Loading…</span>',
link: function (scope, element, attr) {
scope.$watch('[loading, offset]' , function (val) {
if(attr.index == scope.offset || scope.offset == -1){
if (val[0])
element.show();
else
element.hide();
}
});
}
};
}
SCRIPT:
function showMe(i){
mc.loading = true;
i=true;
}
IN HTML ADD THIS
<div ng-repeat="i in [1,2,3,4,5]">
<span><loading-directive loading="mc.loading" ng-show="i==true"></loading-directive>
<button ng-click="mc.showMe(i)">show</button></span>
<button ng-click="mc.hideMe()">hide</button>
</div>

Improve Angular Tabs directive

I created an angular tab directive width JQuery.
I can't find the problem with it because all seems right.
Could someone check what am I doing wrong?
I think my Angular directive is to dependent of jQuery.
What steps should I take to make it more "Angular"?
angular.module('app', []);
angular.module("app").directive("tabs", function() {
return {
restrict: "A",
link: function(scope, element, attributes) {
var tabs = element.find("a");
var sections = links.map(function() {
return $("#" + this.href.split("#")[1])
});
$(tabs[0]).show();
sections[0].show();
tabs.bind("click", function(event) {
event.preventDefault();
tabs.not(this).hide();
$(this).siblings("a").hide();
$.each(sections, function() {
$(this).hide()
});
$("#" + this.href.split("#")[1]).show();
});
}
};
});
ul.tabs {
list-style: none;
margin: 0;
padding: 0;
}
ul.tabs li {
display: inline-block;
margin: 0;
padding: 0;
}
ul.tabs li a {
text-decoration: none;
color: white;
padding: 8px;
display: block;
background-color: indigo;
}
div.section {
background-color: #f0f0f0;
padding: 8px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<ul class="tabs">
<li>Tab 1
</li>
<li>Tab 2
</li>
</ul>
<div id="section1" class="section">Section 1</div>
<div id="section2" class="section">Section 2</div>
I'm not going to try fixing your code. Just explain how you would do this the Angular way.
jQuery is all about DOM manipulation. In Angular, DOM manipulation is bad. The DOM should automatically adapt itself based on the model. Tabs can simply be implemented the following way:
<ul class="tabs">
<li><a href ng-click="selectedTab = 1">Tab 1</a>
</li>
<li><a href ng-click="selectedTab = 2">Tab 2</a>
</li>
</ul>
<div class="section" ng-show="selectedTab === 1">Section 1</div>
<div class="section" ng-show="selectedTab === 2">Section 2</div>
This is much simpler: clicking on a tab changes the value of the selectedTab model value. The sections hide/show themselves based on the value of selectedTab.
Can be something like:
angular.module("app").directive("tabs", function() {
return {
restrict: "A",
scope: {
tabs: '='
},
template : '<ul><li ng-repeat="section in tabs" ng-click="handleClick(section)" ng-class="{selected : section.selected}">{{section.name}}</li></ul>',
link: function(scope, element, attributes) {
scope.handleClick = function(section) {
//....
}
}
};
});
where tabs is array like [{name : 'N1', href : '#smth', selected: false}, ...] in html:
<div tabs="tabs"></div>
You can add selected property, disabled, etc. Also notice - that if you change tabs array - template will redraw itself automatically.

AngularJS animation makes hidden element appear when the page is loaded

I have a hidden component in a directive and yet when the page is loaded, the hidden element appears for the duration of the animation, which should only be triggered when the component's model is set to visible.
In this example I set the component to ng-hide="true" permanently, and when the page is loaded it still appears for half a second. In my real program the directive is much more complicated, so I placed the template in its own file, the problem doesn't appear if I just put it in a string.
I tried adding style="display:none" to the template's content, but then it doesn't react to model changes later.
<html ng-app="myApp">
<head>
<style>
.overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: black;
transition: all linear 0.5s;
}
.overlay.ng-hide {
opacity: 0;
}
</style>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.18/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.18/angular-animate.min.js"></script>
<script>
angular.module("myApp", ['ngAnimate'])
.directive("overlay", function() {
return {
replace: true,
templateUrl: "overlay.html"
};
});
</script>
</head>
<body>
<overlay></overlay>
</body>
</html>
overlay.html:
<div class="overlay" ng-hide="true"></div>
Plunker: http://plnkr.co/edit/tYLkGwPJtFPxH2ES6qIe?p=preview
You could do the opposite using ng-class instead. Switch your CSS so the overlay only shows when you add a class show:
.overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: black;
opacity: 0;
transition: all linear 0.5s;
}
.overlay.show {
opacity: 1;
}
Then just use the ng-class directive on your overlay, change the false value to whatever expression you have in mind.
<div class="overlay" ng-class="{show : false}"></div>
Here's the Forked Plunker
You can add a link function that adds a display: none property to the overlay element temporarily and then removed afterwards when the ng-hide directives kicks in by watching the ng-hide initial value and then removing the display: none property and deregistering the watcher.
Something like this:
FORKED PLUNKER
.directive("overlay", function() {
return {
templateUrl: "overlay.html",
link: function(scope, elem, attr) {
elem.css('display', 'none');
var dereg = scope.$watch(attr.ngHide, function(value) {
elem.css('display', '');
dereg();
});
}
};
});
Note that I have removed the replace property, it is depreciated in angular 3.0 and above. Reference

Resources