angular code not working in dynamically generated content - angularjs

I know I've had this happen before but not in exactly this way and I'm having trouble making the necessary changes.
I have a div that starts out empty and hidden as part of a gallery. When I click an image, it populates the div and shows it. Along with the image/content, there's some navigation elements as well. Thing is, those are also dynamically generated and not working since the angular needs to recompile. Here's the function that populates:
$scope.picturePop = function(picID){
match = $.grep($scope.gallery, function(obj) { return obj.id == picID; });
pic = match[0].image;
title = match[0].title;
desc = match[0].desc;
closediv = "<div id=\"divClose\" class=\"floatRight\" ng-click=\"closeParent();\">Close</div>";
navDiv = "";
if(picID > 1){
prev = picID - 1;
navDiv += "<div id=\"picNav\" ng-click=\"picturePop(" + prev + ")\">Previous</div>";
}
if(picID < $scope.picCount){
next = picID + 1;
navDiv += "<div id=\"picNav\" ng-click=\"picturePop(" + next + ");\">Next</div>";
}
$('#innerPictureDisplay').html(closediv + "<br/><br/><img src=\"images/paintings/" + pic + "\" /><p><b>" + title + "</b><\p><p>" + desc + "</p>"+ navDiv);
$('#outerPictureDisplay').css('display','block');
};
How do I make that code "recompile" so that the "CLOSE" and navigational items work?
UPDATE
So, I changed my approach but I'm clearly still missing something:
my new HTML:
<div id="outerPictureDisplay" ng-show="picID > 0">
<div id="innerPictureDisplay">
<div id="divClose" class="floatRight" ng-click="picID = 0;">Close</div>
<div id="picNav" ng-click="picturePop({{prevID}});" ng-show="picID > 1">Previous</div>
<div id="picNav" ng-click="picturePop({{nextID}});" ng-show="picID < picCount">Next</div>
<img src="images/paintings/{{thisPic.image}}" />
<p>
<b>{{thisPic.title}}</b>
</p>
<p>{{thisPic.desc}}</p>
</div>
</div>
my new function:
$scope.picturePop = function(picID){
match = $.grep($scope.gallery, function(obj) { return obj.id == picID; });
$scope.thisPic = match[0];
$scope.picID = picID;
$scope.nextID = picID + 1;
$scope.prevID = picID - 1;
var $content = $('#innerPictureDisplay');
var scope = $content.scope();
$compile($content.contents())(scope);
};
When I click on a pic, it all loads up fine but when I click on a Previous or Next, it closes since it's registering picID as null. I checked and the numbers are indeed getting printed in the code, but angular doesn't see them on the click. They're coming through as undefined - seemingly the compile issue. I tried implementing it all as a directive, even using the suggested link, but I must've done something off since it didn't help.

You can use the angular service $compile like so:
$compile("<a ng-click='myFunction()'>my html</a>")(scope);
And of course you must inject him wherever you are.
Anyway, for God sake, don't use DOM manipulation this way. You should use a directive and feed the content from a model instead.
UPDATE 1
I sugest use incuna's bindHtmlCompile directive that you can found here.

Related

How to pass a variable to css in AngularJS

I'm trying to make a virtual scroll and whenever the user scrolls down I need to add a negative top equal to the container height to each row. But of course this top property can vary depending of some factors like the user's screen resolution or browser window size.
So far this is what I got:
<div class="container" id="my-container">
<!--If it has the class row-scrolled the top property is applied-->
<div ng-repeat="(row) in virtualCollection"
ng-class="{'row-scrolled': controller.isScrolled}">
<!-- row properties -->
</div>
</div>
I have also thought about the idea of using ng-style but would override any style from my .css file.
Is there anyway to get the size/property of a DOM element...
// controller
var containerHeight = angular.element('#my-container')[0].clientHeight;
var cssProperty = '-' + containerHeight + 'px';
And then use it in an css?
// css
.row-scrolled {
top: cssProperty;
}
You can't pass variables from javascript to CSS since CSS is not a programming language but a style sheet language.
What you can do is manipulating specific elements with javascript.
Based on your code here is an example:
// controller
var containerHeight = angular.element('#my-container')[0].clientHeight;
var cssProperty = '-' + containerHeight + 'px';
var $$rowScrolled = document.querySelectorAll(".row-scrolled");
if ($$rowScrolled && $$rowScrolled.length > 0) {
for (var i = 0; i < $$rowScrolled.length; i++) {
var $rowScrolled = $$rowScrolled[i];
$rowScrolled.style.top = cssProperty;
}
}
With jQuery:
// controller
var containerHeight = angular.element('#my-container')[0].clientHeight;
var cssProperty = '-' + containerHeight + 'px';
var $rowScrolled = $(".row-scrolled");
if ($rowScrolled && $rowScrolled.length > 0) {
$rowScrolled.css("top", cssProperty);
}
You can not pass a variable to the CSS.
What you can do though is add the property directly using the ng-style tag:
<div class="container" id="my-container">
<!--If it has the class row-scrolled the top property is applied-->
<div ng-repeat="(row) in virtualCollection"
ng-style="{'top': controller.cssProperty}">
<!-- row properties -->
</div>

Refreshing scope variable not working with scope function

I'm trying to update data of my chart using chartJS and its wrapper angular-chart. When a button is clicked, it adds data on the chart, no problem with this.
I have another event, which update the chart after a scroll event. The call works great, and it calls exactly the same function as previous. But my $scope.valuesData isn't updated . Then I added a $scope.$apply() after the function, and it redraws the whole chart with the right values.
Question are :
Why does my $scope.chartValues is not updated when I call the function AddData from the controller (and why is $scope.chartValues is updated when I call the function from the DOM). It is probably a data-binding issue ?
Is it a function that update only a specific scope variable, instead of refreshing the whole scope ?
My code :
HTML :
<ion-scroll zooming="false" direction="x" delegate-handle="scroller" on-scroll="getScrollPosition()" has-bouncing="true" scroll-event-interval="30000">
<div class="chart_wrapper" ng-style="{ 'width': myWidth + '%' }">
<canvas class="chart-bar" chart-data="data2" chart-labels="labels" chart-dataset-override="datasetOverride2" chart-options="options2" ng-click="goToMetrics()" ">
</canvas>
</div>
</ion-scroll>
<button class="button button-small button-calm" ng-click="addValues()">Add Data </button>
JS (controller) :
$scope.getScrollPosition = function(){
var pos = $ionicScrollDelegate.$getByHandle('scroller').getScrollPosition().left ;
if (pos == 0 && $scope.flag == -1){
$scope.flag = 0;
//setTimeout($scope.addValues(), 1000);
$scope.addValues();
}
if (pos < 0){
$scope.flag = -1;
}
};
$scope.addValues = function(){
console.log('add');
// Retrieve values
var week = Graph.getData();
var pp = $scope.chartData;
var mm = $scope.labels;
// Add values to the chart and update the chart width
var tmp = Graph.addBegin(pp, mm, week);
$scope.valuesData = tmp.values;
$scope.myWidth = $scope.myWidth + tmp.length * 4 ;
};
Any help appreciated
Because setTimeout runs outside angular context, use $timeout version.
Nope. You can use $scope.$digest(), to check changes from current scope, not all $scope tree;

Prevent search changes from spamming history

I'm using links like #!/OrderList?id=123, which shows the list of all orders and more details of the order 123. With reloadOnSearch=false and watching $routeUpdate it works fine, except for one thing: All such links get put into the browsers history, while I'd prefer to have only one such link there. For example, instead of
#!/OrderList?id=123
#!/OrderList?id=124
#!/OrderList?id=125
#!/AnotherList?id=678
#!/AnotherList?id=679
just the last member of each group, i.e.,
#!/OrderList?id=125
#!/AnotherList?id=679
I'm aware of $location.replace(), but I can't see where to place it when the change happens via following a link. I tried to place it in $scope.$on("$routeUpdate", ...), but it did nothing, probably because it's too late when the route has already changed.
I'm not using neither router-ui nor the HTML5 mode (just plain angular-route).
I'm afraid, I wasn't clear about me insisting on using href rather than a custom handler. I want the links to work with middle mouse click and bookmarks and everything. A combination of ng-href and ng-click might do what I want, but I've found a simple solution working with plain links.
Looks like you may want to update the URL query parameter using an ng-click function instead of relying on a link, then call a function like the one below to update the parameter... With replace state, the history should only track the current value. I haven't tested this case so if you try it, let me know if it works.
function changeUrlParam (param, value) {
var currentURL = window.location.href;
var urlObject = currentURL.split('?');
var newQueryString = '?';
value = encodeURIComponent(value);
if(urlObject.length > 1){
var queries = urlObject[1].split('&');
var updatedExistingParam = false;
for (i = 0; i < queries.length; i++){
var queryItem = queries[i].split('=');
if(queryItem.length > 1){
if(queryItem[0] == param){
newQueryString += queryItem[0] + '=' + value + '&';
updatedExistingParam = true;
}else{
newQueryString += queryItem[0] + '=' + queryItem[1] + '&';
}
}
}
if(!updatedExistingParam){
newQueryString += param + '=' + value + '&';
}
}else{
newQueryString += param + '=' + value + '&';
}
window.history.replaceState('', '', urlObject[0] + newQueryString.slice(0, -1));
}
Maybe what you can do is, istead of a regular <a ng-href="#!/OrderList?id={{your.id}}">Link to your ID</a> you can create a link with an ng-clickdirective bound to a function which retrieves the data and passes it to the view.
Your HTML
`<span ng-click="loadListItem(your.id)">Link to your ID</span>`
<div id="your-item-data">
{{item.id}} - {{item.name}}
</div>
Your controller
myApp.controller('someController', function($scope) {
$scope.loadListItem(itemId) = function (
var myItem;
// Get item by 'itemId' and assign it to 'myItem' var
$scope.item = myItem;
);
});
This way instead of changing your URL, you can retrieve the item data in your controller and pass it to your view.
You don't give much detail of your controller/service implementation, but I hope this helps.
I think you were on the right track with the $scope.$on("$routeUpdate", ...) thing. Rather than $routeUpdate, however, try binding on $routeChangeStart:
$scope.$on("$routeChangeStart", function(event, nextRoute, currentRoute){
if (nextRoute.yourCriteria === currentRoute.yourCriteria){
//do your location replacement magic
}
});
If you wanted, you could even define a dontUpdateHistory boolean property in your route definitions, and then check for that property in your run block:
myApp.config(function($routeProvider){
$routeProvider.when('/whatever' {
templateUrl: 'whatever',
dontUpdateHistory: true //something like this
});
}).run(function($rootScope){
$rootScope.on('$routeChangeStart', function(event, nextRoute, currentRoute){
if (nextRoute.dontUpdateHistory){
//do your location replacement magic
}
});
I haven't tested any of this, but hopefully it gets the idea across.
I wasn't satisfied with any answer and after quite some debugging I found this solution:
.run(function($rootScope, $location) {
var replacing;
$rootScope.$on("$locationChangeStart", function(event, newUrl, oldUrl) {
if (oldUrl === newUrl) return; // Nobody cares.
// Make urls relative.
var baseLength = $location.absUrl().length - $location.url().length;
newUrl = newUrl.substring(baseLength);
oldUrl = oldUrl.substring(baseLength);
// Strip search, leave path only.
var newPath = newUrl.replace(/\?.*/, "");
var oldPath = oldUrl.replace(/\?.*/, "");
// Substantial change, history should be written normally.
if (oldPath !== newPath) return;
// We're replacing, just let it happen.
if (replacing) {
replacing = false;
return;
}
// We're NOT replacing, scratch it ...
event.preventDefault();
// ... and do the same transition with replace later.
$rootScope.$evalAsync(function() {
$location.url(newUrl).replace();
replacing = true;
});
});
})

unable to call function from controller in cell template in angular UI grid

i am using angular Ui grid. i am using cell Template in order to render the data. previously i am using ng-repeat in cell template in order to render the data but ng -repeat causing performance issue in case of large data. so i decided to load the on demand rather than binding it during initilization phase of grid not what i done is i have a one button in my cell template on the click of that button i am creating the html template and setting the newly generated html to the one of the div in cell template using .Inner HTML property. all the html is getting render proper but now problem start here i want to call some function in my controller on the click event of my dynamically added div. but when i click the div nothing is happen and function is not getting call.
here is the code .
for (var i = 0; i < data.length; i++) {
if (data[i]) {
var value;
if (data[i].Value > 100) {
value = +data[i].Value;
} else if (data[i].Value > 0 && data[i].Value <= 100) {
value = data[i].Value + " %";
} else {
value = data[i].Value;
}
html = html + "<div data-ng-show='row.entity.Over.length>0' style='height:25px;margin:0px' class='row' >" +
"<div class='col-lg-6'>" + data[i].Type + " : " + data[i].line + "</div>" +
//"<div class='col-lg-6' ><a class="+classname+" ng-click='grid.appScope.clickHandler()'>" + value + "</a></div>"
"<div class='col-lg-6' ><a ng-click='grid.appScope.clickHandler()'>" + value + "</a></div>" + "</div>";
$compile((html)($scope));
}
}
Please help me on this issue.

How can I get ng-click to function with ng-repeat and ng-bind-html?

I am trying to get a ng-click directive to function within an ng-repeat and ng-bind-html. The ng-click code is added to a string of html from data pulled from the server (hence the ng-bind-html). The setup has a controller, a base template that is put onto the page with Drupal, and a partial that is loaded via the template from Drupal.
The controller looks like this at the moment:
var guideListController = angular.module('app')
.controller('guideListController', [
'$scope',
'$sce',
'$compile',
'ViewService',
'ToolKit',
function($scope, $sce, $compile, ViewService, ToolKit) {
// Storage for which rows need more/less links
this.rowIndex = [];
this.showFull = false;
this.showFullClick = function() {
alert('Showfull');
};
this.trustIntro = function(code) {
return $sce.trustAsHtml(code);
};
// Fetch the guide list view from services
var data = ViewService.get({view_endpoint:'guide-service', view_path: 'guide-list'}, function(data) {
//console.log(data);
// Update/process results
for (var row in data.results) {
// Create short intro w/ truncate.js
data.results[row].Intro_short = $sce.trustAsHtml($scope.guideList.getShortIntro(data.results[row], row));
//data.results[row].Intro_short = $scope.guideList.getShortIntro(data.results[row], row);
// Update intro
data.results[row].Introduction = $sce.trustAsHtml($scope.guideList.updateIntro(data.results[row], row));
//data.results[row].Introduction = $scope.guideList.updateIntro(data.results[row], row);
}
$scope.guideList.guides = data.results;
});
// Add a read less anchor tag at the end of the main intro
this.updateIntro = function(row, row_index) {
var intro = row['Introduction'].trim();
if ($scope.guideList.rowIndex[row_index]) { // only apply Less link if needed
var index = intro.length - 1;
var tag = [];
if (intro.charAt(index) === '>') { // we have a tag at the end
index--;
do {
tag.push(intro.charAt(index));
index--;
} while (intro.charAt(index) != '/'); // the closing tag
index--; // we move the index one more for the "<"
tag.reverse(); // Reverse
tag = tag.join('');
}
var inserts = ['div', 'p']; // we insert the Less link here.
if (jQuery.inArray(tag, inserts) >= 0) { // insert into the tag
intro = intro.substr(0, index) + ' <a class="less" ng-click="$parent.guideList.showFull = false">Less</a>' + intro.substr(index);
}
else { // insert at the end of the html
intro = intro + '<a class="less" ng-click="this.showFull = false">Less</a>';
}
}
return intro;
};
// Truncate the long intro into a shorter length blurb
this.getShortIntro = function(row, row_index) {
// Truncate if necc.
var short_intro = jQuery.truncate(row['Introduction'], {
length: 250,
words: true,
ellipsis: '\u2026 <a class="more moreish" attr-ng-click="guideList.showFullClick()">Read on</a>'
});
var more = jQuery('.more', short_intro); // select more link
if (more.length) { // do we have a more link
$scope.guideList.rowIndex[row_index] = true;
}
else { // no more link
$scope.guideList.rowIndex[row_index] = false;
}
$compile(short_intro)($scope);
return short_intro;
};
}]);
As you can see in the ViewService.get() call, data is fetched and then processed. The processing simply involves putting a link at the end of the "Intro" field that is intended to be clickable.
For a while I was having a tough time to even get the ng-click directive to even show (it was being filtered out w/out $sce.trustAsHtml). Now it is there but clicking it has no effect.
The main template (from Drupal) currently looks like:
<div class="guide-listing" ng-controller="guideListController as guideList">
<a ng-click="guideList.showFullClick()">Click me</a>
<div class="guide-teaser"
ng-repeat="guide in guideList.guides"
ng-include src="'/sites/all/themes/ngTheme/ngApp/partials/guide_teaser.html'">
<!-- See guide_teaser.html partial for guide teasers -->
</div>
</div>
The ng-click as placed in the Drupal template above works as expected.
And for the partial that is used in the ng-repeat, it looks like so:
<div ng-controller="guideListController as guideList">
<h2 class="guide-teaser-title">{{guide.node_title}}</h2>
<div class="guide-teaser-intro" ng-bind-html="guide.Introduction" ng-show="guide.showFull">
{{guide.Introduction}}
</div>
<div class="guide-teaser-intro-short" ng-bind-html="guide.Intro_short" ng-show="!guide.showFull">
{{guide.Intro_short}}
</div>
</div>
So far I have only been working on getting the ng-click to work on the short_intro and have had no success so far. Any ideas as to what I am doing wrong would be greatly appreciated!
Ok, So I did get some traction! I used the ngHtmlCompile (http://ngmodules.org/modules/ng-html-compile) directive that was created by https://github.com/francisbouvier (thanks!)
The issue was that the new (dynamic) html wasn't being compiled.
At first it didn't work. I had two issues that prevented it from firing:
A: I stopped using $sce.trustAsHtml. Using this in conjunction with the directive caused the content to disappear!
B: The other issue was one of scope. After I changed the directive such that transclude was set to false it worked just fine!

Resources