Nested Angular directives not working as expected - angularjs

I have been making a directive to display some HTML and handle events as it seemed sensible to keep this code reusable. This directive was placed inside a ng-repeat and worked as expected.
Now I come to add some more display logic to it: 2 modes 'grid' and 'list', and create 2 ng-repeat elements for each mode. At this point the whole thing seems to fall apart with rendering doing some unexpected stuff.
I have created a Plunkr to demonstrate: http://plnkr.co/edit/GjxOjxE7KOlvYjxCqVJj?p=preview
App.js
var app = angular.module('myApp', []);
app.directive('documentObject', [function() {
return {
restrict: 'E',
transclude: true,
scope: {
object: '=',
viewmode: '=',
},
templateUrl: 'storage-object.html',
link: function(scope, element, attrs) {
if (scope.viewMode === 'grid') {
scope.class = 'grid-view';
} else {
scope.class = 'list-view';
}
}
}
}]);
app.controller('DocumentsController', ['$scope', function($scope) {
$scope.browser = {
viewMode: 'grid',
files: [{name: 'First'}, {name: 'Second'}, {name: 'Third'}]
};
}]);
index.html
<div ng-switch="browser.viewMode" class="filebrowser">
<div ng-switch-when="grid">
<div ng-repeat="file in browser.files">
<document-object viewmode="browser.viewMode" object="file"></document-object>
</div>
</div>
<table ng-switch-when="list">
<tr ng-repeat="file in browser.files">
<document-object viewmode="browser.viewMode" object="file"></document-object>
</tr>
</table>
</div>
storage-object.html
<div ng-if="viewmode == 'grid'" class="filebrowser {{ viewmode }}">
Grid Mode: {{ viewmode }}
<span class="file-label {{ class }}">{{ object.name }}</span>
</div>
<td ng-if="viewmode == 'list'" class="filebrowser {{ viewmode }}">
List Mode: {{ viewmode }}
<span class="file-label {{ class }}">{{ object.name }}</span>
</td>
Grid mode should only display the items as a grid, and list mode as a table. What is happening is grid mode is displaying everything and list mode is just rendering one element.
What is going on here?

This happens because you are not allowed to have anything else as a direct child of a <tr> than a <td> or <th> element.
In your case you are having a <document-object> element, which causes the browser to move it around (as it is not allowed to be where it is), which in turn confuses ng-switch.
Wrapping <document-object> in <td></td> and chenging it's template (e.g. replacing <td> with <div>), solves the problem.
index.html:
...
<table ng-switch-when="list">
<tr ng-repeat="file in browser.files">
<td>
<document-object viewmode="browser.viewMode" object="file"></document-object>
</td>
</tr>
</table>
storage-object.html:
...
<div ng-if="viewmode == 'list'" class="filebrowser {{ viewmode }}">
List Mode: {{ viewmode }}
<span class="file-label {{ class }}">{{ object.name }}</span>
</div>
See, also, this updated plunkr.

Please see here http://plnkr.co/edit/aMOsKAATd4aBl0swPik1?p=preview
you missed <td> tag
<table ng-switch-when="list">
<tr ng-repeat="file in browser.files">
<td> <!--here -->
<document-object viewmode="browser.viewMode" object="file"></document-object>
</td>
</tr>
</table>

Related

ng-if not working with ng-repeat and radio button

I have the following html template:
<div ng-app ng-controller="Ctrl">
<div class="cont">
<table>
<tr><th ng-repeat="column in columns">{{column}}</th></tr>
<tr ng-repeat="(i, row) in people">
<td ng-repeat="(j, column) in columns">
<input type="radio" name="select" ng-if="column === 'id'">
<span>{{row[column]}}</span>
</td>
</tr>
</table>
</div>
</div>
Then I define my Ctrl as follow
function Ctrl($scope) {
$scope.selectedId = 'aaaaa';
$scope.columns = ['id','name', 'age'];
$scope.people=[
{
'id':'aaaaa', 'name':'rachit', 'age':11
},
{
'id':'bbbbb', 'name':'gulati', 'age':12
},
{
'id':'ccccc', 'name':'rocks', 'age': 13
}
]
}
https://jsfiddle.net/en91zuxb/
it's showing radio input for all the item, while my intention is to only show the radio button on the first column, because the 1st column is the id column... ...
It seems that your angular version is old, this was an error related to angularjs < 1.2. Try upgrading your angularjs version or use ng-show instead.
Following code worked for me.
<input type="radio" name="select" ng-show="column == 'id' ">

How do i append my attribute to a element in my Angular directive

I have a table in a template I want to populate with different data. My approach is using directives in Angular. I managed to make a template out of my table but I have no idea how to apply the value for the ng-repeat attribute from my html.
Here's a part of my index.html
<div id='unannounced' kc-item-table>
</div>
And here's a part of my template
<table class='table'>
<thead>
<tr>
<th>Name</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr ng-repeat='item in changableItemList'>
<td>{{item.name}}</td>
<td>{{item.description}}</td>
</tr>
</tbody>
</table>
Heres my directive
app.directive('kcItemTable', function() {
return {
restrict: 'E',
templateUrl: 'scripts/controllers/itemTableTemplate.html'
}
})
So in order to reuse the template I want to be able to change the
ng-repeat='item in itemList'
But I have no idea how to append it to right element.
Here is the simple explaination with your code./
Your html template -
<table class='table'>
<thead>
<tr>
<th>Name</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr ng-repeat='item in changableItemList'>
<td>{{item.name}}</td>
<td>{{item.description}}</td>
</tr>
</tbody>
</table>
Directive-With an isolate Scope
app.directive('kcItemTable', function() {
return {
restrict: 'E',
scope :{
itemList :'='
},
templateUrl: 'scripts/controllers/itemTableTemplate.html'
}
})
You can use directive with different list --
<div id='unannounced' kc-item-table item-list='ItemList1'>
</div>
<div id='unannounced' kc-item-table item-list='ItemList2'>
</div>
What you are trying to do is a very basic feature of AngularJS: data-binding to directives.
Check out the documentation about directives: https://docs.angularjs.org/guide/directive
Here is a very basic example forked from the above docs:
Main template:
<div my-customer name="naomi"></div>
<div my-customer name="boby"></div>
Directive:
.directive('myCustomer', function() {
return {
scope: {
name: "#"
},
template: 'Name: {{name}}'
};
});
http://plnkr.co/edit/r9tIzwxCFyEyAU3NX0G1?p=preview
To clarify, what you need in your case is a "scope" property on your directive. You will be able to pass the scope values through the DOM element attributes.
Thats easy just add this to your div where you add your attribute directive.
<div ng-controller="YourCustomController" id='unannounced' kc-item-table>
</div>
then in YourCustomController you would put a $scope property called.
$scope.changableItemList;
Or if you want multiple of these directives on the same page you can work with an isolated scope and do :
<div id='unannounced' kc-item-table customList='customList2'/>
<div id='unannounced' kc-item-table customList='customList1'/>
and in your directive do:
//you will need a controller above this which has $scope.customList declared
app.directive('kcItemTable', function() {
return {
restrict: 'E',
scope :{
customList :'=' //isolated scope with customList passed in
},
templateUrl: 'scripts/controllers/itemTableTemplate.html'
}
})

How to watch ng-model included in ng-repeat?

This is my html code:
<table>
<tr ng-repeat="park in parkOptions.parks">
<td>
<label for="{{park.park_id}}">
<input id="{{park.park_id}}" type="radio" name="connection" ng-model="$parent.currentPark" value="{{park.park_id}}" ng-click="selectValue('park',park)"/>{{park.park_name}}
</label>
</td>
</tr>
</table>
How can i watch the changes in my model( I mean i want to see in $watch another park when i click radio button)
I've got a working version to help you - Fiddle.
HTML
<div ng-app ng-controller="ParentCtrl">
<div ng-controller="ChildCtrl">
<table>
<tr ng-repeat="park in parkOptions.parks">
<td>
<label for="{{park.park_id}}">
<input id="{{park.park_id}}" type="radio" name="connection" ng-model="parkOptions.currentPark" value="{{park.park_id}}" ng-click="selectValue(park)"/> {{park.park_name}}
</label>
</td>
</tr>
</table>
</div>
</div>
JS
function ParentCtrl($scope) {
}
function ChildCtrl($scope) {
$scope.parkOptions = {};
$scope.parkOptions.parks = [
{park_id: '01', park_name: 'England Park'},
{park_id: '02', park_name: 'France Park'}
];
$scope.$watch('parkOptions.currentPark', function(newValue) {
if (newValue != undefined) {
console.log(newValue);
}
});
}
This may not be exactly what you want (as I see $parent in your code) but we can come to the right solution with any further information from you.
I don't use $parent and prefer sending and receiving events.

How to use Angular directive in Salesforce

How to use angular directive in Salesforce
My directive html page
<apex:page showHeader="false" sidebar="false" standardStylesheets="false" applyHtmlTag="false">
<td>
<div class="address-container">
<p data-once-text="addr.company"></p>
<p><span class="once-addr" data-once-text="addr.city"></span>, <span class="once-addr" data-once-text="addr.state"></span></p>
<p>Remove</p>
</div>
</td>
</apex:page>
My directive call
mydirective.directive('shipmentAddress', ['CartService', function(CartService){
return{
scope: true,
replace: true,
templateUrl: 'apex/shipment_addresses_tplhtml',
controller: function($scope){
this.addressInfo = $scope.addr;
$scope.removeAddress = function(accountId, addrId){
if(confirm('Are you sure you want to remove this shipping address?')){
CartService.deleteCartAddress(accountId, addrId).then(function(response){
console.log(response);
$scope.$emit('refreshCart');
});
}
}
}
}
}]);
in my index.html
<tr class="addresses">
<td shipment-address ng-repeat="addr in acc.addresses" ng-if="addr.added == true"></td>
</tr>
But I am getting error in SaleForce/VisualForce as Attribute name "shipment-address" associated with an element type "td" must be followed by the ' = ' character.
Just do it like this:
<tr class="addresses">
<td shipment-address="true" ng-repeat="addr in acc.addresses" ng-if="addr.added == true"></td>
</tr>
That should remove the error. Empty string did not work for me but some value there will do it.

AngularJs To Do list Directive

Edit
JS Fiddle added JSFiddle
I'm trying to learn AngularJs and so I'm trying to take the "To Do" application to the next level.
Instead of an item being done or not, I want to have three states: (status)
0 = Waiting
1 = Working
2 = Completed
I also want to have a priority:
1 = High
2 = Medium
3 = Low
Here is my data:
[
{"taskId":1,"description":"Test 1.","priority":1,"status":0}
{"taskId":2,"description":"Test 2.","priority":1,"status":0}
{"taskId":3,"description":"Test 3.","priority":1,"status":1}
]
I've got three lists that displays each status wonderfully. When I move an item from one status to another, it goes away from the original list and appears in the appropriate list for the new status.
However, I am now working with directives and I want to turn those three separate lists into a single directive and it's really kicking my butt.
This is the hard-coded version of my To-Do list that displays the To-Do items in a 'Waiting' status.
<h3 style="display: inline-block;">Waiting</h3>
<div class="label" style="display: inline-block;" ng-hide="getStatusCount(0) == 0">{{getStatusCount(0)}}</div>
<div class="infoTableWrap">
<table class="infoTable">
<thead>
<tr>
<th style="text-align: left">Description</th>
<th style="text-align: right">Priority</th>
<th style="text-align: right">Status</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="task in tasks.items | filter:{status:0}">
<td style="text-align: left">{{task.description}}</td>
<td style="text-align: right">
<select name="ddlPriority" ng-model="task.priority" ng-options="option.id as option.name for option in priorityOptions"></select>
</td>
<td style="text-align: right">
<select name="ddlStatus" ng-model="task.status" ng-options="option.id as option.name for option in statusOptions"></select>
</td>
</tr>
</tbody>
</table>
</div>
Here is my attempt at making that a directive
<script type="text/template" id="taskListTemplate">
<h3 style="display: inline-block;">{{header}}</h3>
<div class="label" style="display: inline-block;" ng-hide="getStatusCount(2) == 0">{{getStatusCount(2)}}</div>
<div class="infoTableWrap">
<table class="infoTable">
<thead>
<tr>
<th style="text-align: left">Description</th>
<th style="text-align: right">Priority</th>
<th style="text-align: right">Status</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="task in taskList | filter:{status:2}">
<td style="text-align: left">{{task.description}}</td>
<td style="text-align: right">
<select name="ddlPriority" ng-model="task.priority" ng-options="option.id as option.name for option in priorityOptions"></select>
</td>
<td style="text-align: right">
<select name="ddlStatus" ng-model="task.status" ng-options="option.id as option.name for option in statusOptions"></select>
</td>
</tr>
</tbody>
</table>
</div>
</script>
Here is how I'm calling it:
div task-list-directive header="Completed Tasks" status-type="2" task-list="tasks.items"></div>
And here is the directive code
.directive("taskListDirective", function() {
return {
restrict: "EA",
scope: {
header: "#",
taskList: "=",
statusType: "#"
},
template: function () {
return angular.element(document.querySelector("#taskListTemplate")).html();
}
};
})
What works:
Header (the header is coming across just fine)
taskList (the task list is coming across just fine)
What doesn't work in the directive:
Setting the statusType so I don't have to hard-code the 2 in the template
The lists come up empty, but I figure that's a scope thing and I was going to turn those into directives as well.
Thanks,
Duane
Here is my JSFiddle
I included the form to add new items...and there are two hard coded lists for "Waiting" and "Working"...and I commented out the html for the directive as I couldn't get that to work in JSFiddle.
You should be able to reference statusType in your directive and use it to filter on.
ng-repeat="task in taskList | filter:{status:statusType}"
and
{{getStatusCount(statusType)}}
Since the directive uses isolated scope, you can't reference priorityOptions and statusOptions that are in the controller. You could pass them in the same way you do with taskList. But you could also define a controller for the directive and put those things there. Here I've done both. Since you also use priorityOptions in your form, I kept it in the controller and passed it in to the directive. Since statusOptions is only used in the directive, I moved it out of the controller.
.directive("taskListDirective", function () {
return {
restrict: "EAC",
scope: {
header: "#",
taskList: "=",
priorityOptions: "=",
statusType: "#"
},
template: function () {
return angular.element(document.querySelector("#taskListTemplate")).html();
},
controller: function ($scope) {
$scope.getStatusCount = function (statusType) {
return countStatusTypes(statusType);
}
//Private Methods
function countStatusTypes(statusType) {
var count = 0;
angular.forEach($scope.taskList, function (item) {
if (item.status === statusType * 1) {
count++;
}
});
return count;
}
$scope.statusOptions = [{
name: 'Waiting',
id: 0
}, {
name: 'Working',
id: 1
}, {
name: 'Completed',
id: 2
}];
}
};
});
Here's your JSFiddle updated to use the directly exclusively.

Resources