Why is DataTables stripping custom attributes in render function? - html

I'm working on a .Net MVC website and in one of the views a table is rendered using DataTables which makes an ajax call to an API. However, when the table is rendered the custom attributes I supply in the render function are being stripped out:
Here is the HTML:
<table id="table-rate-headers" class="table table-bordered table-fit table-hover table-sm" style="margin-top: 0px; width: 100%;">
<thead class="text-nowrap">
<tr>
<th>Domain</th>
<th>Project</th>
<th>Union</th>
<th>Union Local</th>
<th>Trade Class</th>
<th>Trade Class Description</th>
<th>Date From</th>
<th>Date To</th>
</tr>
</thead>
<tbody class="text-nowrap"></tbody>
</table>
And the js:
var ratesDataTable = $('#table-rate-headers').DataTable({
dom: 'rftp',
pageLength: 5,
select: true,
ajax: {
url: '/api/rates/rateheadersnormalized',
dataSrc: ""
},
columnDefs: [
{
className: 'row-border'
}
],
columns: [
{
"data": "rateDomain",
render: function (data, type, row) {
return '<td rateheadersid="' + row.rateHeaderSID + '">' + row.rateDomain + '</td>';
}
},
{
"data": "project"
},
{
"data": "union"
},
{
"data": "unionLocal"
},
{
"data": "tradeClass"
},
{
"data": "tradeClassDescription"
},
{
"data": "effectiveDateFrom",
render: function (data, type, row) {
return moment(row.effectiveDateFrom).format("MM/DD/YYYY");
}
},
{
"data": "effectiveDateTo",
render: function (data, type, row) {
return moment(row.effectiveDateTo).format("MM/DD/YYYY");
}
}
]
});
When the page is rendered, I expect to see in the dom:
<td rateheadersid="1">Labor</td>
But what I actually see is:
<td class="sorting_1">Labor</td>
Something is stripping out the custom attribute and not rendering the element as I'd expect. I tried adding "orderClasses": false but it just got rid of the class="sorting_1" and nothing else. Also tried taking out all the css classes I specified for the table and that didn't make a difference. I've also compared the setup of the table to all the previous ones I've created and I can' spot anything out of order. Any guidance appreciated.

The DataTables columns option is already inside the <td> and </td> tags of the HTML table cells it is trying to populate. So, it can only populate the cells content - not the cell's HTML attributes.
HTML does not let you embed another <td> inside a <td> - which is what the code is effectively trying to do.
The simplest fix is to change your <td> to a <span>:
return '<span rateheadersid="' + row.rateHeaderSID + '">' + row.rateDomain + '</span>';
That may be sufficient for your needs. If not, then you may need to provide more info in the question.

Related

Filling a table with JSON data

I am taking mmy first steps in Angular and was about to try populating a table with data stored in a JSON file. Facing a bit of an odd/unexpected situation there as the table doesn't look exactly how I expected it to look. First things first.
Table code:
<table class="table table-sm table-bordered">
<thead>
<tr>
<th>Transfers</th>
<th>Tools</th>
</tr>
</thead>
<tbody>
<tr *ngFor = "let data of linksArray">
<td>{{ data.Transfers}}</td>
<td>{{ data.Tools}}</td>
</tr>
</tbody>
</table>
What I would like to achieve is that the columns Transfers and Tools get filled with data that uses the corresponding keywords in my JSON file. And here it is
[{"Type":"Transfers","Name":"1","Hyperlink":"#"}, {"Type":"Transfers","Name":"2","Hyperlink":"#"},
{"Type":"Tools","Name":"1","Hyperlink":"#"}, {"Type":"Tools","Name":"2","Hyperlink":"#"}]
The array gets loaded by using this in my .ts file
this.http.get('../../assets/templatefiles/links.json').subscribe(data => {this.linksArray = data as
any [];
console.log(this.linksArray);
},
(err: HttpErrorResponse) => {
console.log (err.message);
}
);
So far all looks good, I see the array popping up in console, but then look at the table and am confused
I would have expected the entries in the 2nd column to start in the first cell. Instead they start in the 3rd? What am I missing here? Been marveling for quite a while now :)
https://stackblitz.com/edit/angular-ivy-hvcyfq
To get this
Transfers Tools
1 10
2 20
You want code roughly like this
<hello name="{{ name }}"></hello>
<table class="table table-sm table-bordered">
<thead>
<tr>
<th>Transfers</th>
<th>Tools</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let data of linksArray">
<td>{{data.Transfers.Name}}</td>
<td>{{data.Tools.Name}}</td>
</tr>
</tbody>
</table>
And, this is the main bit, json roughly like this
linksArray = [
{
Transfers: {
Name:"1",
Hyperlink:"#"
},
Tools: {
Name:"10",
Hyperlink:"#"
}
},
{
Transfers: {
Name:"2",
Hyperlink:"#"
},
Tools: {
Name:"20",
Hyperlink:"#"
}
},
Your existing json looks like this, where the '{' represents one row
[
{
"Type":"Transfers",
"Name":"1",
"Hyperlink":"#"
},
{
"Type":"Transfers",
"Name":"2",
"Hyperlink":"#"
},
{
"Type":"Tools",
"Name":"1",
"Hyperlink":"#"
},
{
"Type":"Tools",
"Name":"2",
"Hyperlink":"#"
}
]

Bind first value and remove all other repeating value from a ng-repeat

I have a scenario to bind a html table using angular js. In my table i need to show an a tag based on another column value(Payment Status). If its fully paid no need to show the a tag, else need to show it for very next element. I am a new one in angular.
<table>
<thead>
<tr>
<th>Month</th>
<th>Installement</th>
<th>PaymentAmount</th>
<th>PaymentDate</th>
<th>Payment Status</th>
<th>Pay</th>
</tr>
</thead>
<tbody>
<tr dir-paginate="row in rowCollection|orderBy:type:reverse|filter:searchKeyword|itemsPerPage:maxsize">
<td>{{row.Month}}</td>
<td>{{row.MonthlyInstallement}}</td>
<td>{{row.PaymentAmount}}</td>
<td>{{row.PaymentDate}}</td>
<td>{{row.PaymentStatus}}</td>
<td ng-if="row.PaymentStatus == 'UNPAID'">
Pay Online
</td>
<td ng-if="row.PaymentStatus == 'FULLY_PAID'">
</td>
</tr>
</tbody>
</table>
function bindFinanceDetails() {
var finUrl = baseurl + 'api/FinancialStatement/GetCarFinanceInfo';
var req = {
method: 'post',
data: {
LoginID: LoginID,
ContractNumber: 11170200669,
CustomerId: 2355898046
},
url: finUrl,
headers: {
RequestedPlatform: "Web",
RequestedLanguage: cookiePreferredLanguage,
Logintoken: $cookieStore.get('LoginToken'),
LoginId: LoginID
}
};
$http(req).then(function(response) {
var getData = response.data.FinanceList;
$scope.rowCollection = getData;
}, function(error) {
toastr.error($filter('translate')('Error Occured'));
});
}
A quite hacky solution will be something like the following (just showing you the needed change in the unpaid td element):
<td ng-if="row.PaymentStatus === 'UNPAID'" ng-show="$index === data.payOnlineIndex"
ng-init="data.payOnlineIndex = (!data.payOnlineIndex || (data.payOnlineIndex > $index)) ? $index : data.payOnlineIndex">
Pay Online
</td>
This way ng-init will run for all unpaid elements, setting the smallest index to the payOnlineIndex variable. ng-show will make sure to only show that one element that has the smallest index.
I encapsulate payOnlineIndex with a data object to keep a stable reference to it. This also requires the following addition to the controller code:
$scope.data = { payOnlineIndex: null };
See a working jsFiddle example here: https://jsfiddle.net/t3vktv0r/
Another option is running your filter and orderBy in the controller, searching for the first occurrence of an "unpaid" row, and marking that element for the "pay online" feature with some flag you can test with ng-if in your view.

How to make draggable column in HTML5 with 5 seconds refresh rate?

I am making a website which is loading data from ajax webservice call and populating it in html tables and this data gets refreshed in every 5 seconds. I have achieved this using lazy loading in Backbone JS. So when javascript timer fires in every 5 seconds it makes ajax call and it reloads the template which contains the table thus showing new data.
this.metrices.fetch({
url : (isLocal) ? ('js/jsons/agent.json') : (prev_this.url + '/agentstat'),
data: JSON.stringify(param),
type: "POST",
dataType: "JSON",
contentType: "application/json",
})
.done(function() {
});
this.$("#agentMetrics").html(this.agentTemplate({
agents: this.metrices.toJSON()
}));
And the #agentMetrics template is like this
<table class="table_class js-table-deskop" id="agent" style = "width:100%">
<tr class="">
<th class="js-table-agent js-agentPref-0 js-agentPref-0">Names</th>
<th class="js-table-agent js-agentPref-1">State</th>
<th class="js-table-agent js-agentPref-2">Skill</th>
<th class="js-table-agent js-agentPref-8">Center</th>
<th class="js-table-agent js-agentPref-9">Lan Id</th>
<th class="js-table-agent js-agentPref-10">Login Id</th>
<th class="js-table-agent js-agentPref-11">Manager</th>
<th class="js-table-agent js-agentPref-12">Site</th>
<th class="js-table-agent js-agentPref-13">Team Leader</th>
<th class="js-table-agent js-agentPref-14">Workgroup</th>
</tr>
{{#each agents}}
<tr >
<td class="th1 js-agentPref-0">{{name}}</td>
<td class="js-alert js-agentPref-1 js-state-agent" >{{state}}</td>
<td class="js-alert js-agentPref-2 js-skill-agent">{{dispSkill}}</td>
<td class="js-alert js-agentPref-8">{{center}}</td>
<td class="js-alert js-agentPref-9 js-lanid-agent">{{lanId}}</td>
<td class="js-alert js-agentPref-10">{{loginId}}</td>
<td class="js-alert js-agentPref-11">{{manager}}</td>
<td class="js-alert js-agentPref-12">{{site}}</td>
<td class="js-alert js-agentPref-13">{{teamLead}}</td>
<td class="js-alert js-agentPref-14">{{workGroup}}</td>
</tr>
{{/each}}
</table>
Now the problem is that I have a new requirement where I have to make these columns resizable and swappable i.e their width could be changed by dragging and can columns can be swapped like it can be done in Adobe flex.
This is where it gets tricky as what ever I used since the template gets refreshed in 5 seconds so it is not able to hold this selection is there any plugin to achieve this which supports retention of this preference dragging or swapping. Or can I some how prevent loading of template, any suggession
Use a data binding library like rivets.js.
Once you bind your view with rivets, changes in the model/collection will be updated in DOM by rivets without replacing the entire DOM.
If new records are added, new DOM element will be inserted in the respective position without altering the existing ones.
Below is a small demo taken from another answer of mine:
rivets.adapters[':'] = {
observe: function(obj, keypath, callback) {
obj.on('change:' + keypath, callback)
},
unobserve: function(obj, keypath, callback) {
obj.off('change:' + keypath, callback)
},
get: function(obj, keypath) {
return obj.get(keypath)
},
set: function(obj, keypath, value) {
obj.set(keypath, value)
}
}
var data = [{
name: "john",
age: 10
}, {
name: "joseph",
age: 11
}, {
name: "joy",
age: 12
}]
var userCollection = new Backbone.Collection(data);
var View = Backbone.View.extend({
initialize: function(options) {
this.render()
},
render: function() {
rivets.bind(this.el, {
users: this.collection
});
}
});
var userView = new View({
el: '#user-list',
collection: userCollection
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.2.3/backbone-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rivets/0.8.1/rivets.bundled.min.js"></script>
<ul id='user-list'>
<li rv-each-user="users.models">
<input type="text" rv-value="user:age"/>
<span>{user:name}</span><span> {user:age}</span>
</li>
</ul>

Iterating through JSON data with Mustache JS

I have been trying and failing to iterate through some JSON Data with Mustache JS in order to populate a table which looks like:
{
"Pay":[
[
"Regular Hours",
"$75.00",
"$75.00"
]
],
"Earnings":[
[
"Regular Earnings",
"$2,277.00",
"$1,200.00"
],
[
"Non Tax Vacation Pay",
"$0.00",
"$50.80"
]
],
"Deductions":[
[
"Other Deduction 5",
"$0.00",
"$50.00"
]
]
}
How would I iterate using Mustache JS in order to have every inner array as rows and the outer array as headers like this:
<tr>
<th colspan="4">Pay</th>
</tr>
<tr>
<td>Regular Hours</td>
<td>75</td>
<td>75</td>
</tr>
<!-- Etc. -->
Any help would be greatly appreciated
This issue has made me consider switching over to handlebars to avoid the need for hacky code. The data being passed in is the JSON input from the original question. This function will format the data into key value pairs, after which the data is rendered by the Mustache template.
function (data) {
var payslipList = JSON.parse(data.d);
formattedData = { 'entries': [] };
for (var property in payslipList) {
if (payslipList.hasOwnProperty(property)) {
formattedData['entries'].push({
'key': property,
'value': payslipList[property]
});
}
}
var tablePayslip = $("#tblPayDetails tbody");
$.each(formattedData, function (id, payslip) {
var template = $('#renderPaystub').html();
var html = Mustache.render(template, payslip);
tablePayslip.append(html);
});
The template to access the key value pairs would look something like this:
<script type="text/template" id="renderPaystub">
{{#.}}
{{#.}}
<tr>
<th colspan="3">{{key}}</th>
</tr>
{{/.}}
{{#value}}
<tr>
{{#.}}<td>{{.}}</td>{{/.}}
</tr>
{{/value}}
{{/.}}
</script>
This template is fairly ugly and ambiguous, if there is a better method to achieve this, feel free to let me know.

Retrieving values from a table cell <td> to a controller

I'm trying to get a specific value from a table cell and pass is to a controller. But it doesn't seem to be working. I show you some of my codes:
This is in the controller:
def searchUser = {
String abc = request.getParameter("harrow")
println(abc)
}
This is in the html page:
<form>
<div style="height: 250px; overflow: scroll; width: 100%;">
<table id="normal">
<g:each in = "${result}">
<tr id="btn">
<td width=10% >${it.ID}</td>
<td width=25% id="harrow">${it.username}</td>
</tr>
</g:each>
</table>
</div>
<input type ="submit" name ="abc" id="opener">
</form>
EDIT
AJAX:
$("#edittable").on('click', function() {
$.ajax({
url: URL,
data: $(this).serialize(),
type: "POST",
success: function(html){
//do something with the `html` returned from the server here
$("#edit1").html(html).dialog("open")
},
error: function(jqXHR, textStatus, errorThrown){
alert('error: ' + textStatus + ': ' + errorThrown);
}
});
return false;//suppress natural form selection
});
I can get the value to pass to the controller, but right now it only retrieves the first row of values rather than the other. Is there something wrong with my AJAX codes? Thank you guys so much.
So to show details of the row, one of the approaches can be:
CONTROLLER METHODS
def rows = [
[id:1, name:'name1'],
[id:2, name:'name2'],
[id:3, name:'name3']
]
def show(){
[results:rows]
}
def showLine(Long id){
render rows.find {it.id == id }
}
VIEW
<html>
<head>
<g:javascript library="jquery" />
<r:layoutResources />
</head>
<body>
<table>
<thead>
<tr>
<th>Action</th>
<th>Id</th>
<th>Name</th>
</tr>
</thead>
<tbody>
<g:each in="${results}" status="i" var="r">
<tr>
<td><g:remoteLink value="Details" id="${r.id}" action="showLine" update="lineDetails">Details</g:remoteLink></td>
<td>${r.id}</td>
<td>${r.name}</td>
</tr>
</g:each>
</tbody>
</table>
<div id="lineDetails">
</div>
</body>
</html>
Basically you call showLine method using AJAX and passing row object id. Then you search for the object and render it back. Rendered data is put into div under the table. It's up to you if you use onclick, button or link in the table. It's also up to you how to show details on results page - use jquery dialog, or something else. Hope it helps