Angular table row directive not rendering inside table - angularjs

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>

Related

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.

element.replaceWith not working as expected in AngularJS Directive when the model is updated

So I have an array with objects:
var columns= [{label:"Column 1",value:"val 1"},{label:"Column 2",value:"val 2"}];
I have a directive as attribute of table data element.
<td ng-repeat="column in columns" my-directibe column="column.label", value="column.value">
This is the directive:
.directive("queryResult", function ($compile) {
return {
scope: {
value:"=",
column:"="
},
restrict: "AE",
link:function(scope,elemnt){
var tableData=angular.element('<td ng-bind="value"></td>');
elemnt.replaceWith($compile(tableData)(scope))
}
}
})
The expected result should be like and it's is like:
<td class="ng-scope">val 1</td>
The problem comes when I add additional result to the array and the array becomes with three elements. Then this works only for the last added element for the first two elements it's like:
<td query-result ng-repeat="column in columns" value="value" column="column" class="ng-scope ng-isolate-scope"></td>
<td class="ng-scope">val 1</td>
<td query-result ng-repeat="column in columns" value="value" column="column" class="ng-scope ng-isolate-scope"></td>
<td class="ng-scope">val 2</td>
<td class="ng-scope">val 3</td>
So these additional td creates additional columns with blank values and the whole table goes wrong.
As far as I know, elemnt.replaceWith will execute only once, so binding will not work at all.
Try this:
return {
scope: {
value:"=",
column:"="
},
restrict: "AE",
link:function(scope,elemnt){
var tableData=angular.element('<td ng-bind="value"></td>');
scope.$watch('value', function() {
elemnt.replaceWith($compile(tableData)(scope));
});
}
}

Table row as directive displays out of table context in 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.

AngularJS dynamic table with multiple headers based on hierarchical collections

I'm trying to create a table with two rows for header, based on a hierarchical collection. I've found that ng-repeat can't do that, and I'm trying to make the job with a directive and Angular.forEach.
Here the jsfiddle : http://jsfiddle.net/echterpat/RxR2M/9/
But my problem is that when I made the first display of table, collections were empty (they were filled by REST call afterward), so link method was not able to build all the table. And then when REST updated collections, link method was not called anymore...
Any idea to call directive after REST answer, and to fill my table with collected data ?
Directive with angular.forEach :
myApp.directive('myTable', ['$compile', function (compile) {
var linker = function (scope, element, attrs) {
var html = '<table BORDER="1"><thead>';
html += '<tr><th><button type="submit" class="btn btn-info">Back</button></th>';
angular.forEach(scope.myFirstCol, function (item, index) {
html += '<th colspan="{{item.mySecondCol.length}}" id="item_{{item.id}}"> {{item.name}}</th>';
});
html += '</tr><tr><th><input type="checkbox" ng-model="selectAll"/></th>';
angular.forEach(scope.myFirstCol, function (item2, index) {
angular.forEach(item2.mySecondCol, function (item3, index) {
html += '<th id="headerStep_{{item3.id}}">{{item3.name}}</th>';
});
});
html += '</tr></thead>';
html += '</table>';
element.replaceWith(html);
compile(element.contents())(scope);
};
return {
restrict: 'E',
rep1ace: true,
link: linker,
scope: {
myFirstCol: '=myFirstCol'
}
};
}]);
If you dont want to use a directive, you can go for nested tables:
<table BORDER="1">
<thead>
<tr>
<th>
<button type="submit" class="btn btn-info">Back</button>
</th>
<th ng-repeat="item in myFirstCol">{{item.name}}</th>
</tr>
<tr>
<th>
<input type="checkbox" ng-model="selectAll" />
</th>
<th ng-repeat="item in myFirstCol">
<table BORDER="1">
<tr>
<th ng-repeat="item2 in item.mySecondCol">
{{item2.name}}
</th>
</tr>
</table>
</th>
</tr>
</thead>
</table>

What is angular way to transform a table cell

The problem is as follows. I have a directive which builds some table, and i use ng-repeat to construct table rows dynamicly. Each row has two buttons for editing and deleting accordingly. I look for action with ng-click, but how then transform a cell with static text into a cell with an input field in angular way? I know how to do it with jquery. I look around with ng-switch, but i can't get it works inside ng-repeat on a table cell as expected. My code:
JS
order.directive('orderLines',function($http,calculateTotalQtyService,$compile){
return {
restrict: 'A',
link: function($scope,element, attrs){
$scope.editLine = function(line,order_id){
// alert ('Edit'+line.id);
some code here to perform transformation
}
$scope.deleteLine = function(idx){
var line_to_delete = $scope.order.lines[idx]
$http.post('/delete/line?id='+line_to_delete.id +'&order_id='+$scope.order.id).success(function(data){
$scope.order.lines.splice(idx,1);
$scope.order = data.ord;
$scope.order.customer = data.customer;
$scope.order.lines = data.lines;
var res = calculateTotalQtyService.set(data.lines);
$scope.total_kg = res[0];
$scope.total_piece = res[1];
});
}
},
templateUrl:'/assets/fragments/order/order_lines.html',
replace: true
}
});
and HTML
<div class="span12 fiche" id="lines">
<table class="table table-bordered" id="order_lines">
<thead class="header_lines">
<th>S.N.</th>
<th>Ref.</th>
<th>Label</th>
<th>Tva</th>
<th>Qty</th>
<th>Unite</th>
<th>Prix HT</th>
<th>Total HT</th>
<th></th>
</thead>
<tbody ng-switch="lines.length">
<tr id="no_record" ng-switch-when="0"><th colspan="9" style="text-align: center" >No records</th></tr>
<tr ng-repeat="line in order.lines">
<td>{{$index+1}}</td>
<td class='line-ref'>{{line.product_ref}}</td>
<td>{{line.label}}</td>
<td class='line-tva'>{{line.tva}}</td>
<td class='line-qty'>{{line.qty}}</td>
<td class='line-unity'>{{line.unity}}</td>
<td class='line-prix_ht'>{{line.prix_ht}}</td>
<td>{{line.prix_ht*line.qty}}</td>
<th class='control-buttons'>
<button class='btn editline' ng-click="editLine(line,order.id)"><i class='icon-edit'></i></button>
<button class='btn deleteline' ng-click="deleteLine($index)"><i class='icon-trash'></i> </button>
</th>
</tr>
</tbody>
</table>
</div>
So html is a template, that i use in directive. How perform transformation ? with ng-switch? but how, or there are other solutions ? I want to avoid jquery if it's possible. Any help would be appreciated.
So I got it with a custom directive, inspired by #laut3rry. For those who would be interested this is my solution:
Directive:
order.directive('editable', function(){
return {
restrict : 'E',
replace : true,
template: '<div><span ng-hide="editMode">{{line.qty}</span><input class="span1" ng-show="editMode" type="text" ng-model="line.qty"/></div>',
link : function(scope, element, attrs){
}
}
});
And in HTML, in my example it's for a qty cell only, that I need to change, but we can use it with any cell and with a little bit of modification, for example by passing cell value in the attribute of the directive and it can be universal:
<td class='line-qty'><editable></editable></td>
In my controller I initialize $scope.editMode = false the cell isn't editable by default and then in the ng-click="editLine()" handler we change $scope.editMode = true and the cell transforms in to the input field. So directives and directives and once more directives.... :)
For those who is interested, here the link to the plunk plunk

Resources