Get height of div in angularjs - angularjs

I have 3 div in which the data is filling from controller. and the div is dependent on dropdown select (particular div will be shown for particular dropdown value). the problem is that I am unable to get the height of that div when page is loaded and also when I changed the dropdown value. Everytime I am getting 0 height.
here is the code for html:
<div class="brand-categoryWrp">
<select ng-model="allbrandscategory" ng-change="catChange(allbrandscategory)">
<option value="all">AllBrands</option>
<option value="watches">Watches</option>
<option value="pens">Pens</option>
</select>
</div>
<div ng-show="firstdiv" allbrands-directive>
<ul>
<li ng-repeat="res in brands">
{{res.name}}
</li>
</ul>
</div>
<div ng-show="seconddiv" watches-directive>
<ul>
<li ng-repeat="res in brands1">
{{res.name}}
</li>
</ul>
</div>
<div ng-show="thirddiv" pens-directive>
<ul>
<li ng-repeat="res in brands2">
{{res.name}}
</li>
</ul>
</div>
and here is for controller:
// Code goes here
var myapp = angular.module('myModule', []);
myapp.controller('mycntrl',function($scope){
$scope.allbrandscategory = 'all';
$scope.firstdiv = true;
$scope.brands = [
{name: 'Adidas'},
{name: 'Armani'}
];
$scope.brands1 = [
{name: 'Adidas1'},
{name: 'Armani1'},
{name: 'Fossil'}
];
$scope.brands2 = [
{name: 'Adidas2'},
{name: 'Armani2'},
{name: 'Mont blanc'},
{name: 'calvin'}
];
$scope.catChange = function(val){
$scope.firstdiv = false;
$scope.seconddiv = false;
$scope.thirddiv = false;
if(val == 'all')
{
$scope.firstdiv = true;
}
else if(val == 'watches')
{
$scope.seconddiv = true;
}
else if(val == 'pens')
{
$scope.thirddiv = true;
}
};
});
myapp.directive('pensDirective', function($timeout) {
return {
restrict: 'A',
link: function(scope, element) {
//console.log(element);
console.log("pens: "+element[0].offsetHeight);
}
};
});
myapp.directive('watchesDirective', function($timeout) {
return {
restrict: 'A',
link: function(scope, element) {
// console.log(element);
console.log('watches: '+element[0].offsetHeight);
}
};
});
myapp.directive('allbrandsDirective', function($timeout) {
return {
restrict: 'A',
link: function(scope, element) {
//console.log(element);
console.log("brand: "+element[0].offsetHeight);
}
};
});
Here is the plunker

The link function is executed before the model data is bound to the view (the div does not contain any child elements when you're requesting the offsetHeight. Try wrapping it into a $timeout(function() { /* ... */ }), which will execute at the end of the current digest cycle.

Check out AngularJs event to call after content is loaded
This has a nice answer to your problem and seems to work. Second answer down.
I tested on yours by adding the below to your allbranddirective:
element.ready(function(){
console.log("brand: "+element[0].offsetHeight);
});
EDIT:
HTML:
<html>
<div class="brand-categoryWrp">
<select ng-model="divtoshow" ng-change="catChange(divtoshow)">
<option value="allbrands-directive">AllBrands</option>
<option value="watches-directive">Watches</option>
<option value="pens-directive">Pens</option>
</select>
</div>
<div allbrands-directive><ul><li ng-repeat='res in brands'>{{res.name}}</li></ul></div>
JS:
var myapp = angular.module('myModule', []);
myapp.controller('mycntrl',function($scope){
$scope.brands = [{name: 'Adidas'}, {name: 'Armani'}];
$scope.divtoshow = 'allbrands-directive'
$scope.catChange = function(val){
$scope.divtoshow = val;
if (val == 'allbrands-directive'){
$scope.brands = [{name: 'Adidas'}, {name: 'Armani'}];
}else if (val == 'watches-directive') {
$scope.brands = [{name: 'Adidas1'},{name: 'Armani1'},{name: 'Fossil'}];
}else if (val == 'pens-directive') {
$scope.brands = [{name: 'Adidas2'},{name: 'Armani2'},{name: 'Mont blanc'},{name: 'calvin'}];
}
}
});
myapp.directive('allbrandsDirective', function($timeout) {
return {
restrict: 'A',
link: function(scope, element) {
scope.$watch('brands', function(){
console.log("brand: "+element[0].offsetHeight);
});
}
};
});
So I have changed your code a bit, I have set up only one directive and in here I watch on a change of the brands variable, when this changes we then get the height of the element.
See plunker - http://plnkr.co/edit/vZhn2aMRI6wKn3yVharS?p=preview
If you need each section in separate directives see nulls answers

Related

How to count the number of elements selected in a directive?

If you have a career-box directive like this:
<div ng-repeat='career in careers' career-box ng-click="toggleSelected()"> {{ career.name }} </div>
The directive:
app.directive('careerBox', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
scope.selected = false,
scope.toggleSelected = function() {
element.toggleClass('selected');
if(scope.selected) {
scope.selected = false;
} else {
scope.selected = true;
}
}
}
};
});
Every time a career-box is selected I would like to count the total number of career-boxes selected (selected=true) and enable a button if they are more than 0.
How can I do that in the code?
There is no need to write a custom directive. It can be done with Angular core directives:
vm.selectedCount = function(item) {
var count=0;
for (let i=0; i<vm.careers.length; i++) {
if (vm.careers[i].selected) count++;
}
return count;
}
<div ng-repeat='career in careers'
ng-click="career.selected = !career.selected"
ng-style="{'background-color': career.selected?'yellow':''}">
{{ career.name }}
</div>
<hr>Selected count: {{selectedCount()}}
The DEMO on PLNKR
The DEMO
angular.module("myApp",[]);
angular.module("myApp").controller("myVm", function($scope) {
var vm = $scope;
vm.careers = [ {name: "Police Officer", value: 1},
{name: "Nurse", value:2},
{name: "Doctor", value:3},
];
vm.selectedCount = function(item) {
var count=0;
for (let i=0; i<vm.careers.length; i++) {
if (vm.careers[i].selected) count++;
}
return count;
}
})
<script src="//unpkg.com/angular/angular.js"></script>
<body ng-app="myApp" ng-controller="myVm">
<h1>Selected Div Example</h1>
<div ng-repeat='career in careers'
ng-click="career.selected = !career.selected"
ng-style="{'background-color': career.selected?'yellow':''}">
{{ career.name }}
</div>
<hr>Selected count: {{selectedCount()}}
</body>
First, you need to have a select element with the multiple property:
<select name="carers" multiple id="multipleCarers" ng-model="data.multipleCarers">
<option ng-repeat="career in careers" value="{{career.value}}">
{{career.name}}
</option>
</select>
After of that, you can get the count of options selected:
{{data.multipleCarers.length}}

ng-repeat with ng-bind-html as pre and post-markup

I have an array with multiple objects, similar to this:
[
{ title: 'abc', 'pre': '<div class="class1"><div class="class2">', 'post': '</div>' },
{ title: 'def', 'pre': <div class="class3">', 'post': '</div>' },
{ title: 'ghi', 'pre': '<div class="class3">', 'post': '</div></div>' }
]
<div ng-repeat="item in myVar">
<div ng-bind-html="item.pre" />{{ item.title }}<div ng-bind-html="item.post" />
</div>
The above does not work (I have to open two div's in one, and close in two other items in that array, as illustrated above). The problem is that ng-bind-html needs to be bound to an element, which I cannot use, neither does a filter work:
<div ng-repeat="item in myVar">
{{ item.pre | trust }}{{ item.title }}{{ item.post | trust }}
</div>
angular.module('myModule').filter('trust', ['$sce',function($sce) {
return function(value, type) { return $sce.trustAsHtml; }
}]);
Any ideas?
You'll have to perform the concatenation pre-view, trust that (or turn on ngSanitize, potentially better-yet), then inject it.
As far as I know, there's no way to inject a partial HTML element the way you're trying to.
In your controller:
$scope.items = [...];
for (var i = 0; i < $scope.items.length; i++) {
var e = $scope.items[i];
e.concatenated = $sce.trustAsHtml(e.pre + e.title + e.post);
}
Then in your view:
<div ng-repeat="item in items">
<div ng-bind-html="item.concatenated" />
</div>
Of course, you'll probably want ngSanitize turned on, just to avoid any issues with e.title. That is, if someone entered a title of <script>alert('ahh!')</script>, that would end up being trusted.
Your version did not work because of how ngBindHtml is written:
var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse, $compile) {
return {
restrict: 'A',
compile: function ngBindHtmlCompile(tElement, tAttrs) {
var ngBindHtmlGetter = $parse(tAttrs.ngBindHtml);
var ngBindHtmlWatch = $parse(tAttrs.ngBindHtml, function getStringValue(value) {
return (value || '').toString();
});
$compile.$$addBindingClass(tElement);
return function ngBindHtmlLink(scope, element, attr) {
$compile.$$addBindingInfo(element, attr.ngBindHtml);
scope.$watch(ngBindHtmlWatch, function ngBindHtmlWatchAction() {
// we re-evaluate the expr because we want a TrustedValueHolderType
// for $sce, not a string
element.html($sce.getTrustedHtml(ngBindHtmlGetter(scope)) || '');
});
};
}
};
}];
It injects using element.html(...), which needs a complete HTML element.

Flickity carousel: Items pushed out of viewport with ng-repeat?

Im trying to use metafizzy's flickity framework to display content dynamically, using angulars ng-repeat.
But for some reason the items seem to get pushed out from the flickity-viewport when loaded onto the DOM. Anyone know why that happens and how to avoid it?
The gallery works fine When displaying static content inside it like this;
HTML : STATIC MARKUP EXAMPLE
<div ng-controller="FlickityCtrl">
<div id="main-content" class="gallery js-gallery">
<div class="gallery-cell"> Static Title </div>
<div class="gallery-cell"> Static Title </div>
<div class="gallery-cell"> Static Title </div>
</div>
..its When trying to populate the gallery with the help of angular's ng-repeat directive,that the gallery breaks.
HTML : MARKUP USING NG-REPEAT
<div ng-controller="FlickityCtrl" >
<div id="main-content" class="gallery js-gallery">
<div ng-repeat="chapter in chapters" ng-click="loadSubchapters(chapter.title)">
<h1 class="gallery-cell cell-card-bg">
{{ chapter.title | strip_namespace }}
</h1>
</div>
</div>
<hr>
<button ng-click="loadChapters()" >Load chapters</button>
<hr>
<ul>
<li ng-repeat="chapter in subchapters">
{{ chapter.title | strip_namespace }}
</li>
</ul><br />
<hr >
</div>
JAVASCRIPT
angular.module('FlickityApp', [])
.controller('flickityCtrl', ['$scope', '$timeout', function ($scope, $timeout) {
var updateUI = function(data) {
if (!data || !data.query) { return; }
$timeout(function() {
$scope.chapters = data.query.pages;
console.log(data);
});
};
$scope.loadChapters = function() {
mw.loader.using('mediawiki.api', function() {
(new mw.Api()).get({
action: 'query',
generator: 'categorymembers',
gcmtitle: 'Category:examplepage'
}).done(function(data) {
$timeout(function() {
$scope.chapters = data && data.query ? data.query.pages : {};
});
});
});
};
$scope.loadSubchapters = function(chapterTitle) {
mw.loader.using('mediawiki.api', function() {
(new mw.Api()).get({
action: 'query',
generator: 'categorymembers',
gcmtitle: chapterTitle
}).done(function(data) {
$timeout(function() {
$scope.subchapters = data && data.query ? data.query.pages : {};
});
});
});
};
}])
.filter('strip_namespace', ['$sce', function($sce){
return function(text) {
text = text.split(":");
return text.length > 1 ? text[1] : text[0];
};
}]);
.directive('flickity', [function() {
return {
restrict: 'E',
templateUrl: 'templates/view.html',
replace: true,
scope: { chapters: '=' },
link: function(scope, elem, attrs, ctrl) {
scope.$watch('chapters', function() {
elem.flickity({
// settings
});
});
}
};
}]);
angular.element(document).ready(function() {
angular.bootstrap(document, ['FlickityApp']);
var flkty = new Flickity('.gallery');
});
Link to flickity api : http://flickity.metafizzy.co/api.htm

Issue with Popover AngularJS

I have a bunch of table rows which include inputs and buttons, namely. I would like to have a Popover display to the right of an input for a row if the value isn't matching the requirements defined. The button will also be disabled until the value of the input is correct.
Relevant HTML:
<div class="row col-md-4">
<table ng-controller="TestController" style="width: 100%">
<tr ng-repeat="element in model.InvoiceNumbers">
<td><input ng-model="element.id"
popover="Invoice must match ##-####!"
popover-placement="right"
popover-trigger="{{ { false: 'manual', true: 'blur'}[!isValidInvoice(element.id)] }}"
popover-title="{{element.id}}"/></td>
<td>{{element.id}}</td>
<td><button ng-disabled="!isValidInvoice(element.id)">Approve</button></td>
</tr>
</table>
</div>
Relevant JavaScript:
app.controller("TestController", function ($scope) {
$scope.model = {
InvoiceNumbers : [
{ id: '12-1234' },
{ id: '12-1235' },
{ id: '1234567' },
{ id: '1' },
{ id: '' }],
};
$scope.isValidInvoice = function (invoice) {
if (invoice == null) return false;
if (invoice.length != 7) return false;
if (invoice.search('[0-9]{2}-[0-9]{4}') == -1) return false;
return true;
};
});
The button gets disabled correctly on my local solution. However, I can't get the Popover to work; it behaves as if the model in its scope isn't getting updated. So, I looked through several links here (though most were from 2013 so I'd imagine a bit has changed) and their problems seemed to be solved by removing primitive binding. That didn't fix anything here. I added some console.log() lines in the function getting called from the Popover, and it was getting the correct value from the model each time. I also added a title to the Popover to show that its seeing the right value from the model.After seeing the log showing that it should be working correctly, I've run out of ideas.
The issue is element.id isn't updating dynamically within the trigger (it keeps its initial value, unlike popover-title which updates with the model). Is there something I did wrong?
Also, I've only been working with angular for a day so if you all have any suggestions on better ways to accomplish this, I'm open to suggestions.
Plunker: http://plnkr.co/edit/tiooSxSDgzXhbmIty3Kc?p=preview
Thanks
Found a solution on the angular-ui github page that involved adding these directives:
.directive( 'popPopup', function () {
return {
restrict: 'EA',
replace: true,
scope: { title: '#', content: '#', placement: '#', animation: '&', isOpen: '&' },
templateUrl: 'template/popover/popover.html'
};
})
.directive('pop', function($tooltip, $timeout) {
var tooltip = $tooltip('pop', 'pop', 'event');
var compile = angular.copy(tooltip.compile);
tooltip.compile = function (element, attrs) {
var parentCompile = compile(element, attrs);
return function(scope, element, attrs ) {
var first = true;
attrs.$observe('popShow', function (val) {
if (JSON.parse(!first || val || false)) {
$timeout(function () {
element.triggerHandler('event');
});
}
first = false;
});
parentCompile(scope, element, attrs);
}
};
return tooltip;
});
And here's the changes I made to the controller and view to make it work like I wanted in the original question:
<div class="row col-md-4">
<table ng-controller="TestController" style="width: 100%">
<tr ng-repeat="element in model.InvoiceNumbers">
<td><input ng-model="element.id"
pop="Invoice must match ##-####!"
pop-placement="right"
pop-show="{{element.showPop}}"
ng-blur="isValidInvoice($index, $event)" /></td>
<td>{{element.id}}</td>
<td><button ng-disabled="!isValidInvoice($index)">Approve</button></td>
</tr>
</table>
</div>
JavaScript:
app.controller("TestController", function ($scope) {
$scope.model = {
InvoiceNumbers: [
{ id: '12-1234', showPop: false },
{ id: '12-1235', showPop: false },
{ id: '1234567', showPop: false },
{ id: '1', showPop: false },
{ id: '', showPop: false }]
};
$scope.isValidInvoice = function ($index, $event) {
var obj = $scope.model.InvoiceNumbers[$index];
var isValid = function () {
if (obj.id === null) return false;
if (obj.id.length != 7) return false;
if (obj.id.search('[0-9]{2}-[0-9]{4}') == -1) return false;
return true;
};
if ($event != null && $event.type == "blur") obj.showPop = !isValid();
return isValid();
};
});
Plunker: http://plnkr.co/edit/5m6LHbapxp5jqk8jANR2?p=preview

AngularJS multiple selection model data binding

I am using AngularJS v1.2.0-rc.3.
I have a model y with a 1 to many relationship with model x.
Initially, I had a form for model y with a multiple select for xs, something like this:
Controller:
function test($scope) {
$scope.xs = [
{id:1, value:'value 1'},
{id:2, value:'value 2'},
{id:3, value:'value 3'}
];
$scope.y = {xs:[2]};
}
View:
<div ng-controller="test">
<select multiple ng-model="y.xs" ng-options="x.id as x.value for x in xs">
</select>
</div>
The result is an array of the selected items.
http://plnkr.co/edit/s3tvvHeyE17TVH5KNkPZ
All fine and good, but I needed to change it to a checkbox list and found I couldn't use an array anymore.
I tried using the repeater's index, like this:
<div ng-repeat="x in xs">
<input type="checkbox" ng-model="y.xs[$index]" ng-true-value="{{x.id}}"/>
{{x.value}}
</div>
but to pre-select the 2nd item for example, I needed to use this:
$scope.y = {xs: [null, '2']};
which was useless.
http://plnkr.co/edit/9UfbKF2gFLnhTOKu3Yep
After a bit of searching, it seems the recommended method is to use an object hash, like so
<div ng-repeat="x in xs">
<input type="checkbox" ng-model="y.xs[x.id]"/>
{{x.value}}
</div>
http://plnkr.co/edit/Xek8alEJbwq3g0NAPMcF
but if items are de-selected, you end up with something that looks like this:
y={
"xs": {
"1": false,
"2": false,
"3": false
}
}
so I ended up adding a watch expression to filter out the false values, like this:
$scope.$watch('y.xs', function(n) {
for (var k in n)
if (n.hasOwnProperty(k) && !n[k])
delete n[k];
}, true);
http://plnkr.co/edit/S1C1g5fYKzUZb7b0pRtp
It works but it feels unsatisfactory.
As this is such a common use case, I'm interested to know how others are solving it.
Update
Following the suggestion to use a custom directive, I came up with this solution which maintains the selection as a list.
Directive:
angular.module('checkbox', [])
.directive('checkboxList', function () {
return {
restrict: 'A',
replace: true,
scope: {
selection: '=',
items: '=',
value: '#',
label: '#'
},
template: '<div ng-repeat="item in list">' +
'<label>' +
'<input type="checkbox" value="{{item.value}}" ng-checked="item.checked" ng-click="toggle($index)"/>' +
'{{item.label}}' +
'</label>' +
'</div>',
controller: ['$scope', function ($scope) {
$scope.toggle = function (index) {
var item = $scope.list[index],
i = $scope.selection.indexOf(item.value);
item.checked = !item.checked;
if (!item.checked && i > -1) {
$scope.selection.splice(i, 1);
} else if (item.checked && i < 0) {
$scope.selection.push(item.value);
}
};
$scope.$watch('items', function (value) {
$scope.list = [];
if (angular.isArray(value)) {
angular.forEach(value, function (item) {
$scope.list.push({
value: item[$scope.value],
label: item[$scope.label],
checked: $scope.selection.indexOf(item[$scope.value]) > -1
});
});
}
}, true);
}]
};
});
View:
<div checkbox-list
selection="a.bs"
items="bs"
value="id"
label="name">
</div>
http://plnkr.co/edit/m7yH9bMPuRCg5OP2u0VX
I had to write a multi select directive myself in the past, feel free to grab it at http://isteven.github.io/angular-multi-select.
As for the data binding approach, my data structure is actually quite similar with yours, but in my approach I added one more property which represent the checkbox state.
Example, with your input above, I added "checked":
$scope.xs = [
{ id:1, value:'value 1', checked: false },
{ id:2, value:'value 2', checked: false },
{ id:3, value:'value 3', checked: false }
];
And I pass it to the directive like this:
<div
multi-select
input-model="xs"
button-label="value"
item-label="id value"
tick-property="checked" >
</div>
When you tick / untick a checkbox, the directive will modify the input model $scope.xs.checked accordingly. To achieve this, i attach a click handler to each checkbox. This handler will call a function in which I pass the checkbox object as the function parameter. This function will then synchronize the checkbox state and the model.
To get the selected / ticked checkboxes, you will only need to loop $scope.xs over where .checked === true
Example:
angular.forEach( $scope.xs, function( value, key ) {
if ( value.checked === true ) {
// Do your stuff here
}
});
Pardon my bad English, and hope this helps. Cheers.
I went the directive approach. It leaves me with a list of ids for the objects that are checked. This is a fiddle for it JSFIDDLE.
This is what my html looks like.
<div ng-app="checkbox" ng-controller="homeCtrl">
<div ng-repeat="item in list">
<input type="checkbox" checkbox-group />
<label>{{item.value}}</label>
</div>{{array}}
<br>{{update()}}
</div>
And my directive
.directive("checkboxGroup", function () {
return {
restrict: "A",
link: function (scope, elem, attrs) {
// Determine initial checked boxes
if (scope.array.indexOf(scope.item.id) !== -1) {
elem[0].checked = true;
}
// Update array on click
elem.bind('click', function () {
var index = scope.array.indexOf(scope.item.id);
// Add if checked
if (elem[0].checked) {
if (index === -1) scope.array.push(scope.item.id);
}
// Remove if unchecked
else {
if (index !== -1) scope.array.splice(index, 1);
}
// Sort and update DOM display
scope.$apply(scope.array.sort(function (a, b) {
return a - b
}));
});
}
}
});
I needed a checkbox solution that would also display values that were not in the list of possible choices (what if that list had changed since the user filled out the form?).
Also, I didn't think a watch function was necessary.
Finally, I needed to pass in the form in so I could set it to dirty if the user had toggled any of the checkboxes.
Caveat: In my case I only needed an array of strings as my model, which isn't exactly what the poster was using for his model.
HTML:
<multiple-checkbox
form="form"
selections="model"
options="options">
</multiple-checkbox>
Directive:
.directive("multipleCheckbox", function () {
return {
restrict: 'E',
scope: {
form: '=',
selections: '=',
options: '=',
},
template:
'<div>' +
'<div class="checkbox" ng-repeat="item in options">' +
'<label>' +
'<input type="checkbox" value="{{$index}}" ng-checked="selections.indexOf(item) > -1" ng-click="toggle(item)"/>' +
'{{item}}' +
'</label>' +
'</div>' +
'<div class="checkbox" ng-repeat="item in selections" ng-if="options.indexOf(item) === -1">' +
'<label>' +
'<input type="checkbox" value="{{options.length + $index}}" ng-checked="selections.indexOf(item) > -1" ng-click="toggle(item)"/>' +
'<strong>Other: </strong>{{item}}' +
'</label>' +
'</div>' +
'</div>',
controller: ['$scope', function ($scope) {
// Executed when the checkboxes are toggled
$scope.toggle = function (item) {
// set the form to dirty if we checked the boxes
if ($scope.form) $scope.form.$dirty = true;
// get the index of the selection
var index = -1;
if (angular.isArray($scope.selections)) {
index = $scope.selections.indexOf(item);
}
// if it's not already an array, initialize it
else $scope.selections = [];
if (index > -1) {
// remove the item
$scope.selections.splice(index, 1);
// if the array is empty, set it to null
if ($scope.selections.length === 0) {
$scope.selections = null;
}
}
else if (index < 0) {
// add the item
$scope.selections.push(item);
}
};
}],
};
});

Resources