Table row as directive displays out of table context in angularjs - angularjs

I have a table and each row is to displayed using a directive and ng-repeat.
Unfortunately, the rows are rendered out of the table context.
Please run the fiddle here: http://jsfiddle.net/nu1tu9qL/4/
The directive:
.directive('myrow', [function () {
return {
restrict: 'E',
replace: true,
scope: {
rowdata: "="
},
template: '<tr><td>{{rowdata.a}}</td><td>{{rowdata.b}}</td></tr>',
};
}]);
The HTML:
<table>
<thead>
<th>A</th>
<th>B</th>
</thead>
<tbody>
<myrow ng-repeat="d in data" rowdata="d"></row>
</tbody>
</table>

Without going deep into Angular's source code to diagnose this issue, I'd say that this is likely an artifact of the fact that you are using an unexpected element within <tbody>. <tbody> accepts only <tr> (for the purpose of this question).
And so, change your myrow directive to be an attribute, and make the template only add <td>s:
<tbody>
<tr myrow rowdata="d" ng-repeat="d in data"></tr>
</tbody>
and the directive:
.directive("myrow", function(){
return {
scope: {
rowdata: "=?"
},
template: "<td>{{rowdata.a}}</td><td>{{rowdata.b}}</td>"
};
});
(you could even use myrow as the scope, rather than rowdata)
EDIT:
For the sake of verification of the assumption above, I reproduced the behavior with the following:
<table>
<tr><th>Column 1</th></tr>
<tbody>
<myrow></myrow>
</tbody>
</table>
and using jQuery (something similar to what Angular would have done):
$(function(){
$("myrow").replaceWith("<tr><td>A</td></tr>");
});
and the result is that the inserted row appearing above <table>, as is the case in the original question. In fact, <myrow> is moved outside of table (in Chrome and IE - not sure if this is browser-specific behavior) even before the replaceWith happens.

Related

Angular directive doesn't work on table

I have the following table:
<table>
<thead>
<tr>
<th>Uno</th>
<th>Dos</th>
</tr>
</thead>
<!--directive template should be here-->
<double-rows></double-rows>
<!--/directive template should be here-->
</table>
The directive is defined as:
angular.module('app', []);
angular.module('app').directive('doubleRows', function () {
return {
templateUrl: 'template.html',
restrict: 'E',
scope: {
row: '='
}
}
});
As simple as it looks, it doesn't render properly. e.g:
<double-rows class="ng-isolate-scope">
Cell1
Cell2
</double-rows>
<table>
<thead>
<tr>
<th>Uno</th>
<th>Dos</th>
</tr>
</thead>
<!--directive template should be here-->
<!--/directive template should be here-->
</table>
You can see the full code in here: https://plnkr.co/edit/0Z65aK?p=preview
How to make it work?
This isn't exactly an angular problem but more to do with how the browser decides to interpret the HTML you wrote. Certain tags aren't allowed to be nested in other tags, this is particularly prevalent within lists (ul, or li) and tables (also applies to nested links and a few other things).
Instead of using restrict E if you use restrict A and use an element type that is allowed in the table then it works (I also used replace:true on your directive so it replaces the original element it's applied to instead of being a child of it)
https://plnkr.co/edit/fgRzZU?p=preview
angular.module('app').directive('doubleRows', function () {
return {
templateUrl: 'template.html',
restrict: 'A',
replace:true,
scope: {
row: '='
},
controller: function () {
console.log('hi');
}
}
});
The HTML
<table>
<thead>
<tr>
<th>Uno</th>
<th>Dos</th>
</tr>
</thead>
<!--directive template should be here-->
<tbody double-rows>
</tbody>
<!--/directive template should be here-->
</table>

Nesting a directive inside of a table

Is this possible? I want the ability to tidily swap out a table body depending on user input, so i just threw this little *test together to see if it would work, but it's loading all wonky, with the body preceding and exterior to the table itself in the DOM even though I nest it appropriately in my html. so my questions are thus:
1) what's this behavior all about? and
2) can i achieve what I want the way i'm going about it?
*simple fiddle
html:
<div ng-app="myApp" ng-controller="myController">
<table class="table">
<thead>
<tr>
<th>Month</th>
<th>Savings</th>
</tr>
</thead>
<my-directive></my-directive><!-- this should be the tbody -->
<tfoot>
<tr>
<td>Sum</td>
<td>$180</td>
</tr>
</tfoot>
</table>
</div>
js:
var app = angular.module("myApp", []);
app.directive("myDirective", function () {
return {
restrict: 'E',
template: '<tbody><tr><td>January</td><td>$100</td></tr><tr><td>February</td><td>$80</td></tr></tbody>',
};
});
You are currently rendering markup within a <my-directive></my-directive> element, which is messing up the table layout.
Instead, change your directive to an attribute-based directive and place it on the <tbody> element, replacing the content..
Template
</thead>
<tbody my-directive></tbody><!-- this should be the tbody -->
<tfoot>
Directive
return {
restrict: 'A',
template: '<tr><td>January</td><td>$100</td></tr><tr><td>February</td><td>$80</td></tr>'
};
See working fiddle.

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'
}
})

AngularJS directive with ngTransclude not showing {{bound}} content

I'm attempting to create an Angular directive that creates a standardised structure of a table that I wish to use around my application.
I want to specify the structure of the tr when I declare the directive in HTML so that I can have different layouts depending on the data that is passed in. However, I can't seem to get the content of ng-transclude to actually render.
Plunker: Example
I'd like the following:
<custom-table table="data">
<td>
{{row.Username}}
</td>
<td>
{{row.FirstName}}
</td>
<td>
{{row.LastName}}
</td>
</custom-table>
to be injected into the within the template.
How do I get the {{row.Username}} etc tags to resolve within the ng-transclude in the angular directive?
Edit1: I think this is a similar question that I've just found, although most top voted answer seems to recommend avoid using table, tr, td etc within directives :\
This doesn't answer your question, but I think it is a more generic way of doing what you want.
First pass the list of columns you want to display to your directive:
<custom-table table="data" columns="columns">
</custom-table>
In your controller:
app.controller('MainCtrl', function($scope) {
$scope.data = [
{Username: "Test1", FirstName: "Bill", LastName: "Jones"},
{Username: "Test2", FirstName: "Sophie", LastName: "O'Grady"},
{Username: "Test3", FirstName: "Tim", LastName: "Cross"}
];
$scope.columns = ["Username", "FirstName", "LastName"]
});
In your directive:
app.directive('customTable', ['$compile', function($compile){
return {
restrict: 'E',
templateUrl: 'tableTemplate.html',
scope: {
table: '=',
columns: '='
}
};
}]);
And finally change your template to:
<div>
<table>
<thead>
<tr>
<th ng-repeat="col in columns">{{ col }}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in table">
<td ng-repeat="col in columns">
{{ row[col] }}
</td>
</tr>
</tbody>
</table>
</div>
And here's the updated plunker: http://plnkr.co/edit/dYwZWD2jB2GsmnvmuSbT
I found a work-around which solves the problem for me.
I've updated the plunker with a working example. I had to create a directive:
app.directive('myTransclude', function() {
return {
compile: function(tElement, tAttrs, transclude) {
return function(scope, iElement, iAttrs) {
transclude(scope.$new(), function(clone) {
iElement.append(clone);
});
};
}
};
});
I found the issue here within the comments.
I also had to update the directive so it uses a CSS/div based table rather than using an actual HTML table.

Angular table row directive not rendering inside table

I am trying to add a row "isrcrow" directive to a table as follows:
<table class="table">
<thead><tr>
<th>Artist Name</th>
<th>Track Title</th>
<th>Version</th>
<th>Track Duration</th>
<th>Recording Year</th>
<th></th>
</tr>
</thead>
<tbody>
<isrcrow></isrcrow>
</tbody>
</table>
Here is the directive:
(function() {
var isrcorderapp;
isrcorderapp = angular.module("isrcorderapp", []);
isrcorderapp.controller("isrcordercontroller", function($scope, $http) {
return $scope.recordingTypes = [
{
type: 'Single'
}, {
type: 'Album'
}, {
type: 'Live'
}, {
type: 'Concert'
}, {
type: 'Instrumental'
}
];
});
isrcorderapp.directive("isrcrow", function() {
return {
restrict: 'E',
template: '<tr>\
<td><input id="artist" ng-model="name"/></td>\
<td><input id="track"/></td>\
<td><select id="isrctype" ng-model="isrctype" ng-change="setState(state)" ng-options="s.type for s in recordingTypes" class="ng-pristine ng-valid"></select></td>\
<td><input id="duration"/></td>\
<td><input id="year"/></td>\
<td><input type="button" value="Add ISRC" onclick="AddIsrc()" class="btn btn-small btn-success" />\
<input type="button" value="Delete" onclick="RemoveIsrc()" class="btn btn-small btn-danger" />\
</td>\
</tr>',
scope: {
name: '='
},
link: function(scope, element, attr) {}
};
});
}).call(this);
The problem I am experincing is the isrcrow directive doesnt render inside the table body. Its rendered outside and above the table:
Does anyone knows what could be causing this behaviour?
Adding a summary of my comments as an answer since it appeared to have helped the OP. :-)
As GregL points out, omitting replace: true in a directive with restrict: 'E' and <tr> as the root template node will result in invalid markup, giving rise to the incorrect rendering of the row.
However, for those using a version of Angular prior to 1.2.13 (romantic-transclusion), this solution will not be applicable due to an issue that has been noted.
A work around would be to instead to use the directive as an attribute (i.e. restrict: 'A') and appropriately modify the template such that <tr> is no longer the root template node. This will allow replace: true to be used.
I would guess that this is because you have not specified replace: true for the isrcrow directive. As a result, the final markup would look like:
<isrcrow>
<tr>
<td>...</td>
...
<td>...</td>
</tr>
</isrcrow>
Which will be a direct child of a <tbody>, which is invalid markup. As a result, most modern browsers (e.g. Chrome, and also Firefox, I believe) will try to "fix" your markup to be valid by moving the <isrcrow> tag outside of the table.
Instead, if you add replace: true to your directive specification, the <isrcrow> element won't be rendered, and the browser should see only valid markup and not try to "fix" it.
The previous answers are correct, but I found them a bit hard to understand/apply, so summarized how I solved it with their help:
The table
<tbody>
<tr isrcrow ng-repeat="..."></tr>
</tbody>
The isrcow directive template (without tr, no single root)
<td></td>
<td></td>
...
The isrcrow directive with
restrict: 'A'
replace: false
The end results is
<tbody>
<tr isrcrow>
<td></td>
<td></td>
...
</tr>
<tr isrcrow>
<td></td>
<td></td>
...
</tr>
...
</tbody>

Resources