AngularJs Custom Directive scope data overwritten - angularjs

I have displayed the products based on branch and billing account. In the product template, i have a "+" button, if we click on the button, then i'm displaying the particular product id below that product template.
Now the problem is, when i click the "+" button of "Product 1" , then it display product id as "300152". its fine. After that If i click the "+" button next to "Product 2", its displaying product id as "300153" below both "Product 1" and "Product 2". This is the issue. Please check the following fiddle. Any help would be greatly appreciated.
JS Fiddle
TabsApp.directive('productTemplate', ['$compile',
function($compile){
return {
restrict: "E",
scope: {
branchdata : '='
},
//templateUrl : templateSupportTabV3,
template: ' <li ng-repeat= "(prod_index, product) in branchdata.moduleLevel3List "><span class="normal-negrita">{{product.name}} (ID.{{product.id}})</span><a class = "cursor" ng-click="load_productInfo_branch( branch_index + 1 , prod_index + 1 , product.id , branchdata.id );"><span id="more_product_body_{{branchdata.id}}_{{ product.id }}" class="normal" style="font-size:10px;"> + </span> </a><div id="product_body_{{branchdata.id}}_{{product.id}}" class="product_panel_container"></div></li> ',
link: function (scope, elem, attrs) {
scope.load_productInfo_branch = function(baIndex, productIndex, productId,branchId){
debugger;
scope.prdouctType = productId;
var resp = "<p >ID : {{prdouctType}} </P>";
var divId = document.getElementById("product_body_" + branchId+"_"+productId);
divId.innerHTML=resp;
$compile(divId)(scope);
};
}
};
}]);

You are using two-way binding while adding new DOM child; and there is one "prdouctType" in the scope. So,
var resp = "<p >ID : {{prdouctType}} </P>";
should be something like
var resp = "<p >ID : " + scope.prdouctType + "</P>";

Here is the working JS Fiddle: http://jsfiddle.net/fokv7Lhh/38/
You can use one way binding
var resp = "<p >ID : {{::prdouctType}} </p>";
Do you really need to apply to the scope? Another way to show the value is something like this:
var resp = "<p >ID : " + productId + "</p>";

Add in bindToController:
bindToController: true,
scope: {
branchData: '='
}
That should stop it happening.

Hello If you want to keep the {{prdouctType}} the same. You can try something like this.
You can hide the previous div and open the new one.
var TabsApp = angular.module('TabsApp', []);
TabsApp.controller('IndexCtrl', function ($scope) {
$scope.tabdata =[{"id":49844,"name":"Billing account 1","entityType":"BA","moduleLevel2List":[{"id":2239,"name":"branch 1","entityType":"BRANCH","moduleLevel3List":[{"id":300152,"name":"PRoduct 1","entityType":"PRODUCT"},{"id":300153,"name":"PRoduct 2","entityType":"PRODUCT"},{"id":300154,"name":"PRoduct 3","entityType":"PRODUCT"}]}]},{"id":49845,"name":"Billing account 2","entityType":"BA","moduleLevel2List":[{"id":2240,"name":"branch 2","entityType":"BRANCH","moduleLevel3List":[{"id":300127,"name":"PRoduct 4","entityType":"PRODUCT"}]}]}];
});
TabsApp.directive('supportTabV3Directive', ['$compile',
function($compile){
return {
restrict: "E",
scope: {
tabdata : '='
},
//templateUrl : templateSupportTabV3,
template: '<li name="billing_{{ ba_index + 1}}" ng-repeat = "(ba_index, ba) in tabdata "><span class="bold">{{ba.name}} (ID. {{ba.id}})</span><ul><li ng-repeat = "(branch_index, branch) in ba.moduleLevel2List "><span class="normal">Nombre: {{branch.name}}</span><ul><product-template branchdata="branch" ></product-template></ul></li>',
link: function (scope, elem, attrs) {
}
};
}]);
TabsApp.directive('productTemplate', ['$compile',
function($compile){
return {
restrict: "E",
scope: {
branchdata : '='
},
//templateUrl : templateSupportTabV3,
template: ' <li ng-repeat= "(prod_index, product) in branchdata.moduleLevel3List "><span class="normal-negrita">{{product.name}} (ID.{{product.id}})</span><a class = "cursor" ng-click="load_productInfo_branch( branch_index + 1 , prod_index + 1 , product.id , branchdata.id );"><span>{{productType}} </span> <span id="more_product_body_{{branchdata.id}}_{{ product.id }}" class="normal" style="font-size:10px;"> + </span> </a><div id="product_body_{{branchdata.id}}_{{product.id}}" class="product_panel_container"></div></li>',
link: function (scope, elem, attrs) {
scope.load_productInfo_branch = function(baIndex, productIndex, productId,branchId){
scope.prdouctType = productId;
if(document.querySelector('#test'))
{
document.querySelector('#test').remove()
}
var resp = "<p id='test'>ID : {{prdouctType}} </P>";
var divId = document.getElementById("product_body_" + branchId+"_"+productId);
console.log(divId);
divId.innerHTML=resp;
$compile(divId)(scope);
};
}
};
}]);
<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.6.0/underscore-min.js
"></script>
<body>
<div ng-app = "TabsApp">
<div ng-controller="IndexCtrl">
<support-tab-v3-directive tabdata="tabdata"></support-tab-v3-directive>
</div>
</div>
</body>
</html>
You can run the above snippet
(OR)
HEre is a DEMO for it

Related

AngularJS Access Global Variable Under Directive

I have a global variable like following.
var Lightbox = {};
............
Lightbox.openModal = function (newImages, newIndex, modalParams) {
...............
}
I would like to access this variable under directives like the following way.
app.directive('productBuyers', ['Product', function(Product) {
return {
restrict : 'E',
scope : {},
template : '<div>' +
'<p class="product-buyers-f bold" ng-show="photos.length">Others:</p>' +
'<
'<div class="product-buyer square" ng-click="openLightboxModal($index)" ng-repeat="photo in photos | limitTo:3" ng-style="{\'background-image\':\'url(\' + photo.image + \')\'}"></div>' +
'<div class="clear"></div>' +
'</div>' +
'</div>',
link : function($scope, element, attrs) {
$scope.photos = [];
function getImages() {
}
$scope.openLightboxModal = function (index) {
Lightbox.openModal($scope.photos, index);
};
getImages();
}
}
}]);
I have tried by passing "$window" to the directive parameter and also using scope but it's not working. It's showing undefined "Lightbox".
Try window.Lightbox and also window.Lightbox.openModal = function(...)

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.

How to pass a object into a directive

I have a items array which is used by ng-repeat to render the menu,
and on click of Add to cart button addItem() is called.
Currently i pass the name of the selected item as the name attribute in item-container directive.
How shall i pass an entire object through the attribute to the directive
HTML snippet
<p ng-repeat = "item in items">
<item-container
startcounter = 1
resetter = 'reset'
item = 'item'
name = {{item.name}} >
{{item.name}}
</item-container><br><br>
</p>
JS snippet
.directive('itemCounter',function(){
return {
controller: function() {return {}},
restrict:'E',
scope:{
item:"=",
resetter:"="
},
transclude:true,
link:function(scope,elem,attr){
scope.qty = attr.startcounter
scope.add = function(){
scope.qty++;
}
scope.remove = function(){
scope.qty--;
}
scope.addItem = function(){
console.log(attr.item);
scope.$parent.addMsg(scope.qty,attr.name)
console.log("value when submitted:" + scope.qty + "name:"+ attr.name);
scope.qty = attr.startcounter;
scope.$parent.resettrigger();
}
scope.$watch(function(attr){
return attr.resetter
},
function(newValue){
if(newValue === true){
scope.qty = attr.startcounter;
}
});
},
template:"<button ng-click='addItem();'>Add to cart</button>&nbsp&nbsp"+
"<button ng-click='remove();' >-</button>&nbsp"+
"{{qty}}&nbsp" +
"<button ng-click='add();'>+</button>&nbsp&nbsp"+
"<a ng-transclude> </a>"
}
Currently you actually aren't even passing in the name it seems. All the passing in magic happens in this part:
scope:{
resetter:"="
},
As you can see, there is no mention of name. What you need to do is add a field for item and just pass that in:
scope:{
resetter:"=",
item: "="
},
Then you can just do
<p ng-repeat = "item in items">
<item-container
startcounter = 1
resetter = 'reset'
item = item >
{{item.name}}
</item-container><br><br>
</p>
Also I'm fairly sure you dont want to be using transclude here. Look into templateUrl

How to use $compile inside an Angular directive?

I have a collection of 500 items (persons) that I am rendering in an Angular ng-repeat directive. Each item has three fields (FirstName, LastName, Company). I want the user to be able to see details / edit the items below each rendered row. I have a button (Font Awesome square-plus) that when clicked needs to show the item details / edit. I do not want to include this markup/logic within the controller because having it rendered but hidden is very slow...multiple seconds in Chrome. I assume this is due to all the watches.
To resolve the issue, I created a directive that injects the details / edit items under the current record at run-time. I attempt to $compile the markup to link it to the current scope of the ng-repeat row.
Problems.. I think I am having issues with the scope. The directive is references within the ng-repeat block (p in Persons), so I would think I would be passed the record scope in the directive link function. But I can only get the record object by getting the parent scope (scope.$parent.p instead of scope.p). I don't understand.
Also, once the $compile function is executed, I do see the person info displayed in the new details blocks. But changes are not reflected in the current record data, nor an I dismiss the details block.
Any suggestions?
Markup:
<div class="row" ng-repeat="p in Persons">
<div class="col-lg-1">
<i class="fa fa-plus-square" ng-show="!p.showDetail" manage-details></i>
</div>
<div class="col-lg-2">{{::p.FirstName}}</div>
<div class="col-lg-2">{{::p.LastName}}</div>
<div class="col-lg-2">{{::p.Company}}</div>
<div id="marker{{$index}}"></div>
<hr ng-if="!$last" />
</div>
JS:
(function () {
'use strict';
angular
.module('ngRepeatMystery', [])
.controller('TestController', TestController)
.directive('manageDetails', manageDetails);
TestController.$inject = ['$scope'];
function TestController($scope) {
$scope.Persons = [
{ 'FirstName': 'Joe', 'LastName': 'Delbridge', 'Company': 'Dow', 'showDetail': false },
{ 'FirstName': 'Tony', 'LastName': 'Ingram', 'Company': 'Samtec', 'showDetail': false },
{ 'FirstName': 'Mike', 'LastName': 'Smolinski', 'Company': 'HCHB', 'showDetail': false },
{ 'FirstName': 'Lee', 'LastName': 'Shipman', 'Company': 'Cummins', 'showDetail': false },
{ 'FirstName': 'Eric', 'LastName': 'ONeal', 'Company': 'MSD', 'showDetail': false },
];
$scope.DismissDetails = function (index) {
$scope.Persons[index].showDetail = false;
var wrappedMonkey = angular.element($document[0].getElementById('details' + index));
angular.element(wrappedMonkey).hide();
}
};
manageDetails.$inject = ['$compile', '$document', '$timeout'];
function manageDetails($compile, $document, $timeout) {
return {
restrict: 'A',
scope: {},
link: function (scope, element, attrs) {
element.bind('click', function () {
// scope.$parent !!? WAT!
scope.$parent.p.showDetail = !scope.$parent.p.showDetail;
if (scope.$parent.p.showDetail) {
var index = scope.$parent.$index;
var wrappedMarker = angular.element($document[0].getElementById('marker' + index));
var details = getDetailsTemplate(index);
wrappedMarker.replaceWith(details);
var wrappedDetails = angular.element($document[0].getElementById('details' + index));
$compile(wrappedDetails.contents())(scope.$parent);
};
});
}
};
function getDetailsTemplate(index) {
var detailsTemplate =
"<div id=\"details" + index + "\" style=\"padding: 20px;\">" +
"<div class=\"row\">" +
"<div class=\"col-lg-2\"></div>" +
"<div class=\"col-lg-8\">" +
"<label>Last Name</label>" +
"<input ng-model=\"p.LastName\" placeholder=\"Last Name\"><br/>" +
"<label>First Name</label>" +
"<input ng-model=\"p.FirstName\" placeholder=\"First Name\"><br/>" +
"<label>Company</label>" +
"<input ng-model=\"p.Company\" placeholder=\"Company\"><br/>" +
"<button class=\"btn btn-primary\" ng-click=\"p.DismissDetails($index);\">Done</button><br/>" +
"</div>" +
"<div class=\"col-lg-2\"></div>" +
"</div>" +
"</div>";
return detailsTemplate;
}
};
})()
Plunker: http://plnkr.co/edit/64TcuhaNi2JcC1hzon15
I am also open to other alternatives...
Ok, I think there are a lot of issues with your code.
I would advise against having the directive modify something outside of itself.
As I commented earlier, don't use $parent. Just pass data as attributes. And create a new scope when you call $compile to avoid polluting the existing scope.
I modified your code so it works, but it's still not pretty:
http://plnkr.co/edit/eLNxewwFzobqTkQ4867n
HTML:
<div class="row" ng-repeat="p in Persons">
<div class="col-lg-1">
<i class="fa fa-plus-square" ng-show="!showDetail" manage-details monkey="p" index="$index" show-detail="showDetail"></i>
</div>
<div class="col-lg-2">{{p.FirstName}}</div>
<div class="col-lg-2">{{p.LastName}}</div>
<div class="col-lg-2">{{p.Company}}</div>
<div id="marker{{$index}}"></div>
<hr ng-if="!$last" />
</div>
JS:
return {
restrict: 'A',
scope: {
monkey: '=',
index: '=',
showDetail: '='
},
link: function (scope, element, attrs) {
var childScope;
element.bind('click', function () {
scope.showDetail = !scope.showDetail;
if (scope.showDetail) {
childScope && childScope.$destroy();
childScope = scope.$new();
var index = scope.index;
var wrappedMarker = angular.element($document[0].getElementById('marker' + index));
wrappedMarker.html(getDetailsTemplate(index));
childScope.p = angular.copy(scope.monkey);
childScope.dismissDetails = function () {
scope.showDetail = false;
scope.monkey = angular.copy(childScope.p);
wrappedMarker.html('');
};
$compile(wrappedMarker.contents())(childScope);
};
});
}
};
function getDetailsTemplate(index) {
var detailsTemplate =
"<div id=\"details" + index + "\" style=\"padding: 20px;\">" +
"<div class=\"row\">" +
"<div class=\"col-lg-2\"></div>" +
"<div class=\"col-lg-8\">" +
"<label>Last Name</label>" +
"<input ng-model=\"p.LastName\" placeholder=\"Last Name\"><br/>" +
"<label>First Name</label>" +
"<input ng-model=\"p.FirstName\" placeholder=\"First Name\"><br/>" +
"<label>Company</label>" +
"<input ng-model=\"p.Company\" placeholder=\"Company\"><br/>" +
"<button class=\"btn btn-primary\" ng-click=\"dismissDetails();\">Done</button><br/>" +
"</div>" +
"<div class=\"col-lg-2\"></div>" +
"</div>" +
"</div>";
return detailsTemplate;
}

Angularjs Hash linking redirects to a new URL

I want to link to an element on the same page. I followed the link below but for some reason it redirecting me to the same URL with #id attached to the URL, I have html5Mode enabled.
How to handle anchor hash linking in AngularJS
directive code
var commonComponentsApp = commonComponentsApp || angular.module('cpUtils', []);
commonComponentsApp.directive("highlight", function ($location, $compile, $anchorScroll) {
'use strict';
return {
restrict: "A",
controller: function ($scope) {
$scope.scrollTo = function (id) {
console.log(id);
$location.hash(id);
$anchorScroll();
}
},
link: function (scope, element, attrs) {
var hightedText = attrs.highlight;
hightedText = hightedText.replace("<em", "<em ng-click=scrollTo('" + attrs.id + "')");
var item = '<p>' + hightedText + '</p>';
var e = angular.element(item);
e = $compile(e)(scope);
element.html(e);
}
};
});
Any help will be much appreciated. Thanks
UPDATE
Its inside a ng-repeaet
<li id="document-{{$index}}" ng-repeat="document in query.response.result" ng- mouseenter="select(document, $index)" bubble>
<div class="description-wrapper">
......
<hr />
<p highlight="{{query.response.highlighted[document.id].text[0]}}" id="{{document.id}}"></p>
.....
</div>
</li>
as you hover over a list item, it shows the complete record on the side which has the link it should scroll to.
Does your attrs.id value contain the hash? If so, it shouldn't. You only need the id value, without the hash.
Also, your ng-click=scrollTo(id) is missing quotes:
<em ng-click="scrollTo('idName')">

Resources