I'm using an ng-repeat to create items. I would like to determine the height of each element that is created by using a function.
I know how to pass the index of the element that is created by ng-repeat to the function that should determine the height, but I'm getting stuck in actually selecting that item.
This is what I'm using now:
$scope.getItemHeight = function(index) { // index is index of element in ng-repeat
var itemHeight = angular.element('li').eq('+index+').offsetHeight;
return itemHeight;
};
But that doesn't work due to error: Error: [jqLite:nosel] Looking up elements via selectors is not supported by jqLite!
I also tried:
$scope.getItemHeight = function(index) {
var itemHeight = document.querySelectorAll('ul:nth-child('+index+')');
return itemHeight;
};
This returns an element with length 0 so the selector doesn't work.
What am I missing here?
Codepen here: http://codepen.io/squrler/pen/LxsfE?editors=101
EDIT:
What I want is not possible at this time. More information here: https://github.com/driftyco/ionic/issues/1691
Edit: After looking at this further it seems a bit more complicated. The directive that is firing the repeater needs to run in order for the lis to be rendered, as the li is rendered (assuming you move this to a directive) it triggers the directive to get its height, but the li and it's corresponding data are not yet fully rendered and thus have no height. If you wait for the rendering using timeout, the repeater will just continue rendering without valid height data while the timeout waits. So it seems you have a bit of a conundrum. You might try using http://binarymuse.github.io/ngInfiniteScroll/ or something of the like.
This should be put in a directive which will give you easy access to the li as it is rendered.
Something like:
.directive('getHeight', ['$filter', function ($filter) {
'use strict';
return {
restrict: 'A',
scope: true,
link: function (scope, element, attrs) {
var li = element[0];
var height = li.offsetHeight;
console.log('Height': height)
}
};
}]);
Not sure what you're looking to do with the height once you have it...
Otherwise you can just go:
var ul=document.getElementsByTagName('ul')[0];
var li=ul.getElementsByTagName('li')[index];
var height=li.offsetHeight;
Related
I'm creating elements inside a for loop using $compile and I need to bind data to them.
for (var item in data) {
var elem = $compile('<panel ng-model="item"></panel>')($scope)[0];
container.append(elem);
}
How can I get the panel directive to access the item data? I have to append the elements manually, so I can't use ng-repeat.
Don't do a loop and compile for each item. That would be bad performance for your app. Do:
$scope.data = data;
var elem = $compile('<div ng-repeat="item in data"><panel ng-model="item"></panel></div>')($scope)[0];
container.append(elem);
You place data on the $scope, because that's how the binding occurs. Note that the HTML you write is compiled against a specific $scope.
I think you can do without the [0] as well, but not sure.
I've solved my problem like this:
for (var index in data) {
var elem = $compile('<panel data="data[' + index + ']"></panel>')($scope)[0];
container.append(elem); // pseudocode line
}
so, this is a little complicated.
I have a directive that loads an SVG file based on some criteria. It is a large directive so I won't be posting it here as it does not affect my issue, you just need to know it exists.
In that directive I have a template file that has this bit of HTML:
<div ng-include="svgPath" onload="loaded()"></div>
note the onload function. This is called when the SVG has loaded.
Inside this SVG there are some hidden groups that I need to interact with. Because this is changing the DOM I have written another directive, which looks like this:
.directive('kdGraphicsRepeater', function () {
return {
restrict: 'A',
scope: {
targetElementId: '#kdGraphicsRepeater'
},
templateUrl: '/assets/tpl/directives/graphicsRepeater.tpl.html',
link: function (scope, element) {
// Declare our options
scope.options = [];
// Get our target elements
var target = angular.element(document.getElementById(scope.targetElementId)),
svg = angular.element(target.find('svg')),
children = svg.children();
console.log(target.length);
console.log(svg.length);
console.log(children.length);
// Loop through the SVG children
for(var i = 0; i < children; i++) {
// Get the current child
var child = children[i],
childId = child.attr('id');
console.log(childId);
// If we have an option
if (childId && childId.indexOf('options-') > -1) {
// Push the name to our options array
scope.options.push(childId.replace('options-'));
}
}
}
}
})
and the corresponding template looks like this:
<div ng-repeat="item in options">
{{ item }}
</div>
As you should be able to make out. I am trying to loop through the SVG immediate children and check to see if any of the elements have a partial id match to options-. If they do I add them (omitting the "options-" string) to the scope.options variable. My template simply loops through them displays them.
The problem is that the SVG has not loaded for this directive and the console.log(svg.length) returns 0 for that very reason. I need some way of knowing when the SVG has loaded.
Can someone help me out?
Okay, I'm not sure about this but maybe try using the ng-if directive by angular.
in your loaded() function, have like a boolean that says its loaded, say its called svgLoaded. use ng-init instead of onload. Make sure this would be the same scope as the one in your svg.
then in your svgPath, bind ng-if to your boolean
<"element where you are attaching kdGraphicsRepeater"
ng-if="svgLoaded" kdGraphicsRepeater
>
Sorry, I'm making assumption that you have same scope, and that is possible to do.
I seem to be having a problem using a directory integrating packery into angular. It seems to be working just fine when I load onto the first page, but when I leave the page and return it looks like its breaking packery in a weird sort of a way where it's working but everything seems to be collapsed to 1 column and I can't for the life of me figure out how.
I'm using this example I found http://codepen.io/amergin/pen/AEIvt , and it seems to be working great. for reference here is the directory
angular.module('demoApp')
.directive('packeryAngular', ['$rootScope', '$timeout',
function($rootScope, $timeout) {
return {
restrict: 'A',
scope: true,
link: function(scope, element, attrs) {
//console.log("link called on", element[0]);
scope.element = element;
if (!$rootScope.packery) {
$rootScope.packery = new Packery(element[0].parentElement, {
gutter: 10,
columnWidth: '.grid-sizer',
rowHeight: 300,
itemSelector: '.item'
});
var draggable1 = new Draggabilly(element[0]);
$rootScope.packery.bindDraggabillyEvents(draggable1);
var orderItems = function() {
var itemElems = $rootScope.packery.getItemElements();
};
$rootScope.packery.on('layoutComplete', orderItems);
$rootScope.packery.on('dragItemPositioned', orderItems);
} else {
// console.log("else", element[0]);
$timeout(function() {
$rootScope.packery.appended(element[0]);
});
var draggable2 = new Draggabilly(element[0], {handle: '.handle'} );
$rootScope.packery.bindDraggabillyEvents(draggable2);
}
$timeout(function() {
$rootScope.packery.layout();
});
// watch for destroying an item
scope.$on('$destroy', function() {
$rootScope.packery.remove(scope.element[0]);
scope.packery.layout();
});
}
};
}
]);
and in my template I just have
<div class="item gs-w" ng-class="widget.size" ng-repeat="widget in contentHere" packery-angular >
Pretty straight forward, and again this works great (thank you to the codepen author!), however it's going haywire when I swap views. What's weird is the directive is definitely running because i can drag and re arrange the packery items, but they are all stuck on 1 column on the left most side and I can't figuire out for the life of me why.
I don't know if this is relevant but it might be worth sharing - 1. I am using the ngroutes to swap my views around the typical way. 2. This directive call is nested inside a view. 3. As I mentioned, packery and dragabilly are running when I return the page, they are just stuck on 1 column to the left (can drag up and down).
I really have no idea where to start with this as I have no errors thrown or clues. Would appreciate any help. Thanks for taking the time to read!
Edit - I have some pictures to clarify
This is how it looks normally -
And this is how it looks when you leave and return to the page
you can see it's going over the buttons too which are in a bootstrap row so I'm not sure what's going on with it. You can still drag and move them in both instances.
I think your issue might be here:
if (!$rootScope.packery) { ... }
Within that if block you are defining $rootScope.packery and initializing the plugin. Your destroy method never nullifies $rootScope.packery, so the plugin is never re-initialized. When you return to the view.
Try modifying like so:
// watch for destroying an item
scope.$on('$destroy', function() {
$rootScope.packery.remove(scope.element[0]);
scope.packery.layout();
$rootScope.packery = null; // add this line
});
I was brought in to fix a website that was on fire a couple months back. I've got most things under control and I'm down to fixing various wish-list items. One of them involved some angular code that I just can't seem to get to do what I want. On some pages there are videos followed by a short quiz. I need to update the user's scores after each event. So far, this proved to be easy enough for the total score which looked like this:
<a id="updateafterscore" href="~/user/leaderboard/" class="fill-div">
{{ profile.currentScore }}
</a>
And that got updated with this:
document.getElementById('updateafterscore').innerHTML = data.Data.CurrentScore;
So far, so good. However other elements on the page have, thus far, proved impossible to update. Here's what's on the page:
I added the "id="refreshvideo" myself so I could try to alter the tag. Finally, here's the angular module for simple-circle (I've left out the actual drawing code since it's not really relevant):
angular.module('thrive.shared').directive('simpleCircle', function() {
return{
replace: true,
template: '<canvas width="60" height="60" style="margin: -10px 0 0 -15px;"></canvas>',
restrict: 'E',
scope: {
value: '#',
color: '#',
bgColor: '#',
forecolor: '#',
radius: '#'
},
link: function (scope, elem, attrs) {
var multiplyLength = 1;
var canvasElem = elem[0];
var inMotion = false;
if (scope.value <= 2) {
multiplyLength = 5;
}
scope.$watch('value', function() {
drawCircle(canvasElem, scope.color, scope.value * multiplyLength, scope.value, scope.name);
});
function drawCircle(canvas, color, calculatedPoints, displayPoints, name) {
So, to the question: how the heck do I update the number that's displayed? I tried various things:
document.getElementById('refreshvideo').setAttribute('value', data.Data.VideoWatchedCount);
document.getElementById('refreshvideo').setAttribute('data-value', data.Data.VideoWatchedCount);
$scope.profile.videosWatched = data.Data.VideoWatchedCount;
None of these things worked. I inspected the canvas element in the source in the browser and I could see the value and data-value tags change to whatever I set them, but the image remained unchanged. Am I setting the wrong thing? (Perhaps whatever $watch is watching) Do I have to force some kind of re-paint of a canvas element?
#charlietfl means your solution is not actually using AngularJS - you're completely bypassing it. Angular provides two-way data binding between Javascript data and the HTML DOM. All you do is tell it where to draw data, and it will do that for you automatically, keeping it up to date from then on as the data changes.
In Angular, you never call getElementById and certain never set innerHTML because then you block Angular from doing its thing - in many cases you actually break it. Every one of those instances introduces a new bug while "patching" another.
Go back to your example template line:
<a ..attributes...>{{ profile.currentScore }}</a>
When it sees this, Angular will create what it calls a "watcher" on profile.currentScore. If its value right now is '1', it will render this as <a ...>1</a>.
Every digest cycle, that watcher will tell it to look at profile.currentScore to see if it changed. This line of code is pretty typical in JS:
profile.currentScore = 42;
Angular will "see" this happen through that watcher, and will automatically update the rendered template. You do nothing else - and if you ever feel that you need to, it almost always means something else is wrong.
If you're running into this a lot, try the "standard quick-fix". We see this a lot with people who didn't architect an application properly, and they're doing data model updates outside Angular's digest cycle where it can't "see" them. Try wrapping your update code in an $apply() call:
$scope.$apply(function() {
profile.currentScore = 42;
});
If you have a LOT of updates to make and you don't want to nest the call, you can also cheat, like this:
// Lots of stuff...
profile.currentScore = 42;
// Lots more stuff...
$scope.$apply();
You will know right away if you need to do this. If it works, you need to do it. :) If you get an error message in your console saying you're already in a digest cycle, you do NOT need to do it (it's something else).
I mentioned that I thought perhaps I was modifying the wrong profile variable and so it wasn't refreshing. So I looked back a little bit in the code that is supplying the numbers:
angular.module('episodes').controller('episodeCtrl', ['$scope', '$rootScope', '$window', 'episode', 'relatedCourses', 'Video', 'episodeItems', 'profile', 'Profile',
function ($scope, $rootScope, $window, episode, relatedCourses, Video, episodeItems, profile, Profile) {
// stuff skipped....
onComplete: function () {
Video.complete({ videoId: item.item.id }).$promise.then(function () {
item.progress = "Completed";
$scope.loadNextItem();
$scope.profile = Profile.get(); // <<-- gotten from somewhere
$.ajaxSetup({ cache: false });
$.get('/user/getCurrentUserPointsModel', function (data) {
if (data == "")
return;
$scope.profile.currentScore = data.Data.CurrentScore;
$scope.profile.videosWatched = data.Data.VideoWatchedCount;
$scope.profile.testTakenAndCorrectAnswerCount = data.Data.TestTakenAndCorrectAnswerCount;
Profile.save(); // <-- added
The value in $scope.profile is pulled from Profile, but I don't fully get how that gets where it is. I suppose I will need to figure that out because there's another place where these updates have to happen that lack that Profile information. Anyways I added the last 4 lines in place of this:
document.getElementById('updateafterscore').innerHTML = data.Data.CurrentScore;
... and all worked according to plan. I guess I tackle the other part later when I figure out how the data gets to the controller.
You can't do this that way. It's not Angular way of dealing with data.
Read the documentation before https://docs.angularjs.org/tutorial/step_04
If you need to modify your DOM using document.. probably sth wrong is with your code.
BTW. Stop using globals like:
document.getElementById('updateafterscore')
I've got a problem with angularjs and even after research I just couldn't find where I'm wrong.
I need to recalculate the css value "left" for an element. I'm using the 'ng-style' directive and a method that will return an object with the css value. Thats - afaik - what I have to do. But when I update the value, it wont update the style.
ng-bind usage:
<div ng-style="getCssShiftObject()">
method to create object
$scope.getCssShiftObject =function(){
return {'left':this.cssShift+'px'};
};
method to change the object
$scope.nextPosition = function(){
if((this.currentPosition+1) <= this.maxPosition){
this.currentPosition = this.currentPosition+1;
this.cssShift = (this.currentPosition*this.slideSize)*-1;
}
return this.currentPosition;
};
It will update at another place in the content when I use it like that:
{{getCssShiftObject()}}
I hope you can give mit a hit, thanks for your time!
I came across a similar problem. I was trying to use ngStyle to load a background image, but if the variable in an expression is not immediately available (which might be the case if it's part of a resource promise), it won't work.
To address this, I created my own ngStyle directive that addresses this issue. Hopefully this is better than creating functions for every single scenario where you want to use ngStyle in this way.
app.directive("myStyle", function (){
return {
restrict: 'A',
link: function(scope, element, attrs)
{
var el = element[0],
attr = el.getAttribute('style');
el.setAttribute('style', attr);
// We need to watch for changes in the style in case required data is not yet ready when compiling
attrs.$observe('style', function (){
attr = el.getAttribute('style');
if(attr)
{
el.setAttribute('style', attr);
}
});
}
};
});
Then, you can use it this way:
<a my-style style="background-image: url('{{promise.myImage}}')"></a>
Thx for your time! I solved the Problem with the input from Cherniv, but I'm not sure how. I changed the way I create the values. Now it's working.
$scope.calcCssShift = function(){
this.cssShift = ($scope.currentPosition * $scope.slideSize)*-1;
};
$scope.getCssShiftObject =function(){
return {'left':$scope.cssShift+'px'};
};
$scope.nextPosition = function(){
if((this.currentPosition+1) <= this.maxPosition){
$scope.currentPosition = this.currentPosition+1;
$scope.calcCssShift();
}
};
I had a similar problem with the style attribute. My binding was not working in some browsers, especially IE. I solved it by using ng-attr-style="{{yourBindingExpression}}".
Read more about ng-attr interpolation at https://docs.angularjs.org/guide/interpolation