I have an input box which accepts the value for div width.
Using angular.js, How can you change div's width based on user input?
I have implemented following code after referring this fiddle.http://jsfiddle.net/311chaos/vUUf4/4/
Markup:
<input type="text" id="gcols" placeholder="Number of Columns" ng-model="cols" ng-change="flush()"/>
<div getWidth rowHeight=cols ng-repeat="div in divs">{{$index+1}} aaaaaa</div>
controller.js
var grid = angular.module('gridApp', []);
grid.controller('control', ['$scope', function ($scope) {
/* code for repeating divs based on input*/
$scope.divs = new Array();
$scope.create=function(){ //function invoked on button's ng-click
var a = $scope.cols;
for(i=0;i<a;i++)
{
$scope.divs.push(a);
}
alert($scope.divs);
};
}]);
grid.directive('getWidth', function () {
return {
restrict: "A",
scope: {
"rowHeight": '='
},
link: function (scope, element) {
scope.$watch("rowHeight", function (value) {
console.log(scope.rowHeight);
$(element).css('width', scope.rowHeight + "px");
}, false);
}
}
});
function appCtrl($scope) {
$scope.cols = 150; //just using same input value to check if working
}
UPDATE
My ultimate goal is to set width of that div. When I call inline CSS like following I achieve what I want.
<div get-width row-height="{{cols}}" ng-repeat="div in divs" style="width:{{cols}}px">{{$index+1}} aaaaaa</div>
But this wouldn't be always efficient if number of divs goes high. So, again question remains, how to do this via angular script?
Use ng-style="{width: cols + 'px'}", instead of style.
link to the docs: http://docs.angularjs.org/api/ng.directive:ngStyle
You have an error in yor angular syntax. You Should use
ng-repeat="div in divs track by $index"
Also you should use attrs.$observe instead of $watch.
Working demo (updated): http://jsfiddle.net/nidzix/UKHyL/40/
Related
I have an AngularJS project (I don't use JQuery) where I need to display a table with users and load more as the user scrolls near the end of the page. I'm trying to implement this without relying on external libraries, since this is part of my requirements.
I've checked several examples like this, this and this.
But so far, none of the examples I've checked have helped me implement the infinite scrolling as I expected. They use different ways to calculate when to trigger the call, and some of this values return undefined to me (i.e. people say clientHeight and scrollHeight have different values, but to me it's always the same one, the total height including scrolling space).
I created a directive like the following:
usersModule.directive('whenScrollEnds', function($window) {
return {
restrict: "A",
link: function(scope, elm, attr) {
angular.element($window).bind('scroll', function() {
var hiddenContentHeight = elm[0].scrollHeight - angular.element($window)[0].innerHeight;
if ((hiddenContentHeight - angular.element($window)[0].scrollY) <= (10 )) {
angular.element($window)[0].requestAnimationFrame(function(){
scope.$apply(attr.whenScrollEnds);
})
}
});
}
};
});
This kind of works but there's the following problems/doubts which I hope someone can explain to me:
It triggers too fast. I want the loading to trigger when I'm near the bottom of the scrollable space, like near 90% or so.
The scrollHeight is only accesible through elm[0], angular.element($window)[0] has no scrollHeight property so it returns undefined, and elm[0] has no scrollY value.
The scrollY value I get is the distance the scrollbar has moved from the top, minus the scrollbar length, but I feel like that value is wrong.
Is binding the scroll event through angular.element($window).bind the right decision?
How can I implement a proper infinite scrolling table? Am I using the correct variables? Please provide an answer that uses Javascript and AngularJS only, JQuery or libraries solutions won't help me.
After checking several examples and trying to avoid those that used JQuery as part of the solution I found a way to make this work.
First my directive that would handle when the scroll ends:
usersModule.directive('whenScrollEnds', function($window) {
return {
restrict: "A",
link: function(scope, elm, attr) {
var raw = elm[0];
raw.addEventListener('scroll', function() {
if ((raw.scrollTop + raw.clientHeight) >= (raw.scrollHeight )) {
scope.$apply(attr.whenScrollEnds);
}
});
}
};
});
My view has the table inside a div like this:
<div id="usersScrollable" when-scroll-ends="uc.loadMoreUsers()">
<table id="tableUsers" class="table" ng-show="uc.users.length" >
<thead>
<tr>
<th>Email</th>
<th>Nombre Completo</th>
<th>Estado</th>
<th>Acción</th>
</tr>
</thead>
<tbody >
<tr ng-repeat="u in uc.users">
<td>{{u.email}}</td>
<td>{{u.fullName}}</td>
<td>{{uc.getStateString(u.active)}}</td>
<td><button type="button" class="btn btn-primary" ng-click="uc.edit(u)">Editar</button></td>
</tr>
</tbody>
</table>
</div>
Make sure the div that contains the table is the one listening to when the scrolling ends. This div has to have a set height, if it doesn't then the clientHeight property will be the same as scrollHeight (not sure what would happen if there's no height defined explicitly, like instead of setting the height you set the top or bottom properties).
In my controller, loadMoreUsers() is in charge of incrementing the page (in case there's more users, each call I get has the total of users so I can know before making another request how many users I have left), also it calls the function that makes the request to the web service.
The problem with the solution provided by Uriel Arvizu is that if the raw element is empty because it is waiting for a Http Request at the page load, all the following raw.scrollTop, raw.clientHeight) and raw.scrollHeight will have wrong dimensions and the scroll is no working anymore.
I would suggest this other solution that basically adds the scroll event to the $window without cacheing it, so it is always sized correctly when the Http response is received.
(function () {
'use strict';
angular.module('ra.infinite-scroll', []).directive('infiniteScroll', function ($window) {
return {
restrict: 'A',
scope: {
infiniteScrollCallbackFn: '&'
},
link: function (scope, elem, attrs) {
var percentage = (attrs.infiniteScrollPercentage !== undefined ? (attrs.infiniteScrollPercentage / 100) : '.9');
var $element = elem[0];
angular.element($window).on('scroll', function () {
if ((this.scrollY + this.innerHeight - $element.offsetTop) >= ($element.scrollHeight * percentage)) {
scope.$apply(scope.infiniteScrollCallbackFn);
}
});
}
};
});
})();
Also, with this module, you can pass 2 parameters in the HTML: a callback function and a percentage of the specific element (where the module is applied) that when it is reached the callback function is called, i.e. to repeat the Http request (default is 90% of that element).
<div infinite-scroll infinite-scroll-callback-fn="callBackFunction()" infinite-scroll-percentage="80">
// Here you may include the infinite repeating template
</div>
Using example code of Ferie, yet I had to use mousewheel event instead of scroll and had to use elem.scrollTop() instead of elem.scrollY
app.directive('infiniteScroll', function ($window) {
return {
restrict: 'A',
scope: {
infiniteScrollCallbackFn: '&'
},
link: function (scope, elem, attrs) {
var percentage = (attrs.infiniteScrollPercentage !== undefined ? (attrs.infiniteScrollPercentage / 100) : '.9');
var $element = elem[0];
angular.element($window).on('mousewheel', function () {
if ((elem.scrollTop() + this.innerHeight - $element.offsetTop) >= ($element.scrollHeight * percentage)) {
scope.$apply(scope.infiniteScrollCallbackFn);
}
});
}
};
});
when used in Angular table with a ng-repeat on the <tr>, I had to add the directive into the parent tbody of this tr in order to capture the right element containing the scroll state.
<tbody infinite-scroll infinite-scroll-callback-fn="callBackFunction()" infinite-scroll-percentage="80">
I currently have an AngularJS application embedded in an iframe which needs to be resized to avoid scrollbars. I've got a function in the app that calculates the height of the container and then resizes the iframe.
Currently I am using a directive (resizeAppPart) which will call the resize function on the last item in the scope.
Directive:
app.directive('resizeAppPart', function () {
return function (scope, element, attrs) {
if (scope.$last) {
Communica.Part.adjustSize();
}
}
});
Layout:
<tr ng-repeat="task in filteredTasks = (tasks | filter:filters)" resize-app-part>
<td>{{task.Task_x0020_Title}}</td>
<td><span ng-repeat="user in task.assignees" ng-show="user.Title != ''">
...
</tr>
This works on the initial load but if I filter the list using any of the search boxes, the directive doesn't run so you either end up with a scrollbar or a few thousand pixels of whitespace - neither are ideal.
Is there a way to call the directive, or even the function directly, after the table is filtered?
You need to put a $watch , Use this:
app.directive('resizeAppPart', function ($timeout) {
return function (scope, element, attrs) {
scope.$watch('$last',function(){
Communica.Part.adjustSize();
}
scope.$watch(attrs.filterWatcher,function(){
$timeout(function(){
Communica.Part.adjustSize();
},100)
})
}
});
And slight change in html this way
<tr ng-repeat="task in tasks | filter:filters" resize-app-part filter-watcher={{filters}}>
I am not sure if I am doing this correctly, but I am trying to convert something was in a controller to a directive because I want to use it multiple times and just change a few values, so instead of making many huge object literals, I will have just one and just change the values passed in. I am trying to bind chartConfig, but it doesn't seem to be working. Am I doing this wrong?
Here is my directive:
app.directive('percentageSquare', function(){
return {
restrict: 'E',
scope: {
bgClass: '#',
percentage: '#',
chartConfig: '='
},
link: function(scope){
var fontSize = 80;
var percentage = scope.percentage || 0;
scope.chartConfig = {
options: {
chart: {
// Chart settings here
}
}
};
},
templateUrl: '/templates/dashboard/charts/PercentageChart.html'
};
});
Here is the template that the directive is using (PercentageChart.html):
<div class="drop-shadow">
<div class="row">
<div class="col-sm-12">
<highchart config="chartConfig" class="{{bgClass||''}}" ng-show="true"></highchart>
</div>
</div>
</div>
Here is how I am calling the directive:
<percentage-square bg-class="bg-orange" percentage="23"></percentage-square>
Now my chartConfig no longer binds to the directive like it used to when it was in a controller. What can be done to fix this?
Edit
I have gotten a little further, this seems to work:
scope.$watch(scope.chartConfig, function(){
scope.chartConfig = {
// Chart Settings
};
});
But it seems to load the chart twice, as I get two animations.
Looks like what I want to do is watch chartConfig. it must also be wrapped in a string in order for it to work properly. Using scope.chartConfig loaded the chart twice, while 'chartConfig' loads the chart once and properly.
scope.$watch('chartConfig', function(){
scope.chartConfig = {
// Chart Settings
};
});
I have broken this problem down into it's simplest form. Basically I have a directive that, for the demo, doesn't yet really do anything. I have a div with the directive as an attribute. The values within the div, which come from an object array, are not displayed. If I remove the directive from the div, they are displayed OK. I am clearly missing something really obvious here as I have done this before without any problems.
Here's the Plunk: http://plnkr.co/edit/ZUXD4qW5hXvB7y9RG6sB?p=preview
Script:
app.controller('MainCtrl', function($scope) {
$scope.tooltips = [{"id":1,"warn":true},{"id":2,"warn":false},{"id":3,"warn":true},{"id":4,"warn":true}];
});
app.directive("cmTooltip", function () {
return {
scope: {
cmTooltip: "="
}
};
});
HTML
<div ng-repeat="tip in tooltips" class="titlecell" cm-tooltip="true">
A div element: {{ tip.id }}
</div>
<br><br>
Just to prove it works without the directive:
<div ng-repeat="tip in tooltips" class="titlecell">
A div element: {{ tip.id }}
</div>
There is a hack to make it working in earlier versions of angular by making use of transclusion, like that:
app.directive("cmTooltip", function () {
return {
scope: {
cmTooltip: "="
},
transclude: true,
template : '<div ng-transclude></div>'
};
});
PLNKR
As by Beyers' comment above and below, the behaviour the question is about no longer exists in at least 1.2.5
To be clearer; this has nothing to do with ng-repeat, you can remove it and there still will be no tip ( or tooltips ).
See this question on what the = and other configs mean and what it is doing for you.
Basically for your situation when you use = the scope of the directive will be used in the underlying elements, you no longer have your controller's scope. What this means for you is that there is no {{ tip.id }} or not even tip. Because the directive doesn't supply one.
Here's a plunker that demonstrates what you can do with it.
Basically all i did was
app.directive("cmTooltip", function () {
return {
scope: {
cmTooltip: "="
},
link: function($scope){ // <<
$scope.tip = { id: 1 }; // <<
} // <<
};
});
This creates the tip object on the scope so it has an id.
For your situation you would probably just not use = and look at this question for your other options depending on what you want.
In my opinion this isn't the way to go.
I would use Objects.
JS code:
function tooltip(id,warn){
this.id = id;
this.warn = warn;
}
tooltip.prototype.toString = function toolToString(){
return "I'm a tooltip, my id = "+this.id+" and my warn value = "+this.warn;
}
$scope.tooltips = [new tooltip(1,true),new tooltip(2,false),new tooltip(3,true),new tooltip(4,true)];
HTML:
<div ng-repeat="tip in tooltips" class="titlecell">
A div element: {{ tip.toString() }}
</div>
What I am trying to do is make a function so I can change the height of my ng-grid column width. That is irrelevant besides the fact that the scope from my controller needs to communicate with the scope in my directive.
.directive('getWidth', function(){
return{
controller: 'QuotesCtrl',
link: function(scope){
scope.smRowHeight = function(the value i want){
scope.theRowHeight = the value i want;
}
}
}
})
And I just want to be able to go into my html and say hey for this div I want the height 20
<div getWidth = '20'></div>
I have looking around and I couldn't find anything doing with this exact thing. and by the way, in my QuotesCtrl i initialized the row height like so
$scope.theRowHeight;
Any suggestions?
Try something like this:
.directive('getWidth', function(){
return{
controller: 'QuotesCtrl',
link: function(scope){
console.log(scope.theRowHeight);
},
scope: {
'theRowHeight': '='
}
}
});
Markup:
<div the-row-height="20"></div>
Directives are amazing! You can pass in what is called an isolate scope, and with that you can pass in values as strings or references to your controller scope. There are 3 options on the isolate scope that you should look into. = # & See the link below the example to the docs.
Here is a working JSFiddle
.directive('getHeight', function(){
return{
scope: {
"rowHeight": '='
},
controller: 'QuotesCtrl',
link: function(scope){
scope.smRowHeight = function(the value i want){
scope.theRowHeight = the value i want;
}
}
}
})
You would need to update your html to pass in the new scope value.
<div get-height row-height='20'></div>
More Info on Directives