fetch the height of an element - angularjs

I'm writing a pagination function in an angular app. I have got a <div> container, that has a size depending on the window size. In that container, there is my list. Now this list has to be split into n pages to avoid having a scrollbar. Everything works fine yet, except for the fact, that I have to hardcode the height of a list element.
What I need to calculate the number of pages for the pagination is:
The length of the list (number of list items),
the height of the container (refreshed on each window.onresize) and
the height of one list element.
I am able to keep track of the list length. I am also able to fetch the height of the container and to refresh that value on window.onresize.
But I am not able to fetch the height of a list element in the DOM tree.
I've written a directive, to pass the scope and selector names from a template into my code:
<div class="multicolumn-list" ng-controller="mymodule.listtypes.multicolumn.controller">
<pagination-updater space-selector=".list-spacer" item-selector="li:first" page-size-model="listmodel.pageSize" listen="listmodel.list.items">
<div class="list-spacer">
<ul>
<li ng-repeat="item in listmodel.pageItems">
<a ng-click="mymodule.selectItem(item)">{{item.id}} ({{item.type}})</a>
</li>
</ul>
</div>
</pagination-updater>
</div>
My directive looks like this right now:
'use strict';
angular.module('shared.directives.paginationUpdater', ['shared.services.dimensions']);
angular.module('shared.directives.paginationUpdater').directive('paginationUpdater', function() {
return {
restrict: 'E',
link: {
'post': function (scope, iElement, iAttrs, controller) {
var pageSizeModelName = iAttrs.pageSizeModel;
var setPageSize = function () {
var spaceElement = iElement.find(iAttrs.spaceSelector);
var itemElement = iElement.find(iAttrs.itemSelector);
console.log(itemElement.height());
};
scope.$watch('$parent.' + iAttrs.listen, function () { setPageSize(); }, true);
scope.$on('windowResized', function () { setPageSize(); });
setPageSize();
}
}
};
});
When I run my application, the list is empty, so there is no <li> and itemElement.height() returns 0. Then my list becomes updated, then the setPageSize() will be called again, but itemElement.height() still returns 0. I think it is, because this code is executed before the list will be rendered into the page. Do you have any idea, how I can archive this? Right now, I have the height of an element hardcoded and the best compromise I see is to pass the height value from the template to the directive:
<div class="multicolumn-list" ng-controller="mymodule.listtypes.multicolumn.controller">
<pagination-updater space-selector=".list-spacer" item-height="{{main.zoomFactor}} * 26 + 1" page-size-model="listmodel.pageSize" listen="listmodel.list.items">
<div class="list-spacer">
<ul>
<li ng-repeat="item in listmodel.pageItems">
<a ng-click="mymodule.selectItem(item)">{{item.id}} ({{item.type}})</a>
</li>
</ul>
</div>
</pagination-updater>
</div>
But then still I have to change the templates if we have a change in the style sheets. I also don't want to write a timeout. Maybe there is an event angular fires after updating the DOM tree?

A little hack you could do to get around this is to have a 'dummy' list item on load. Unfortunately it does need to be visible at render time. So you could do something like this, that way you have a <li> Item rendered and the should be enough to get it to have some height without any content being visible to the user while your data is being fetched.
<ul>
<li class="dummy-item"
ng-show="!listMode.pageItems.length"><a> </a></li>
<li ng-repeat="item in listmodel.pageItems">
<a ng-click="mymodule.selectItem(item)">{{item.id}} ({{item.type}})</a></li>
</ul>
I know this is an old question but hope it still helps.

Related

Removing element from ng-repeat does not allow to remove or hide element from DOM

I have below code to display records using ng-repeat:
<div class="gallery">
<div ng-cloak
ng-repeat="photo in photos| orderBy:'-id'"
ng-mouseLeave = "delete_btn = !delete_btn"
ng-mouseEnter = "delete_btn = !delete_btn"
class="gallery_block"
id="photo_block_[[photo.id]]">
<span title="delete photo" ng-show="delete_btn" class="delete_btn_span" rel="[[photo.id]]" id="delete_photo_[[photo.id]]">
<img src="{{asset('frontend/images/cross_icon.png')}}">
</span>
<p>
<a href="javascript:;">
<img ng-click="showImagePopup([[photo.path_popup_thumbnail]]);" src={{$public_path}}./image.php?width=149&height=109&cropratio=2:1.4&image=[[photo.path_popup_thumbnail]] alt="">
</a>
{{--<span> </span>--}}
</p>
</div>
</div>
I have to delete records for that I first have to call a function using $http and then I remove element from photos array:
var index = $scope.photos.indexOf($('#photo_block_'+id));
$scope.photos.splice(index, 1);
Then I remove element from DOM:
$('#photo_block_'+id).remove();
Update
I have called deletePhoto function using jqyery like below:
$(".gallery").on('click','span.delete_btn_span',function()
{
$scope.deletePhoto($(elem), $(elem).attr('rel'));
});
deletePhoto function further do all the work of deleting elements etc.
But it does not remove element. Where as all the selectors are fine.
Is removing element from photos array causing problem?
With angular JS, you have to deal with data. So you should put an ng-click="delete(photo)" on your span.
and create a method in your controller :
$scope.delete = function(photo) {
// delete froms scope.photos here...
}
to delete the photo from the list.
Your code does not work because $scope.photos.indexOf($('#photo_block_'+id)) surely return -1, because you're looking for $('#photo_block_'+id), which is a jquery object in a list of simple json object (i suppose).
So the splice method does anything, and the $scope.photos list remain unchanged, so the view is not changed, because it's build from the list by the ng-repeat directive.
ps: you should never manipulate dom element from controller, if you need to do that, you should create directive.

List items not dragging using Sortable.js

Sortable.Js newbie here, so please be gentle.
I'm trying to implement a sortable list in my page using Sortable.js. I'm not getting any errors upon execution, however when I tried to drag my list items, they're not moving at all.
Here's my HTML:
<ul id="forcedranking" class="wizard-contents-container-ul forcedRankCls" ng-show="isForcedRankingQuestion">
<div class="answers-container">
<div class="answer-child-container">
<li ng-repeat="query in currentQuestionObject.choices | orderBy:['sequence','answer']" class="listModulePopup forcedRankingChoice" data-index="{{$index}}" ng-model="query.value">
{{query.answer}}
</li>
</div>
</div>
</ul>
And here's my JS:
/* Get current input type for Clear All checkbox activation */
$scope.currentInputType = $scope.currentQuestionObject.inputType.toLowerCase();
if ($scope.currentInputType == "forced ranking") {
$scope.isForcedRankingQuestion = true;
/* SORTABLE FOR FORCED RANKING */
var mySort = document.getElementById("forcedranking");
// forcedranking is my id for the <ul>
var sortable = Sortable.create(forcedranking, {});
// Tried setting this because I thought this was the culprit. Turns out it's not.
sortable.option("disabled", false);
I called my Sortable.js like so (underneath my angularJS libraries):
<script type="text/javascript" src="content1/carousel/Sortable.js"></script>
Overall, I think Sortable's a really neat library, which is why I want to make it work so bad. But I just don't know what I'm doing wrong here.
The problem is you are not following the sortable angular documentation. They recently change the project and separated the js from the frameworks.
So first you need to include the lib and the angular sortable lib angular-legacy-sortablejs
Sortable.js
ng-sortable.js
Second inject the module 'ng-sortable' in your app
Then you can pass the options (if you need to) in the html via directive or use it in the controller
HTML:
<ul ng-sortable="{group: 'foobar'}">
<li ng-repeat="query in currentQuestionObject.choices">{{query.answer}}</li>
</ul>
Or you can pass an object declared in your controller with the options, ex:
$scope.sortableConfig = {
group: 'collection',
animation: 150,
handle: '.handle',
filter: '.inbox'
}
<ul ng-sortable="sortableConfig">
<li ng-repeat="collection in test">
<div class="handle"></div>
{{collection.name}}
</li>
</ul>
Hope this help you!
I think what's going on here is that your JS is being executed before Angular is done rendering all the LI items, but it's hard to tell from your snippet of JS.
Just for testing, see if the items become draggable after 1 second if you change these two lines:
/* SORTABLE FOR FORCED RANKING */
var mySort = document.getElementById("forcedranking");
// forcedranking is my id for the <ul>
var sortable = Sortable.create(forcedranking, {});
into this:
window.setTimeout(function() {
/* SORTABLE FOR FORCED RANKING */
var mySort = document.getElementById("forcedranking");
// forcedranking is my id for the <ul>
var sortable = Sortable.create(forcedranking, {});
}, 1000);
Also, I don't believe that <div> elements are valid children for <ul> elements, so that may also be your problem. If the Javascript change does nothing, then perhaps you should try to change your html so that your <li> items are direct children of the <ul> element.
Hope this helps.

Hide/show an element in Angularjs using custom directive, ng-show and $scope

When a link is clicked in the app navigation a dropdown with ui-view content shows below each respective link.
The HTML:
<div class="sc-dash-header">
<ul>
<li>
<a class="navbar-brand" show-nav-popup href="">download</a>
<div id="nav-download-progress" class="dash-hdr-popup" ng-show="showPopup">
<div ui-view="hdr-download-progress"></div>
</div>
</li>
<li>
<a class="navbar-brand" show-nav-popup href="">add</a>
<div id="nav-add" class="dash-hdr-popup" ng-show="showPopup">
<div ui-view="hdr-add-content"></div>
</div>
</li>
<li>
<a class="navbar-brand" show-nav-popup href="">enter pin</a>
<div id="nav-unlock" class="dash-hdr-popup" ng-show="showPopup">
<div ui-view="hdr-unlock"></div>
</div>
</li>
</ul>
</div>
I've included an ng-show attribute to open the dropdown when $scope.showPopup is set to true.
To achieve this I've created a custom directive with an on click called show-nav-popup.
The JS:
.directive('showNavPopup', function () {
return {
restrict: 'A',
// scope: {},
link: function(scope, el, attrs) {
el.on('click', function(){
scope.$apply(function () {
scope.showPopup = true;
});
console.log(scope);
});
}
};
});
The above works, but the dropdown opens on each element.
Question: I need to isolate the scope, so on each click, only the respective dropdown appears. I uncomment the line // scope: {} - but this doesn't work..
Angularjs n00b here - any help would be much appreciated
Having an isolate scope in this situation wouldn't fix the problem. There are a ton of ways to achieve what you want though. One of which is to assign each show-popup-nav an id, turn $scope.showPopup into an array, and keep an individual true/false for each id. Then for each ng-show, you look at the index corresponding to each id for the true/false value.
I coded it up on that guy's Plunker, working as you expect: http://plnkr.co/edit/CSikLIiuPNT9dfsfZfLk
EDIT: I should say, you COULD use an isolate scope to fix this, but that would require a lot of changes to your DOM, as the ng-show directive is a sibling to your show-popup-nav, and not a child.
When you create the isolate scope, the scope applies to the element that your directive is applied to, and it's child elements. In this case that's just the anchor tag:
<a class="navbar-brand" show-nav-popup href="">download</a>
You are using an ng-show on a tag that is a sibling to the anchor tag:
<div id="nav-download-progress" class="dash-hdr-popup" ng-show="showPopup">
The sibling is not part of the isolate scope, and so it never notices that the value of showPopup has changed.
The ng-show would work if it were applied to a DOM element that was a child of the anchor tag.
EDIT
One way to make this work would be to wrap your two siblings in a parent tag, and use the directive on the parent:
<div show-nav-popup>
Download
<div ng-show="showPopup"></div>
</div>
Then you'd need to modify your directive's code to find the anchor tag and apply the click handler.
You might instead try a completely different approach as suggest in the other answer by #Bill Bergquist

How do I get the clicked element's info (from the scope) in a given AngularJS directive?

Well, let me describe my original problem that's being aroused time to time. Consider the following product.html page:
<div data-ng-controller="productsCtrl" data-ng-init="getProducts()" class="row-fluid">
<div class="span12">
<div data-ng-repeat="section in sections">
<h2>{{section.name}}</h2>
<div class="products-container">
<div data-ng-repeat="product in section.products">
<img alt="{{product.name}}" class="product-img" data-zoomable-image="/path_to_large_img_or_empty_if_no_image_to_zoom" data-ng-src="/path_to_thumbnail_img">
<div class="product">
<!-- Product Info -->
</div>
</div>
</div>
</div>
</div>
</div>
The productsCtrl controller is obviously responsible to get the products information, one of which is the thumbnail / large image url of the product. The idea is to create a directive that controls the img element based on "data-zoomable" attribute on a given image. If the attribute's value is empty, there's no image to be displayed; Otherwise, a modal box should be opened to display the image.
So far, so good. So let me show the zoomableImage directive:
app.directive('zoomableImage', function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var zoomIn = element.wrap('').click(function (e) {
var href = element.data('zoomableImage');
var modal = $('#zoomInModal');
//Here, I need to pass the "product's name" to the zoomInCtrl's scope.
//This can be done using the following 3 lines of code...
var s = modal.scope();
s.product = 'The clicked element\'s name obtained from the DOM "element"';
s.$apply();
//I believe that those 3 above lines suck, since I'm not supposed to
//set the scope of another controller in a directive. Right?
modal.prependTo('body').modal('show');
e.preventDefault();
});
$('<span class="label label-info">Zoom In</span>').insertAfter(zoomIn);
}
};
});
So, the question is that what's the correct way of passing the "clicked" product's Name, Part#, and Image URL to the dialog I'm going to open above? Please note that the ProductsCtrl returns an array of section, each section contains multiple products and so on...
Of course, I can put the name, part# and other related information in different data attributes of the img element, but I do believe this duplicates the information that's already there in the products controller.
Thanks.
You can pass the reference of your product objects(s) to the directive by adding this to the directive:
scope: {
product: '='
}
The object is then shared between your controller and your directive instance.
You have to add the attribute in your html too:
<img data-product="product" data-zoomable-image="..." ... >
EDIT:
Modal should be a service. Checkout the angular-bootstrap modal.

Angular directives collaboration

So here I am trying to implement an autocomplete suggestion with angular and I need your expertise.
Here is the html:
<div my-autosuggest>
<input type="text" my-autosuggest-input>
<ol>
<li ng-repeat"item in items" my-autosuggest-list>...</li>
</ol>
</div>
I don't want to use templates to generate <li> elements. (I want it to be flexible to use any kind of element in any order and maybe some other extra elements in between for the list and dropdown)
The hard part is to respond to arrow keys on the input to highlight the next/prev element in the list. How do I let the other directive my-autosuggest-list know that it should select the next element from my-autosuggest-input directive.
Note that I might have multiple autosuggests in one controller like this:
<div ng-controller="MyController">
<div my-autosuggest>
<input type="text" my-autosuggest-input>
<ol>
<li ng-repeat"item in items" my-autosuggest-list>...</li>
</ol>
</div>
<div my-autosuggest>
<ol>
<li ng-repeat"item in items" my-autosuggest-list>...</li>
</ol>
<input type="text" my-autosuggest-input>
</div>
</div>
So far I have tried to $watch for an index change but sometime the watch wont be called for some elements in the list (maybe it's a bug).
$broadcast won't work because the input might be wrapped in another controller or inside another element so the broadcast won't be heard by the list.
I also tried to put a variable for each autosuggest in the root scope but the order in which the directives are called in not always from parent to child so I cannot initialize that variable and create a new one each time my-autosuggest is called because my-autosuggest-input or others might be called before that.
Any suggestion on how to design this with angular is appreciated.
Assuming that my-autosuggest-input and my-autosuggest-list are always inside a my-autosuggest. You could use a series of $emitted and $broadcast events to accomplish this.
Inside the my-autosuggest-input's link function add a $emit when the arrow key is pressed:
element.on('keyup', function () {
// Figure out if this is an arrow key, if so:
$scope.$emit('listSelect', { message: 'prevItem' }); // Or 'nextItem'
});
Inside the my-autosuggest link function or controller add:
// Add a controllerId, so that the listener doesn't handle its own events.
$scope.controllerId = Math.random().toString();
$scope.$on('listSelect', function (e, data) {
if ($scope.controllerId !== data.controllerId) {
$scope.$broadcast('listSelect', {
controllerId: $scope.controllerId,
message: data.message
});
}
});
Finally, inside my-autosuggest-list's link function or controller add:
$scope.$on('listSelect', function (e, data) {
if (data.message === 'nextItem') {
// Highlight next item.
} else {
// Highlight previous item.
}
});
In this scenario my-autosuggest routes any listSelect events from any of its children to all of its children.

Resources