Refresh KnockoutJS Data In DataTables - binding

I have the following table
<table class="datatable zebra-striped">
<thead>
<tr>
<th>Name</th>
<th>Issue</th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: Publications">
<tr>
<td><span data-bind="text: Name" /></td>
<td><span data-bind="text: IssueNumber" /></td>
<td><button type="button" class="btn" data-bind="click: $parent.DeletePublication">Delete</button></td>
</tr>
</tbody>
</table>
Here is a snippet of the viewmodel:
function EditReaderViewModel() {
var self = this;
self.Publications = ko.observableArray([]);
self.OnAddPublicationSaveButtonClicked = function () {
//.. code omitted.
self.Publications.push(new Publication(publication.value, publication.label, publication.issueNumber));
};
};
}
and the Publication object binded to the table:
function Publication(id, name, issueNumber) {
var self = this;
self.Id = id;
self.Name = name;
self.IssueNumber = issueNumber;
self.LoadedFromDatabase = false;
}
When the data is first loaded into the table, and the datatable initialisation e.g.
$(".datatable").dataTable({ // blahh });
Everything works fine - the data is loaded in the table, sorting and filtering is working, etc.
Now when I add a new item to the Publications array in the viewmodel things just fall apart. For example, say I originally have 1 item in the Publications array, when I add another item, it will appear in the table until I click on a column heading to sort.
It seems that the datatable has it's own internal data list somewhere which does not include the new Publication I created.
Can someone guide me on how to rebuild the datatable, taking into account that the data is from a KnockoutJS viewmodel?
Please note that I have looked at this binding plugin:
http://www.joshbuckley.co.uk/2011/07/knockout-js-datatable-bindings/
The problem is that when I use this, I get error messages such as:
Requested unknown parameter '0' from the data source for row 0.
EDIT:
I'm looking through the code and I think I see the problem. In jQuery.DataTables.js there is this function function _fnGetCellData( oSettings, iRow, iCol, sSpecific )
In that function there is this line:
if ( (sData=oCol.fnGetData( oData )) === undefined )
which in turn calls:
function _fnGetObjectDataFn( mSource )
Now, in function _fnGetObjectDataFn( mSource ) there is this code which is called:
else
{
/* Array or flat object mapping */
return function (data) {
return data[mSource];
};
}
data is not an array, it's a JSON object which means that the above code will fail (return data[mSource];).
I'm really not sure what to do here.
EDIT - IMPORTANT:
As one of the commentators has noticed, and I've just confirmed, accessing
http://www.joshbuckley.co.uk/2011/07/knockout-js-datatable-bindings
will result in a Trojan warning. So please be aware that this url is now an big NO-NO.

I know that´s an older thread but since I found it when I had the same problem I feel the need to post the solution so others might find it quicker.
When refreshing the Table you can simple .clear() and then .destroy() the table.
After that just reinitialize the table using the already known .DataTable()
Although that's a little messy because you really tear down the structure and rebuild it it work very smoothly.
You just have to add a boolean identifier you set to true the first time you initialize the table. And of course you have to bind the Table to a variable to be able to work the DataTable API magic on the DOM Element.
if (managementLoadedDone){
userTable.clear();
userTable.destroy();
}
Fill data in your Table
userTable = $("#userTable").DataTable({responsive: true});
usersLoaded = true;

You mention that you used datatable plugin and get "Requested unknown parameter '0' from the data source for row 0" error.
In my previous experineces using datatable without knockout plugin i got this error only using more columns than the header definitions or vice versa. You have to check your tbody and thead section and make it sure that both of them is the same size.
Or may be you are using "aoColumnDefs" option when you initialize the table.Again checked the option definiton and make sure that you havent try to reach unknown column :
For example if you have a definition like this
"aoColumnDefs": [{
"bSearchable": false,
"bVisible": false,
"aTargets": [0]
}, {
"bSortable": false,
"bSearchable": false,
"sWidth": "45px",
"aTargets": [2]
}]
and if you have only 2 thead section this will give you that error. Because you are reaching third column with aTargets : [2]

I was experiencing a similar problem as you've described here (and didn't have any intention of exploring the "bindings" link after the Kaspersky warning, even if it is really safe.)
Going a bit on the approach suggested here KnockoutJs - how to use datatables with existing bound table, I ended up taking a route where when using knockout on the table, I added a knockout if statement to test if the observableArray had at least one row of data.
<!-- if Publications.length() > 0 -->
<table class="datatable zebra-striped">
<thead>
<tr>
<th>Name</th>
<th>Issue</th>
</tr>
</thead>
<tbody>
<!-- ko foreach: Publications -->
<tr>
<td><span data-bind="text: Name" /></td>
<td><span data-bind="text: IssueNumber" /></td>
</tr> <!-- /ko -->
</tbody>
</table>
<!-- /ko -->
When loading the View Model, I then called the dataTables function
inside my .ajax success function.
Then the other part to making it work was to make use of dataTables "bStateSave": true,
so that any sorting I had done prior to a refresh would not be lost.

Related

How to keep a table row selected across multiple Views AngularJS

On the left side of my Layout page there is a table with a list of Job Names. To the right is where the views populate. My views each have identical tables. ChangeOrders.cshtml, PurchaseOrders.cshtml, etc... Right now when you are on the ChangeOrders view nothing appears until you select a job from the Job Names table. The ChangeOrders with that JobId appear. However when I switch to another view the job Selection clears itself. I need that Job to stay selected until I select another Job or a clear filter button is pressed.
Jobs Table on the Layout Page
<table class=" table table-bordred table-striped table-hover" ng-table=" tableparams" show-filter="true" ng-scroll-viewport style="height:200px;">
<tr ng-repeat="job in jobArray" class="pointer" ng-click="selectJob(job)">
<td data-title="'Job Name'" sortable="'JobName'" filter="{ 'JobName': 'text' }">{{job.JobName}}</td>
</tr>
</table>
ChangeOrder.cshtml (All other views will identical except the data)
<table class=" table table-bordred table-striped table-hover" ng-table="tableParams" show-filter="true" >
<tr ng-repeat="job in selectedJob().ChangeOrders" class=" pointer">
<td ng-click="editChangeOrderModal(job)" data-title="'CO Number'" sortable="'ChangeOrderNumber'" filter="{ 'ChangeOrderNumber': 'text' }">{{job.ChangeOrderNumber}}</td>
<td data-title="'CO Date'" sortable="'ChangeOrderDate'" filter="{ 'ChangeOrderDate': 'text' }">{{job.ChangeOrderDate | date : date : shortDate}}</td>
<td data-title="'CO Name'" sortable="'ChangeOrderName'" filter="{ 'ChangeOrderName': 'text' }">{{job.ChangeOrderName}}</td>
<td data-title="'CO Amount'" sortable="'ChangeOrderAmount'" filter="{ 'ChangeOrderAmount': 'text' }">${{job.ChangeOrderAmount | number : fractionSize}}</td>
<td data-title="'CO ApprovedDate'" sortable="'ChangeOrderApprovedDate'" filter="{ 'ChangeOrderApprovedDate': 'text' }">{{job.ChangeOrderApprovedDate | date : date : shortDate}}</td>
<td data-title="'CO ApprovedAmount'" sortable="'ChangeOrderApprovedAmount'" filter="{ 'ChangeOrderApprovedAmount': 'text' }">${{job.ChangeOrderApprovedAmount | number : fractionSize}}</td>
<td data-title="'CO ApprovedNumber'" sortable="'ChangeOrderApprovedNumber'" filter="{ 'ChangeOrderApprovedNumber': 'text' }">{{job.ChangeOrderApprovedNumber}}</td>
<td data-title="'CO Attn'" sortable="'ChangeOrderAttn'" filter="{ 'ChangeOrderAttn': 'text' }">{{job.ChangeOrderAttn}}</td>
</tr>
</table>
Controller
//Sync Table Selections
$scope.selectJob = function (job) {
$rootScope.selectedJob = job;
};
Updated
I am using ngStorage for my sessionStorage
ngStorage
I tried to implement it but I am not sure how to use it with the $rootScope. I also need to add the jquery function as well.
$scope.selectJob = function (job) {
$rootScope.selectedJob = $sessionStorage.$default(job);
console.log($rootScope.selectedJob);
};
$scope.selectedJob = $scope.selectedJob = function () {
return $rootScope.selectedJob;
};
$('#myTable').on('click', ' tbody tr', function (event) {
$(this).addClass('highlight').siblings().removeClass('highlight');
});
Summary of the Problem
As discussed $rootScope is being cleared because your current
application setup is not an AngularJS Single Page App. It is
understandable that you can not switch it out at this time and that is
ok, your application will still be a nice switch to some angular.
In order to have data be persistent across views you will need to save the data in some form of client storage then check for the values when a controller loads and populate your values.
Several Storage Options
1) HTML5 Storage
Here is a an article about HTML5 storage. There are two types of html5 web storage.
localStorage
This can be very flexible it persists across sessions and tabs. But if you want it to clear it has to be manually clearedk, so it can be considered insecure depending on what you are storing.
sessionStorage
sessionStorage is almost identical to local storage except it does not save after the session ends. So it is not available on a new window.
If you are interrested in this option these objects are not hard to work with on your own. But if you found it easier you could use this project, which wraps the objects in an angular factory.
2) Cookies
While a valid storage option. Cookies are really meant if you want to share the data serverside and want the data sent in the headers.
Angular has modules for handling cookies. You have to download angular-cookies from the AngularJS site and include it to use ngCookies. Angular API Reference
Angular cookies can be implemented in two ways
$cookie (API reference) which is a basic wrapper for the document.cookie object. And sets named values.
$cookieStore (API reference) has the same base functionality but uses a (key,value) format.

Integrating Pusher with data in a HTML table on Angular

We are evaluating Pusher http://pusher.com for a specific need. A user selects multiple rows in a HTML table tied to an Angular.js model, and these are background processed by a Rails application. I understand Rails 4 Live Streaming could also be used to do the same thing.
The status and some other fields are updated in a SQL table by the Rails backend. When the page is first loaded the $scope.assays variable is updated from a http call. This is then looped through to populate the table.
How can Pusher update the individual cells in the table as each row has completed processing? Will the code need to loop through a new $scope.assays variable and reload every cell?
Ideally we would like to just update the cell where the change has been made (eg: status on row 3),and if possible apply a CSS class to make the cell glow for a moment.
Sample code -
<tbody>
<tr ng-repeat="assay in assays">
<td>
<input type="checkbox" name="selected" ng-checked="isSelected(assay.id)" ng-click="updateSelection($event, assay.id)"/>
</td>
<td key="id">{{assay.id}}</td>
<td key="source">{{assay.source}}</td>
<td key="status">{{assay.status}}</td>
<td key="chrom">{{assay.chrom}}</td>
<td key="chrom_start">{{assay.chrom_start}}</td>
<td key="chrom_end">{{assay.chrom_end}}</td>
<td key="gene">{{assay.gene}}</td>
<td key="cosmic_mut_id">{{assay.cosmic_mut_id}}</td>
<td key="primer_left">{{assay.primer_left}}</td>
<td key="primer_right">{{assay.primer_right}}</td>
</tr>
</tbody>
Pusher
How can Pusher update the individual cells in the table as each row
has completed processing?
Pusher won't need to do this - it's a job for JS. Pusher just connects (via websocket) to the pusher server & "listens" to updates you send from your app:
var channel = pusher.subscribe('my-channel');
channel.bind('my-event', function(data) {
alert('An event was triggered with message: ' + data.message);
});
Function
You'll be better using a separate function to go through the table, populating as required:
var channel = pusher.subscribe('my-channel');
channel.bind('my-event', populate_table(data));
function populate_table(data) {
var row = document.getElementById("row_id");
//do what you need with row to append data
};
Hope this helps?

Displaying large tables of data with angular and MVC

We are trying to display large amounts of data on a webpage.
Data is cached, and is loaded via angular (to allow the actual page to load first and then fetch the data for the grid)
All working ok, however once the data has been received, the page freezes for 1 - 3 seconds,
which we assume is due to the table being rendered.
Size of table can vary, and range from 20 columns 10 rows to 100 columns 100 rows.
All data has to be loaded, as it is to display trends etc.
Below is the table:-
<table id="tbl" class="table table-striped table-bordered" ng-repeat="Grid in userresults.Trend">
<tr ng-repeat="item in Grid" ng-show="$first">
<th ng-repeat="somat in item | filter:{ColumnType :'!1'}">
{{somat.ColumnHeader}}
</th>
</tr>
<tr ng-repeat="item in Grid">
<td ng-repeat="somat in item | filter:{ColumnType : '!1'}" >
{{somat.ColumnValue}}
</td>
</tr>
</table>
Below is the angular:-
$scope.GetPatientResults = function () {
$("#ResultsLoader").show();
var url = "/user/11111/GetUserResults/";
$http.get(url)
.success(function (data) {
// if successful, bind success message to message
$scope.userresults = data;
$("#ResultsLoader").hide();
$('#ResultsSummary').show();
});
};
is there a way to actually only build part of the table that is displayed on screen to avoid this 1 - 3 second rendering / some kind of lazy loading for tables for rows and columns?
Any help / advise is greatly appreciated as this is currently impacting usability on our system.
One alternative may be to try to use ngGrid instead of a table. I believe that ngGrid will only render the items seen on screen, and not all grid items at once. If your problem is indeed the browser rendering, this approach should help.
The problem might be the <table> rendering type of the browsers.
All browsers render tables when all the columns and rows complete, so your data delay the rendering. There is an option to make the appropriate changes in order to render everything in div or try the following link which says that speeds up the table layout.
<div id="tbl" style="float:left;" ng-repeat="Grid in userresults.Trend">
<div style="float:left;" ng-repeat="item in Grid" ng-show="$first">
<div style="float:left;width:200px;" ng-repeat="somat in item | filter:{ColumnType :'!1'}">
{{somat.ColumnHeader}}
</div>
</div>
<div style="float:left;" ng-repeat="item in Grid">
<div style="float:left;width:200px;" ng-repeat="somat in item | filter:{ColumnType : '!1'}">
{{somat.ColumnValue}}
</div>
</div>
</div>

Are dynamically created table rows not targetable by jQuery?

I'm trying to make table rows draggable and then connectToSortable -able.
For some reason, when I put the ID of the tbody ("#sem1") in the selector, it works but obviously drags the whole table-body with all of its rows.
However, when I put "#sem1 tr" in the selector the webpage seems to just ignore that code, meaning the table still shows up correctly, but nothing becomes draggable.
HTML:
<table class = "sem">
<thead>
<th class = "header1">header</th>
<th class = "header2">header</th>
<tr>
<td class = "static1">static1</td>
<td class = "static2">static2</td>
</tr>
</thead>
<tbody id = "sem1">
</tbody>
</table>
Through some JavaScript table rows get added to sem1 like so.
JavaScript:
First pos0[0] (an array) gets populated:
for(var i in setPos[0]){
setPos[0][i]=("<tr><td class = " + String(setClass[i])+ ">" + setPos[0][i].slice(2) + "</td><td class = 'someClass'>"+setThis[i]+"</td></tr>");
}
Then pos0[0][a] gets added to sem1 like this:
for(var a in pos0[0]){
document.getElementById("sem1").innerHTML += pos0[0][a];
}
and when I try to make the rows draggable, it just doesn't work.
jQuery:
$("#sem1 tr").draggable()
Putting just tr in the selector doesn't work either (I don't mind if all the table rows in the whole document are draggable)
**I know that the code says setPos[0] - it's part of a function that does the same thing to pos1, pos2...
Thanks in advance for any help!
My guess is that you are calling the $("#sem1 tr").draggable() line before the code that has inserted the new <tr>'s has been run, so it doesn't see the rows you've added.
Also, have you tried manually inserting some markup to check that the draggable code actually works on a per row basis?
It would help if you could post an example in jsfiddle or something so we can work on this with you.
Finally it could be overkill for this situation but have you looked into using a JavaScript templating engine if you are going to be building chunks of html in your app?

Changing the background colour of a given row in a table

I need to change the background color of a given row in a table based on a value of a model (boolean) property which is used in the #Html.CheckBox. The model is updated with the new checkbox value in the PostExampleCompleted action method.
<table>
<thead>
<tr>
<th>Item name</th>
<th>Comments</th>
<th>User</th>
<th>Complete</th>
</tr>
</thead>
<tbody>
<tr id="FooRow">
<td>Foo</td>
<td>#Model.FooComments</td>
<td>#Model.FooUserName</td>
<td>
#using (Ajax.BeginForm("PostFooCompleted", "Home", new AjaxOptions { OnBegin = "ShowProcessingMsg", OnComplete = "HideProcessingMsg" }))
{
#Html.CheckBox("FooItemComplete", Model.FooComplete, new { onClick = "$(this).parent('form:first').submit();" })
}
</td>
</tr>
<tr id="WidgetRow">
<td>Widget</td>
<td>#Model.WidgetComments</td>
<td>#Model.WidgetUserName</td>
#using (Ajax.BeginForm("PostWidgetCompleted", "Home", new AjaxOptions { OnBegin = "ShowProcessingMsg", OnComplete = "HideProcessingMsg" }))
{
#Html.CheckBox("WidgetItemComplete", Model.WidgetComplete, new { onClick = "$(this).parent('form:first').submit();" })
}
</td>
</tr>
</tbody>
</table>
What would be the best way to achieve this? Code examples would be appreciated :).
Thanks.
Something like this should do the trick, given that I've understood what you're trying to do correctly.
First here's a css class which I've used to colour a row if a checkbox is checked.
.redBackground
{
background-color: Red;
}
Next, here is some JQuery code to colour the row where the check-box resides if it is checked. I've also added a 'change' handler to each check-box so if any of them are updated, the row colour is updated appropriately (I've just used red for a row where a check-box is checked and no colour where a check-box is not checked).
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script type="text/javascript">
$(function () {
$('input[type=checkbox]').each(function () {
var checkbox = $(this);
// if the checkbox is already checked, colour its row
CheckStatus(checkbox);
//Every time a check-box status changes we
//want to re-evaluate the row colour
checkbox.change(function () {
CheckStatus(checkbox);
});
});
//Method which checks if the check-box is checked. If it's checked
//the row is given the 'redBackground' class, otherwise it is taken away
function CheckStatus(checkbox) {
if (checkbox.attr('checked') == 'checked') {
checkbox.parent().parent().addClass('redBackground');
}
else {
checkbox.parent().parent().removeClass('redBackground');
}
}
});
</script>
Hopefully this makes sense... :)
The code is a little to small to exactly get what your settings are.
From the fact that you have a Element I deduce that you have multiple rows in a table.
Personally I'd object to having a AjaxForm in each of the rows. That seems like a lot of overhead.
You could very easy have a single construct of jQuery code to handle all rows and perform the neccessary tasks by itself.
If you use the live function of jQuery you are even independent of rows being added.
The checkbox or the row need to have an identifier with which you can send the ajax call back to your controller. On success you can evaluate the state of the checkbox and color the row appropriately.
I would have to see more of the code, to really help you with that.
Maybe I'm missing something. This seems like a job for html or css. As far as I can make out, there are several ways to approach this depending on your requirements.
On a row by row basis (i.e., you will never know what the colours are until they are defined somewhere by the app or in a database where storing their value statically is not an option), assign the html style property <tr style='background-color: #Model.Colour'> on the row to set the background-color to the colour you want.
On a type/state basis (i.e., you know what the colours are going to be ahead of time, but not what rows they are going to be assigned to), I'd define a class for each row: <tr class='#Model.Type'> and then assign the background-color in your CSS class with the same name as the value in Model.Type.
To get that colour/type itself, you are going to want to pass it in using your model. You might already have that property on your model, I don't know.
For simple alternating pattens, simply use this technique or the :nth-child(odd) and :nth-child(even) css selectors.
Sometimes the simplest solutions are the best solutions.

Resources