What is angular way to transform a table cell - angularjs

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

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.

limit the character to 10 for filter text-field - ngTable

I have created an application using angularjs and ngTable with filer feature, the application is working fine also the filtering, but the filtering text-field should accept only 10 characters according to my requirement
Can anyone please tell me how to limit the character to 10 for that text-field, my code is as given below:
JSFiddle
script
$scope.tableParams = new ngTableParams({
page: 0,
count: 0
}, {
total: 0,
counts:[],
getData: function ($defer, params) {
// use build-in angular filter
var orderedData = params.filter() ? $filter('filter')(data, params.filter()) : data;
$defer.resolve(orderedData);
}
});
html
<div ng-app="main" ng-controller="DemoCtrl">
<table ng-table="tableParams" show-filter="true" class="table">
<tr ng-repeat="user in $data">
<td data-title="'Name'" filter="{ 'name': 'text' }">{{user.name}}</td>
<td data-title="'Age'">{{user.age}}</td>
</tr>
</table>
</div>
You can't do that easily with the directives provided by ng-table.
In order to achieve that, you must create your own input filterer, apply it on the ng-repeat and use a simple directive which will limit the caracteres of the input field.
Here is a working example, hope it will help you.
fiddle
HTML:
<div ng-app="main" ng-controller="DemoCtrl">
<table ng-table="tableParams" class="table">
<!-- first row with custom input filterer using the charLimit directive -->
<tr>
<td data-title="'Name'"><input char-limit="10" ng-model="textFilter"/></td>
<td data-title="'Age'"></td>
</tr>
<!-- declare the filter on your ng-repeat directive -->
<tr ng-repeat="user in $data | filter: { 'name': textFilter }">
<td>{{user.name}}</td>
<td>{{user.age}}</td>
</tr>
</table>
</div>
DIRECTIVE:
directive('charLimit', function(){
return {
scope: {
limit: '#charLimit',
model: '=ngModel'
},
require: 'ngModel',
restrict: 'A',
link: function($scope, $node) {
var limit = $scope.limit ? parseInt($scope.limit, 10) : 0;
$scope.$watch('model', function(val) {
if(limit>0 && !isNaN(limit))
$scope.model = $scope.model.slice(0,limit);
});
}
};
})
EDIT
An html5 attribute called maxlength can do it for you so you can avoid the directive and simply add this attribute to your input field

Add new element between table rows by click on item

I have a table with some items. I would like by click on item open a list of comments to this item.
I use directive for this aim and append data to my element. The issue is that data is appended to the right side of the table instead of put it between rows. I try to play with open\closing tr tag but it doesn't help either.
My html page:
<div id="content_people" ng-show="active=='people'" ng-controller="DevsListCtrl">
<table ng-table="tableParams" class="table">
<tr feedback-holder ng-repeat="d in $data">
<td add-new-team-member data-title='name' data-value="{{d.name}}" bs-tooltip="tooltip">{{d.name}}</td>
<td data-title='age'>{{d.age}}</td>
<td data-title='grade'>{{d.grade}}</td>
<td data-title='job'>{{d.job}}</td>
</tr>
</table>
</div>
My js directive:
mainApp.directive("feedbackHolder", ['$compile', 'teamSharedObj', function ($compile, teamSharedObj) {
return {
restrict: 'A',
link: function( scope, element, attrs ) {
element.bind('click', function() {
console.log("Click on feedback");
var el = angular.element('</tr><tr><div><input type="submit" value="Refresh"></div>');
$compile(el)(scope);
element.append(el);
});
}
}
}])
Could you advise me a right way to do this? Thanks.
If you want to insert your new content between rows, use element.after() instead of element.append
element.after(el);
Also the element you insert should look like this:
var el = angular.element('<tr><td colspan="4"><input type="submit" value="Refresh"></td></tr>');
You probably just need colspan like :
var el = angular.element('</tr><tr><td colspan="4"><input type="submit" value="Refresh"></td>');

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>

setting ng-href in <tr> elements

The following code makes the client.name an anchor on each client in clients. I am interested in having the entire <tr> element be that link however. ng-href does not work on the <tr> element.. what can I do so that the entire row is a single link instantiated by ng-href?
<tr ng-repeat="client in clients">
<td><a ng-href="#/user/{{client.tagid}}">{{client.firstname}}</a></td>
<td>{{client.lastname}}</td>
<td>{{client.inumber}}</td>
</tr>
What I am looking to do is something like this.. which of course does not work..
<a ng-href="#/user/{{client.tagid}}">
<tr ng-repeat="client in clients">
<td>{{client.firstname}}</td>
<td>{{client.lastname}}</td>
<td>{{client.inumber}}</td>
</tr>
</a>
OR
<tr ng-repeat="client in clients" ng-href="#/user/{{client.tagid}}">
<td>{{client.firstname}}</td>
<td>{{client.lastname}}</td>
<td>{{client.inumber}}</td>
</tr>
You can use an ng-click (instead of onClick) as Jason suggests as well.
Something like:
HTML
<tr ng-repeat="client in clients" ng-click="showClient(client)">
<td><a ng-href="#/user/{{client.tagid}}">{{client.firstname}}</a></td>
<td>{{client.lastname}}</td>
<td>{{client.inumber}}</td>
</tr>
Controller
$scope.showClient = function(client) {
$location.path('#/user/' + client.tagid);
};
And styling to make it show as an clickable element (wont work in IE7)
CSS
tr {
cursor: pointer;
}
// or
[ng-click] {
cursor: pointer;
}
I wrote a directive so that you can simply write:
<tr ng-repeat="client in clients" such-href="#/user/{{client.tagid}}">
The source:
app.directive('suchHref', ['$location', function ($location) {
return{
restrict: 'A',
link: function (scope, element, attr) {
element.attr('style', 'cursor:pointer');
element.on('click', function(){
$location.url(attr.suchHref)
scope.$apply();
});
}
}
}]);
I use my own Angular directive that automatically wraps every cell in the row with a link.
The advantages are:
You don't duplicate code.
There is a regular link in every cell so things like "Open in new tab" (middle button or CTRL+click) works as expected (in opposite of the ng-click version).
HTML usage:
<tr row-href="#/user/{{client.tagid}}">
<td>...</td>
<td>...</td>
</tr>
Directive code (in TypeSript):
export class RowHrefDirective implements ng.IDirective {
constructor(private $compile: ng.ICompileService) {
}
restrict = "A";
scope = {
rowHref: "#rowHref"
};
link = (scope: Scope, element: ng.IAugmentedJQuery, attrs: ng.IAttributes): void => {
const cells = element.children("td[skip-href!='yes'],th[skip-href!='yes']");
cells.addClass("cell-link");
for (const cell of cells.toArray()) {
const link = jQuery(`<a ng-href="{{ rowHref }}"></a>`);
this.$compile(link)(scope);
jQuery(cell).prepend(link);
}
}
}
Required CSS code (to fill the whole cell with the link):
td.cell-link,
th.cell-link {
position: relative;
}
td.cell-link a,
th.cell-link a {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
}
This is a CSS and HTML issue, not specific to AngularJS. The only allowed child of a <tr> is a <td>, and so you need to wrap the content of each cell in an anchor. You also need to make the anchor a block element to make it the full height/width of its container:
<tr ng-repeat="client in clients">
<td>
<a style="display: block;" ng-href="#/user/{{client.tagid}}">
{{client.firstname}}
</a>
</td>
<td>
<a style="display: block;" ng-href="#/user/{{client.tagid}}">
{{client.lastname}}
</a>
</td>
<td>
<a style="display: block;" ng-href="#/user/{{client.tagid}}">
{{client.inumber}}
</a>
</td>
</tr>
<tr ng-repeat="client in clients" ng-href="#/user/{{client.tagid}}">
<td>{{client.firstname}}</td>
<td>{{client.lastname}}</td>
<td>{{client.inumber}}</td>
</tr>
This is with the referrence to the options provided which may work.
I think this binds the entire row with the each field in the row. but is not clickable. how to do that. i mean we should be able to click so that another view/module can be open.
As requested by #sfs, here’s the solution which we’re using for ui-sref (Angular 1.5; TypeScript code, apologies for any inconvenience).
Credits: The code is based on the awesome answer by Martin Volek:
import { IDirective, IDirectiveFactory, ICompileService, forEach, element } from 'angular';
export default class RowUiSrefDirective implements IDirective {
restrict = 'A';
scope = { rowUiSref: '#rowUiSref' };
constructor(private $compile: ICompileService) { }
link = (scope, elm, attrs) => {
if (elm[0].tagName !== 'TR') {
throw new Error('This directive should only be used in <tr> elements.');
}
forEach(elm.children(), (cell) => {
if (cell.attributes['skip-href'] && cell.attributes['skip-href'].value !== 'false') {
return;
}
cell.className += ' cell-link';
let link = element('<a ui-sref="{{rowUiSref}}"></a>');
this.$compile(link)(scope);
element(cell).prepend(link);
});
};
static factory(): IDirectiveFactory {
let directive = ($compile: ICompileService) => new RowUiSrefDirective($compile);
directive.$inject = ['$compile'];
return directive;
};
}
Directive initialization:
import { module } from 'angular';
import RowUiSrefDirective from './rowUiSref';
module('app').directive('rowUiSref', RowUiSrefDirective.factory());
Example usage:
<table>
<tr ng-repeat="item in itemController.items"
row-ui-sref="state.item({itemId: '{{item.id}}'})">
<td>{{item.name}}</td>
<td>{{item.label}}</td>
</tr>
</table>
An ugly solution would be to just have 1 table cell which contains the link, then within that add another table with a table row and the other cells. So it would look like;
<tr ng-repeat="client in clients">
<a ng-href="#/user/{{client.tagid}}">
<table>
<tr>
<td>{{client.firstname}}</td>
<td>{{client.lastname}}</td>
<td>{{client.inumber}}</td>
</tr>
</table>
</a>
</tr>
I do not agree with using tables for layout!
However, you are using JavaScript and angularjs, so you would be just as good adding a click event to the table row which sends the user to the url via window.location e.g.
<tr ng-repeat="client in clients" ng-click="ChangeLocation([yoururl])">
<td>{{client.firstname}}</td>
<td>{{client.lastname}}</td>
<td>{{client.inumber}}</td>
</tr>
Then have a function within your $scope to handle this;
$scope.ChangeLocation = function(url){
window.location = url;
}
Try for this...
HTML --->
<ul ng-repeat ="item in itemList ">
<li><a data-ng-href="{{getUrl(item)}}">{{item.Name}}</a></li>
</ul>
JS --->
$scope.getUrl = function (item) {
return '/<give your path here>/' + item.ID;
};
I adapted Martin Volek's Typescript code to make an AngularJS 1.x directive:
app.directive('rowHref', function ($compile)
{
return {
restrict: 'A',
link: function (scope, element, attr)
{
scope.rowHref=attr.rowHref;
var cells = element.children("td");
angular.forEach(cells, function (cell)
{
$(cell).addClass("cell-link");
var newElem = angular.element('<a ng-href="{{ rowHref }}"></a>');
$compile(newElem)(scope);
$(cell).append(newElem);
});
}
}
});
Add his same HTML and CSS

Resources