jQuery datatable, angularjs and scope changes - javascript

Description
I'm using AngularJS
I have a table that I want to show it as "Data Table" using DataTable jQuery plugin (http://datatables.net/docs/DataTables/1.9.4/DataTable.html).
The logic
Create table table markup.
Add ng-repeat using $scope.entities on the row .
Run the plugin on the table.
The problem
In the first time that I run it, everything is OK.
The problem start when I need to change the $scope.entities (For example: user select another list)
In the snippets below I was created simpler scenario but the logic is same.
Steps
Run the snippet (probably done automatic).
Click on destroy button. It will destroy the plugin and return the table to original html.
Click on Add row. It should add row to the table The problem is that all the rows were deleted instead of adding the row.
Note
I saw some angular directives who integrating with the plugin (datatable). I can't using it. I need to understand why the view nodes are deleting.
angular.module('myApp', []).
controller('ctrl', function($scope, $timeout) {
$scope.test = 'test';
$scope.generateData = function() {
var temp = [];
for (var i = 0; i < 5; i++) {
temp.push({
d1: Math.floor(Math.random() * 10),
d2: Math.floor(Math.random() * 10),
d3: Math.floor(Math.random() * 10)
});
}
$scope.entities = temp;
$timeout(function(){
$scope.dt();
});
};
$scope.dt = function(){
$timeout(function() {
if (!$scope.dataTable) {
$scope.dataTable = $('table').dataTable();
}
});
}
$scope.destroy = function(){
if ($scope.dataTable) {
$scope.dataTable.fnDestroy();
}
}
$scope.addRow = function(){
$scope.entities.push({
d1: Math.floor(Math.random() * 10),
d2: Math.floor(Math.random() * 10),
d3: Math.floor(Math.random() * 10)
});
}
$scope.generateData();
});
<script src="https://code.jquery.com/jquery-2.1.4.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.min.js"></script>
<link href="//cdn.datatables.net/1.9.4/css/jquery.dataTables.css" rel="stylesheet" />
<script src="//cdn.datatables.net/1.9.4/js/jquery.dataTables.min.js"></script>
<div data-ng-app="myApp" data-ng-controller="ctrl">
<table>
<thead>
<tr>
<th>Col1</th>
<th>Col2</th>
<th>Col3</th>
</tr>
</thead>
<tbody>
<tr data-ng-repeat="entity in entities">
<td data-ng-bind="entity.d1"></td>
<td data-ng-bind="entity.d2"></td>
<td data-ng-bind="entity.d3"></td>
</tr>
</tbody>
</table>
<button data-ng-click="destroy();">Destroy</button>
<button data-ng-click="addRow();">Add rows</button>
</div>

I believe the issue is you have your ng-repeat upon the <tr> element. Calling fnDestroy() seems to to delete and re-insert all the original elements in the DOM.
I believe because of this Angular looses reference to the template and it was as if the ng-repeat was never there in the first place. As Angular is not removing and creating the new elements itself. Adding {{entities}} in your markup show that the array is correct and that the new item is being added.
I'm not familiar with data tables so don't know if it has events etc that you need to destroy with fnDestroy() but could you not empty the entities array instead so that angular removes the table? Thus not loosing its bindings? Or will the structure of the table change, different number of headers etc.
EDIT: Found this post on the data tables forums. Apparently you can use the data tables API to manipulate the table, add, remove rows etc. They advise, and i agree, that you shouldn't have multiple libraries manipulating the DOM. One option is to have Angular instruct data tables what to do with the table and let data tables actually manipulate it. You could wrap this in your own minimal directive or service also.
https://www.datatables.net/forums/discussion/21286/datatables-and-angularjs#Comment_61703

Related

sending html data to view and then sending to controller to update sql database

I have a HTML table where one of the columns value is dynamically added. I have an update button, upon clicking it I want this data to get updated in my sql database. For this, I am planning to first fetch the table data and put into view , then send data to controller and then updating sql.
I am stuck at the first step,
Descibing table below
<thead>
<tr>
<th>ID</th>
<th >Name</th>
<th>Active</th>
<th>Order By</th>
</tr>
</thead>
<tbody>
#if (ViewBag.data != null)
{
foreach (var item in ViewBag.data)
{
<tr>
<td >#item.AutoID</td>
<td #item.Text</td>
<td >#item.Active</td>
<td>#item.OrderBy</td>
</tr>
}
}
</tbody>
</table>
</div>
</div>
<input type="submit" value="Update Preference" class="BtnUpdOrderId" />
</div>
I tried this below js function to fetch the data
$(".BtnUpdOrderId").click(function () {
var tr = $(this).closest('tr');
var id = tr.find('input[name="autoid"]').val();
var text = tr.find('input[name="text"]').val();
var active = tr.find('input[name="active"]').val();
var orderby = tr.find('input[name="orderby"]').val();
alert('type1 : ' + id + ' ' + text + ' ' + active + ' ' + active);
});
but not sure why nothing came in alert
var TableData = new Array();
$('#tblLookup1 tr').each(function (row, tr) {
TableData = TableData + $(tr).find('td:eq(0)').text();
alert(TableData);
});
then tried the above block of code to get data in a variable but still not able to get anything.
Once I get the data I can try sending from view->controller.
So need the following help:
what mistake am I making?
once this is fixed, how to send data to sql? (this is a ado.net based mvc project)
you might want to consider creating a json object:
Creating json object in mvc and returning from controller
then build your table Convert JSON array to an HTML table in jQuery
finally, the update need only post back the json object
https://dontpaniclabs.com/blog/post/2013/02/27/posting-json-data-to-an-mvc-controller-via-ajax/
if you going to use this jason object make sure you use serialization
https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-how-to?pivots=dotnet-6-0
your have to patch these concepts together but there are a lots of tutorials and examples online so it be a good learning experience
I hope this helps
helpful links:
https://www.sqlshack.com/modifying-json-data-using-json_modify-in-sql-server/
Updating a JSON object using Javascript
https://www.geeksforgeeks.org/how-to-convert-json-data-to-a-html-table-using-javascript-jquery/
create json object from html table using selected colums using jquery

dojo dstore insert row

Can anyone assist me in inserting a row into a DGRID? The way I am doing it now is cloning a row, add it to the collection with the use of directives and then try to apply it to the grid. Below is the code I am using but the new row ends up getting added to the bottom instead of being inserted.
// Clone a row
theTable = tmxdijit.registry.byId(tableName);
firstRow = theTable.collection.data[theTable.collection.data.length-1];
firstRowDom = theTable.row(firstRow.id);
var cloneRow = json.stringify(firstRow);
cloneRow = json.parse(cloneRow);
// Get the row I want to add before
var theSelected = Object.keys(theTable.selection)[0];
if(theSelected.length > 0) {
var theRowID = theSelected[0];
}
theTable.collection.add(cloneRow, {beforeId: theRowID});
theTable.renderArray([cloneRow]);
There are two general ways to handle data insertion. One is to manually add data to an array, ensure it's properly sorted, and then tell the grid to render the array. A better way is to use an OnDemandGrid with a trackable store.
For dgrid/dstore to support simple dynamic insertion of rows, make sure the store is trackable, and that data items have some unique id property:
var StoreClass = Memory.createSubclass(Trackable);
var store = new StoreClass({ data: whatever });
var grid = new OnDemandGrid({
columns: { /* columns */ },
collection: store
});
By default dstore assumes the id property is "id", but you can specify something else:
var store = new StoreClass({ idProperty: "name", data: whatever });
If you want the data to be sorted, a simple solution is to set a sort on the grid (the grid will sort rows in ascending order using the given property name):
grid.set('sort', 'name');
To add or remove data, use the store methods put and remove.
var collection = grid.get('collection');
collection.put(/* a new data item */);
collection.remove(/* a data item id */);
The grid will be notified of the store update and will insert or remove the rows.
The dgrid Using Grids and Stores tutorial has more information and examples.
Instead of this, why don't you add the data directly to the grid store? See if this helps
https://dojotoolkit.org/reference-guide/1.10/dojox/grid/example_Adding_and_deleting_data.html
Adding and Deleting data
If you want to add (remove) data programmatically, you just have to add (remove) it from the underlying data store. Since DataGrid is “DataStoreAware”, changes made to the store will be reflected automatically in the DataGrid.
dojo.require("dojox.grid.DataGrid");
dojo.require("dojo.data.ItemFileWriteStore");
dojo.require("dijit.form.Button");
.
<div>
This example shows, how to add/remove rows
</div>
<table data-dojo-type="dojox.grid.DataGrid"
data-dojo-id="grid5"
data-dojo-props="store:store3,
query:{ name: '*' },
rowsPerPage:20,
clientSort:true,
rowSelector:'20px'
style="width: 400px; height: 200px;">
<thead>
<tr>
<th width="200px"
field="name">Country/Continent Name</th>
<th width="auto"
field="type"
cellType="dojox.grid.cells.Select"
options="country,city,continent"
editable="true">Type</th>
</tr>
</thead>
</table>
<div data-dojo-type="dijit.form.Button">
Add Row
<script type="dojo/method" data-dojo-event="onClick" data-dojo-args="evt">
// set the properties for the new item:
var myNewItem = {type: "country", name: "Fill this country name"};
// Insert the new item into the store:
// (we use store3 from the example above in this example)
store3.newItem(myNewItem);
</script>
</div>
<div data-dojo-type="dijit.form.Button">
Remove Selected Rows
<script type="dojo/method" data-dojo-event="onClick" data-dojo-args="evt">
// Get all selected items from the Grid:
var items = grid5.selection.getSelected();
if(items.length){
// Iterate through the list of selected items.
// The current item is available in the variable
// "selectedItem" within the following function:
dojo.forEach(items, function(selectedItem){
if(selectedItem !== null){
// Delete the item from the data store:
store3.deleteItem(selectedItem);
} // end if
}); // end forEach
} // end if
</script>
</div>
.
#import "{{ baseUrl }}dijit/themes/nihilo/nihilo.css";
#import "{{ baseUrl }}dojox/grid/resources/nihiloGrid.css";

angularjs smart-table programmatically sort

I have a table set up using the smart-table plug in for AngularJS. Everything appears to work nicely. Rather than having the user click on the table header to trigger a sort, I'd like to programmatically trigger sorting from my Angular controller. I do not see a way of doing this in the documentation here:
http://lorenzofox3.github.io/smart-table-website/
Am I overlooking something?
Found this on JSFiddle, might help you: http://jsfiddle.net/vojtajina/js64b/14/
<script type="text/javascript" ng:autobind
src="http://code.angularjs.org/0.10.5/angular-0.10.5.js"></script>
<table ng:controller="SortableTableCtrl">
<thead>
<tr>
<th ng:repeat="(i,th) in head" ng:class="selectedCls(i)" ng:click="changeSorting(i)">{{th}}</th>
</tr>
</thead>
<tbody>
<tr ng:repeat="row in body.$orderBy(sort.column, sort.descending)">
<td>{{row.a}}</td>
<td>{{row.b}}</td>
<td>{{row.c}}</td>
</tr>
</tbody>
</table>
A quick hack i found on how to do this is by setting the table header st-sort property and then triggering a click() on that element
<tr>
<th id="myelement" st-sort="date" st-sort-default="reverse">Date</th>
...
</tr>
Then later:
setTimeout(function() {
document.getElementById('myelement').click()
},
0);
Here is the 'angular' way to do this. Write a directive. This directive will have access to the smart table controller. It will be able to call the controller's sort by function. We will name the new directive stSortBy.
The below HTML includes the standard smart table syntatic sugar. The only new attribute directive here is st-sort-by. That's where the magic will happen. It's bound to a scoped variable sortByColumn. This is a string value of the column to sort
<table st-sort-by="{{sortByColumn"}}" st-table="displayedCollection" st-safe-src="rowCollection">
<thead>
<tr>
<th st-sort="column1">Person</th>
<th st-sort="column2">Person</th>
</tr>
</thead>
</table>
<button ng-click="toggleSort()">Toggle sort columns!</button>
Here is the stSortBy directive that hooks into the st table controller
app.directive('stSortBy', function() {
return {
require: 'stTable',
link: function (scope, element, attr, ctrl) {
attr.$observe('stSortBy', function (newValue) {
if(newValue) {
// ctrl is the smart table controller
// the second parameter is for the sort order
ctrl.sortBy(newValue, true);
}
});
}
};
});
Here is the controller. The controller sets the sort by in it's scope
app.controller("MyTableWrapperCtrl", ["$scope", function($scope) {
$scope.sortByColumn = 'column2';
$scope.toggleSort = function() {
$scope.sortByColumn = $scope.sortByColumn === 'column2' ? 'column1' : 'column2';
// The time out is here to guarantee the attribute selector in the directive
// fires. This is useful is you do a programmatic sort and then the user sorts
// and you want to programmatically sort back to the same column. This forces a sort, even if you are sorting the same column twice.
$timeout(function(){
$scope.sortByColumn = undefined;
}, 0);
};
}]);

angular performance: critical rendering path?

Im trying to optimice the page loading speed when rendering tables with many rows(columns min. 25x).
I am not experienced debugging/improving performance on angular apps so quite lost on what could be involved in this lack of speed.
Here is Chrome timeline report for 5 row query:
Here is Chrome timeline report for 100 row query:
The XHR load(api/list/json/Chemical...) increases in time as more rows are rendered on the table.
The server response with the data is returned fast(not the bottle neck):
Here is the template for the table:
<tbody ng-if="compressed">
<tr ng-if="dbos && (rows.length == 0)">
<td class="tableColumnsDocs"><div class="tableButtons"> </div></td>
<td class="tableColumnsDocs"><div>No results</div></td>
<td class="tableColumnsDocs" ng-repeat="attobj in columns track by $index" ng-if="$index > 0">
<p> </p>
</td>
</tr>
<tr class="tableRowsDocs" ng-repeat="dbo in rows track by $index">
<td class="tableColumnsDocs"><div ng-include="'link_as_eye_template'"></div></td>
<td class="tableColumnsDocs" ng-repeat="attobj in columns track by $index">
<div ng-init="values = dbo.get4(attobj.key); key = attobj.key; template = attobj.template || getAttributeTemplate(dbo.clazz + attobj.key);">
<div class="content" ng-include="template"></div>
<div class="contentFiller" ng-include="template"></div>
</div>
</td>
</tr>
</tbody>
And here templates the table will call:
<script type="text/ng-template" id="plain_values_template">
<p ng-repeat="v in values track by $index">{{ v }}</p>
</script>
<script type="text/ng-template" id="links_as_dns_template">
<div ng-repeat="dbo in values track by $index" ng-include="'link_as_dn_template'"></div>
</script>
<script type="text/ng-template" id="json_doc_template">
<textarea class="form-control" rows="{{values.length + 2}}" ng-trim="false" ng-readonly="true">{{ values | json }}</textarea>
</script>
<script type="text/ng-template" id="link_as_dn_template">
<p>{{ dbo.displayName() }}</p>
Relevant controller part:
$scope.getAttributeTemplate = function(str) {
//console.log("getAttributeTemplate"); console.log(str);
if ($templateCache.get(str + ".template")) {
return str + ".template";
}
var a = str.split(/(>|<)/);
//console.log(a);
if ((a.length - 1) % 4 == 0) {
return "links_as_dns_template";
}
var clsname = a[a.length - 3];
if (clsname == "*") {
return "plain_values_template";
}
var attname = a[a.length - 1];
var cls = datamodel.classes[clsname];
var att = cls.attribute[attname];
if (!att) {
return "plain_values_template";
}
if (att.type == "ref") {
return "links_as_dns_template";
}
return "plain_values_template";
};
I am new to angular and performance opt. so any tips on how to improove or bad practice highlight will be very helpful!
Long tables are angular's biggest evil, because of the hell-as-slow base directives such as ng-repeat
Some easy and obvious stuffs :
I see a lot of bindings in the row/cell templates without one-time binding (::). I dont think your row data is mutating. switching to one-time bindings will reduce the watchers count -> perf.
Some harder stuff :
Quick answer :
dont let angular handle the performance bottleneck
Long answer :
ng-repeat is supposed to compile it's transcluded content once. But using ng-include is killing this effet, causing every row to call compile on their ng-included contents. The key for good performance in big table is to be able to generates (yea, manually, which $compile, $interpolate and stuff) a unique compiled row linking function, with less as possible angular directives - ideally only one-time expression bindings, and to handle row addiction/removal manually (no ng-repeat, you own directive, your own logic)
You should AT LEAST find a way to avoid the second nested ng-repeat on' ng-repeat="attobj in columns track by $index"'. This is a dual repeated on each row, killing compilation &linking (rendering perf) and watcher count (lifecycle perf)
EDIT : as asked, a "naive" example of what can be done to handle the table rendering as manually (and fast) as possible. Note that the example does not handle generating the table header, but it's usually not the hardest thing.
function myCustomRowCompiler(columns) {
var getCellTemplate = function(attribute) {
// this is tricky as i dont know what your "getAttributeTemplate" method does, but it should be able to return
// the cell template AS HTML -> you maybe would need to load them before, as getting them from your server is async.
// but for example, the naive example to display given attribute would be
return $('<span>').text("{{::model."+ attribute +"}}"); // this is NOT interpolated yet
};
var myRowTemplate = $('<tr class="tableRowsDocs">');
// we construct, column per column, the cells of the template row
_.each(columns, function(colAttribute, cellIdx) {
var cell = $("<td>");
cell.html(getCellTemplate());
cell.appendTo(myRowTemplate);
})
return $compile(myRowTemplate); // this returns the linking function
}
and the naive usage :
function renderTableRows(dbos, columns) {
var $scope; // this would be the scope of your TABLE directive
var tableElement = $el; // this would be your table CONTENT
var rowLinker = myCustomRowCompiler(columns); // note : in real life, you would compile this ONCE, but every time you add rows.
for(var i=0; i<dbos; i++) {
var rowScope = $scope.$new(); // creating a scope for each row
rowScope.model = dbos[0]; // injecting the data model to the row scope
rowLinker(rowScope, function(rowClone) { // note : you HAVE to use the linking function second parameter, else it will not clone the element and always use the template
rowClone.appendTo(tableElement);
});
}
};
This is the approach i've been using to my own projects's table framework (well, more advanced, but this is really the global idea), allowing to use angular power to render the cell content ( 'getCellTemplate' implementation can return html with directive, which will be compiled), using filter even including directives in the cell, but keeping the table rendering logic to myself, to avoid useless ng-repeat watch, and minimizing the compilation overheat to it's minimum.

how I get the values of each row and column of a html table in c# codebehind which are dynamically generated by angularjs ng-repeat

I have a html table. the rows of this table are generated automatically by aangular js ng-repeat. I want to get values of each row and column of that table in c# codebehind.
Here is my aspx code:
<table id="stockTable" runat="server" class="table table-bordered">
<thead>
<tr>
<td>Name</td>
<td>Quantity</td>
</tr>
</thead>
<tbody>
<tr ng-repeat="aMedicine in medicines">
<td>{{aMedicine.Name}}</td>
<td>{{aMedicine.Quantity}}</td>
</tr>
</tbody>
</table>
and script is here:
<script>
var myApplication = angular.module("myApp", []);
var myArray = [];
myApplication.controller("myController", function ($scope) {
$scope.medicines = [];
$scope.AddMedicine = function (name, quantity) {
$scope.medicines.push({ Name: name, Quantity: quantity });
myArray = medicines;
};
});
</script>
how i get the added rows of the table in c# codebehind?
You are thinking about this in a reverse way.
You don't get values out of DOM to process data; instead, the values already exist in your ViewModel (in the controller), and the DOM is constructed based on them. In other words, the DOM is just some reflection of your ViewModel; the ViewModel is your source-of-truth for the data.
And so, if you have something like:
$scope.medicines = [...];
Then this is your ViewModel - just submit that.
If you are asking about how to determine which entries (that manifest themselves as rows, but that should not matter) were added vs. being originally retrieved from the backend, then you could just store the newly created elements in a temp array, until, say, the form is saved:
var newEntries = [];
$scope.AddMedicine = function (name, quantity) {
var newEntry = { Name: name, Quantity: quantity };
$scope.medicines.push(newEntry);
newEntries.push(newEntry);
};
$scope.Save = function(){
// ... Some code that submits to backend using MedicineSvc
MedicineSvc.AddMedicine(newEntries)
.then(function(){
newEntries = [];
});
}

Categories

Resources