How to dynamically change the content of a table column - javascript

Given the following HTML fragment, how can I create the content of the td depending on the column.
<div ng-app="" ng-controller="controller" >
<table>
<tr>
<th ng-repeat="column in columns">
{{ column.header }}
</th>
<tr ng-repeat="row in rows">
<td ng-repeat="column in columns">
<!-- TODO -->
</td>
</tr>
</table>
</div>
Each column can show a different kinds of data. For example, one might just show a string, another might contain a text input field that is bound to a property of the row.
I would like to call a function on the column (column.createCell(row)) that creates that necessary HTML and then put the result in place of <!-- TODO -->.
In WPF, I would just put a ContentPresenter with a DataTemplateSelector, but I don't know what the equivalent is in Angular. I read about something called "ng-bind-html", is that the way to go?

It's not given what kind of custom element you want to build for each column, but for DOM manipulation in AngularJS best practise is to keep it in a directive. Something like this:
in your html:
<body ng-controller="MainCtrl">
<table>
<tr ng-repeat="row in rows">
<td ng-repeat="column in row">
<custom-column="column"></custom-column>
</td>
</tr>
</table>
</body>
app.js
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
// whatever you wanted to define here...
$scope.rows = ...
$scope.columns = ...
});
app.directive('customColumn', function() {
return {
scope: {
obj: '=customColumn',
},
link: function(scope, element, attrs) {
var watcher = scope.$watch('obj', function(obj) {
if (!obj) return;
// build custom element
var html = '<div>'+scope.obj.name+'</div>';
element.html(html);
// delete watch if you only need to draw once
watcher();
});
}
}
});

Related

Angular Editable table not working with ng-repeat

<table class="table table-bordered">
<tbody>
<tr ng-repeat="playerOrTeam in template.editableTable track by $index">
<td style="text-align: center;" ng-repeat="playerOrTeamCat in playerOrTeam track by $index">
<input ng-model="playerOrTeamCat" type="text" class="form-control input-sm">
</td>
</tr>
</tbody>
</table>
template.editableTable is a multi dimensional array just filled with standard variables. when I change one of the values in the input box and then i look at the output of the template.editable table, i don't see the changes. Am I missing something obvious?
EDIT with more details because i'm getting no responses =\
//Template Class
app.factory('Template', function () {
var Template = function () {
/*
* Public Variables
*/
this.editableTable = someMultiDimensionalTable;
}
/*
* Functions
*/
Template.prototype.SeeEditableTableContents = function () {
console.log(this.editableTable);
}
}
//Main Controller
app.controller('MainController', function () {
$scope.template = new Template();
//etc
}
You cannot perform direct in-line modifications within ng-repeat. You can update your array entry using a function.
You'd want something like:
$scope.saveEntry = function (idx) {
console.log("Saving entry");
$scope.template.editableTable[idx] = angular.copy($scope.model.selected);
$scope.reset();
};
See JSFiddle for sample.
okay i actually got it to work so that i CAN make direct in-line modifications with ng-repeat by making my multidimensional table full of objects rather than soley a value.
so by doing that, i modified the table to look like this
<table class="table table-bordered">
<tbody>
<tr ng-repeat="playerOrTeam in template.editableTable track by $index">
<td style="text-align: center;" ng-repeat="playerOrTeamCat in playerOrTeam track by $index">
<input ng-model="playerOrTeamCat.value" type="text" class="form-control input-sm">
</td>
</tr>
</tbody>
</table>
got the idea looking here
Modifying objects within Angular Scope inside ng-repeat

angularjs: copy&paste tab delimited text to textarea then convert to table

I have an app in which users copy and paste tab delimited text. I want to take the text and make into a table (each new column is a tab and each new row is a new line in the pasted text.
I have the following which builds a list for each new line in the pasted text, but how do I get an array from tab delimited text?
<div ng-app>
<textarea ng-model="items" ng-list="/\n/"></textarea>
<table>
<tbody>
<tr ng-repeat="item in items">
<td>
{{item}}
</td>
</tr>
</tbody>
</table>
</div>
Since I can't paste tab delimited text here without it reformatting... To get tab delimited text copy and paste from Notepad or a spreadsheet.
Does this work for you? http://jsbin.com/bosamuhoti/1/edit
Here's a snippet:
Angular app
angular.module('myapp', [])
.controller('myCtrl', ['$scope', function($scope){
$scope.items = [];
$scope.table = [];
$scope.$watch("items", (newValue, oldValue) => {
for (var item of newValue) {
$scope.table.push(item.split('\t'));
}
});
}]);
And the corresponding HTML
<textarea ng-model="items" ng-list="
" ng-trim="false"></textarea>
<table border="1">
<tbody>
<tr ng-repeat="item in table track by $index">
<td ng-repeat="data in item track by $index">
{{data}}
</td>
</tr>
</tbody>
</table>

How to make dynamic content-editable table using angular JS?

Caveat: I've just started with client side scripting and Angular JS is the first thing I'm learning and now I feel I should've started with javascript.
PS: I don't wanna use any third party libraries. I wanna learn to code.
Anyway,I have dynamic table which I want to make editable using content-editable=true attribute of HTML.
Problem: How to I get the edited data? whenever I click on submit and pass the this object to the check() function. I doesn't contain edited values. is there a possible way to pass only edited value if it's dirty. It has pagination so If g to the next page the edited values are gone. I know I've give unique Id to every td element with $Index concatenated to it. But I don't know how should I proceed.
Any help or guidance will be appreciated. Controllers and others are defined in my route.
<div>
<form ng-submit="check(this)">
<table class="table table-striped table-hover">
<tbody>
<tr ng-repeat="data in currentItems">
<td contenteditable="true >{{data.EmpNo}}</td>
<td contenteditable="true">{{data.isActive}}</td>
<td contenteditable="true">{{data.balance}}</td>
<td contenteditable="true">{{data.age}}</td>
<td contenteditable="true">{{data.eyeColor}}</td>
<td contenteditable="true">{{data.fname}}</td>
</tr>
</tbody>
<tfoot>
<td>
<div class="pagination pull-right">
<li ng-class="{'disabled': previousPage}">
<a ng-click="previousPage()" >Previous</a>
</li>
<li ng-repeat="page in pageLengthArray track by $index">
<a ng-click="pagination($index)">{{$index+1}} </a>
</li>
<li disabled="disabled">
<a ng-click="nextPage()" ng-class="{'disabled':nextPage}>Next </a>
</li>
</div>
</td>
</tfoot>
</table>
<input type="submit" value="Submit">
</form>
$scope.currentPage=0;
$scope.pageSize=10;
$scope.currentItems;
$scope.tableData;
$http.get('../json/generated.json').then(function(response){
$scope.tableData=response.data;
$scope.pageLength=Math.ceil($scope.tableData.length/$scope.pageSize);
$scope.currentItems=$scope.tableData.slice($scope.currentPage,$scope.pageSize);
$scope.pageLengthArray= new Array($scope.pageLength);
});
$scope.pagination=function(currentPage){ $scope.currentItems=$scope.tableData.slice($scope.pageSize*currentPage,$scope.pageSize*currentPage+$scope.pageSize);
$scope.currentPage=currentPage;
}
$scope.nextPage=function nextPage(argument) {
$scope.currentPage++; $scope.currentItems=$scope.tableData.slice(($scope.pageSize*$scope.currentPage),($scope.pageSize*($scope.currentPage)+$scope.pageSize));
}
$scope.previousPage=function previousPage(argument) {
$scope.currentPage--;
$scope.currentItems=$scope.tableData.slice(($scope.pageSize*$scope.currentPage),($scope.pageSize*($scope.currentPage)+$scope.pageSize));
}
In the usual case, you can not get a change model for contenteditabe because to change the model used ngModel.
But we can create a directive that we have updated the value of the model.
Live example on jsfiddle.
angular.module('ExampleApp', [])
.controller('ExampleController', function($scope, $timeout) {
$scope.data = {
EmpNo: "123"
};
})
.directive('contenteditable', function($timeout) {
return {
restrict: "A",
priority: 1000,
scope: {
ngModel: "="
},
link: function(scope, element) {
element.html(scope.ngModel);
element.on('focus blur keyup paste input', function() {
scope.ngModel = element.text();
scope.$apply();
return element;
});
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="ExampleApp">
<div ng-controller="ExampleController">
<table>
<tr>
<td ng-model="data.EmpNo" contenteditable="true"></td>
</tr>
</table>
<pre>{{data|json}}</pre>
</div>
</div>
I would store any object that gets modified in a seperate array using the ng-keyup directive. When the form is submitted, you will have an array of only elements which have been modified. You may have some UX issues if your pagination is done by server as when you change page and come back, it will show your old data, but hopefully this helps.
$scope.check = function () {
// check modifiedItems
console.log(modifiedItems);
};
// store modified objects in a seperate array
var modifiedItems = [];
$scope.modifyItem = function (data) {
// check if data has already been modified and splice it first
for(var i = 0, j = modifiedItems.length; i < j; i++) {
var currentItem = modifiedItems[i];
if (currentItem.id === data.id) {
modifiedItems.splice(i, 1);
break;
}
}
// add to modified
modifiedItems.push(data);
console.log('modifiedItems: ', modifiedItems);
};
HTML
<form ng-submit="check()">
<table class="table table-striped table-hover">
<tbody>
<tr ng-repeat="data in currentItems">
<td ng-repeat="(key, value) in data" contenteditable="true"
ng-keyup="modifyItem(data)">
{{data[key]}}
</td>
</tr>
</tbody>
<tfoot>
</table>
<input type="submit" value="Submit">
</form>

Grouping rows in ngrepeat

I have json array and i want to create table from this and want to give a heading which is an element of array. Here is fiddle showing the scenario.
<div ng-repeat="products in items">
<div>{{products.IDTYPE}}</div>
<div>
<table border=1>
<thead>
<th>primkey</th>
<th>userid</th>
</thead>
<tbody>
<tr>
<td>{{products.PRIMKEY}}</td>
<td>{{products.USERID}}</td>
</tr>
</tbody>
</table>
</div>
This will create simple table with heading from the IDTYPE . But i want to group the rows with unique IDTYPE. So desired table will be as shown in this link.
So i tried adding a ng-show condition ng-show="$index==0 || items[$index-1].IDTYPE!=items[$index].IDTYPE" but it doesn't work properly as tbody and table will be constructed for every row.This is what i have tried.
So how to generate the table as i desired in the above description?
If you do not wish to change your source data structure to fit better to what you need, you may have to write some extra code to change it from the javascript side. Something like:
var myApp = angular.module('myApp', []);
function MyCtrl($scope) {
$scope.items = [...]; //your original data
// Here the transformation begins...
$scope.newItems = {};
for (var i = 0; i < $scope.items.length; i++) {
// We'll make it look like a map
// Each IDTYPE will have an array associated to it
if (!$scope.newItems[$scope.items[i].IDTYPE]) {
$scope.newItems[$scope.items[i].IDTYPE] = [];
}
$scope.newItems[$scope.items[i].IDTYPE].push($scope.items[i]);
}
}
From the HTML side, you just have to read accordingly to your new data:
<div ng-repeat="products in newItems">
<div>{{products[0].IDTYPE}}</div>
<div>
<table border=1>
<thead>
<th>primkey</th>
<th>userid</th>
</thead>
<tbody>
<tr ng-repeat="productItem in products">
<td>{{productItem.PRIMKEY}}</td>
<td>{{productItem.USERID}}</td>
</tr>
</tbody>
</table>
</div>
</div>
Fiddle: http://jsfiddle.net/5mpgzdem/

AngularJS does not compile element that has ng-repeat

I created an attribute directive that replaces the content inside of the element where it is used with a loading icon, until the variable that is the value of the attribute is evaluated to something other than undefined. The code for the directive is:
.directive('ngLoading', function (Session, $compile) {
var loadingSpinner = '<div class="spinner">' +
'<div class="rect1"></div>' +
'<div class="rect2"></div>' +
'<div class="rect3"></div>' +
'<div class="rect4"></div>' +
'<div class="rect5"></div></div>';
return {
restrict: 'A',
link: function (scope, element, attrs) {
var originalContent = element.html();
element.html(loadingSpinner);
scope.$watch(attrs.ngLoading, function (val) {
if(val) {
element.html(originalContent);
$compile(element.contents())(scope);
} else {
element.html(loadingSpinner);
}
});
}
};
});
I use this directive in my view in the following way:
<div ng-loading="user">
{{user.name}}
</div>
The content of this div is replaced by a laoding icon until the scope variable user contain some data, at which point the original content of the div is put back inside of the div and the div is compiled by $compile.
This works fine in most cases but it doesn't work when the original content of the div has a ng-repeat directive somewhere. The following case does not work, for instance:
<div ng-loading="users">
<table>
<thead>
<tr>
<th>Name</th>
<th>Age</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="user in users">
<td>{{user.name}}</td>
<td>{{user.age}}</td>
</tr>
</tbody>
</table>
</div>
The table gets rendered but no tr inside tbody is rendered, as if the scope variable users was null. When I debug the code, I can see that the value of the variable users is indeed an array of users, right before the $compile call. I've tried wrapping the code inside the $watch in my directive with an $apply call, but when I do that I get the error "$apply already in progress".
It's also worth noting that the controller whose scope encompasses the div has a property called users.
What I am doing wrong?
UPDATE
I changed my html to:
<div ng-loading="users">
{{users[0]}}
<table>
<thead>
<tr>
<th>Name</th>
<th>Age</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="user in users">
<td>{{user.name}}</td>
<td>{{user.age}}</td>
</tr>
</tbody>
</table>
</div>
After the loading finishes, the content of the div is replaced by the toString of the first user in the array, with all the correct information, followed then by the empty table. This really seems to be a problem with ng-repeat...
Possibly approach it differently - in Angular that sort of DOM manipulation isn't usually preferred.
Given the two way data binding of Angular, would a conditional show/hide in the html not accomplish the desired result while letting Angular take care of the details?
eg.
<div ng-show="users">
<table>
<thead>
<tr>
<th>Name</th>
<th>Age</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="user in users">
<td>{{user.name}}</td>
<td>{{user.age}}</td>
</tr>
</tbody>
</table>
</div>
<div class="spinner" ng-hide="users">
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
Instead of getting the html content and attaching the element content, just append the spinner to the content and make css changes to show at the top, once the loading completed, just remove the spinner element as like the below.
app.directive('ngLoading', function ($compile) {
var loadingSpinner = '<div class="spinner">' +
'<div class="rect1"></div>' +
'<div class="rect2"></div>' +
'<div class="rect3"></div>' +
'<div class="rect4"></div>' +
'<div class="rect5"></div></div>';
return {
restrict: 'A',
link: function (scope, element, attrs) {
scope.$watch(attrs.ngLoading, function (val) {
if (val) {
element.append(loadingSpinner);
} else {
element.find('div.spinner').detach();
}
});
}
};
});

Categories