UI-grid Swapping data on same grid - angularjs

I have maintained One grid , and for each data from the back end replaced in the same grid . The grid works fine for the first time whichever data it is , but for the second time there is some problem with the css or the template . Here is the link for the plunker
Given below is the handler for each of the data that is loaded from the back end
$scope.$on('dataLoaded', function (event, gridData, path, ext) {
$activityIndicator.stopAnimating(300);
// Setting up the path and extension for use in the service
$scope.path = path;
$scope.extension = ext;
// There is a change in column for every different data set , so building the headers with the data
var def = new Array();
angular.forEach(Object.keys(gridData[0]), function (key) {
def.push(
{
field: key, displayName: key
//headerCellTemplate:'views/header-template.html'
});
});
//Specify the columnDef: Each different data is supposed to have different columnDefs
$scope.gridData = gridData;
$scope.gridOptions.columnDefs = def;
});

Related

Create tree in angular-grid (ag-grid) with async data loading

I am trying to use angular-grid (ag-grid) to display a tree like in the example provided in the documentation:
http://www.angulargrid.com/example-file-browser/index.php
In the given example, all the data is already provided. How do I use async data loading when a row group is expanded? My guess is that i need to write my own group row renderer.
I came recently to the same problem in my React.js app and found solution. It's similar to what #leden posted but I found solution how to maintain current row expansions between table rows update.
The solution is as follow:
Add dummy child row for each top-level row. Can be empty or can have loading... string for example in first column.
On event getNodeChildDetails, which is called each time you update your table rowData, you can specify if a row should be expanded or not. So the idea is that we keep track of what is expanded and what is not.
getNodeChildDetails = (rowItem) => {
if (rowItem.children) {
return {
group: true,
expanded: rowItem.id in this.expandedRows,
children: rowItem.children,
};
}
else {
return null;
}
};
On event rowGroupOpened we keep track which rows are expanded.
rowGroupOpened = (param) => {
const id= param.node.data.id;
if(!param.node.expanded) {
delete this.expandedRows[id];
return;
}
this.expandedRows[id] = true;
if (param.node.data.children.length !== 1) { // Here we need to check if only dummy row is present
return;
}
this.api.showLoadingOverlay();
// Here I simulate fetching data from server
setTimeout(() => {
this.rowData.forEach((e) => {
if (e.id == id) {
e.children = [
// Add fetch rows
]
}
});
this.api.setRowData(this.rowData); // Setting data, will trigger getNodeChildDetails call on each row
this.api.hideOverlay();
}, 1000);
};
The grid doesn't support lazy loading of the tree data out of the box. So yes you would have to write your own cellRenderer to achieve this.
PS I'm the author of ag-Grid, so you can take this answer as Gospel!
Just an idea, but I think that you could add a single placeholder child row to the group with "loading..." in the first cell, with the group's onRowGroupOpened event set to make the ajax call to get the data from the server, with the onreadystatechange then adding the new rows and replacing the placeholder one. The initial placeholder row can contain server-calculated total values to drive aggregation (total) values in the group row's cells, which would remain the same when real data replaces the placeholder.
I have come up with a basic test of the approach. It's not perfect, as the grid rebuilds after each expansion (I can't find an elegant way to just append the new rows), but it does work.
At the very top of the script is the AJAX call for detail. Although this happens later in the flow I put it at the top, so that if the server receives this request, it provides data and exits, without loading the page again. Alternatively you could just put it into another file.
<?php
if (isset($_REQUEST['g'])) { // this is the AJAX request for child data (called later, but needed at the start of the script)
// get connection to database
require_once 'db_connection.php'; $dbh=getConnection();
// query data to array
$sql="SELECT accounts.description AS account, '' AS info,
tx.amnt AS amount, 1 AS transactions
FROM tx
INNER JOIN accounts ON tx.account=accounts.account_id
WHERE accounts.description='".$_REQUEST['g']."'";
$data=array();
$result = $dbh->query($sql);
while ($row = $result->fetch_assoc()) {
$data[]=$row;
}
$result->free();
// return data as JSON
print json_encode($data, JSON_NUMERIC_CHECK);
exit;
}
?>
Then immediately after that comes a normal HTML page with a little bit more php within the javascript in the head:
<!DOCTYPE html>
<html>
<head>
<script src="lib/ag-grid-enterprise-master/dist/ag-grid-enterprise.js"></script>
<script>
// get JSON for initial group-level data from server with a little snippet of php which is called when the page is first loaded
var rowData =
<?php
// get connection to the database
require_once 'db_connection.php'; $dbh=getConnection();
// query data to array
$sql = "SELECT description AS account, 'loading...' AS info,
SUM(tx.amnt) AS amount, COUNT(tx.tx_id) AS transactions
FROM accounts
INNER JOIN tx ON accounts.account_id=tx.account
GROUP BY accounts.account_id";
$data=array();
$result = $dbh->query($sql);
while ($row = $result->fetch_assoc()) {
$data[]=$row;
}
$result->free();
// inject the JSON into the javascript assignment to rowData
print json_encode($data, JSON_NUMERIC_CHECK);
?>;
// (back in javascript again)
// event function for when a group is expanded
function getChildRows(data) {
if (data.node.allLeafChildren) {
if (data.node.allLeafChildren.length > 0) {
if (data.node.allLeafChildren[0].data.info==="loading...") {
// data for this group has not yet been loaded, so make AJAX request for it
var xmlHttp=new XMLHttpRequest();
xmlHttp.onreadystatechange=function() {
if ((xmlHttp.readyState===4) && (xmlHttp.status === 200)) {
// call function to add the new rows to the grid
addRecords(JSON.parse(xmlHttp.responseText));
}
};
var requestParameters="g="+encodeURIComponent(data.node.key);
xmlHttp.open("POST", "index.php", true); // call to this same script
xmlHttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xmlHttp.send(requestParameters);
}
}
}
}
function addRecords(data) {
var x; var d=new Array();
var acc=data[0].account;
for(x in gridOptions.api.inMemoryRowModel.rootNode.allLeafChildren) {
if (gridOptions.api.inMemoryRowModel.rootNode.allLeafChildren[x].data.account===acc) {
// this is group we are replacing with new data
for (x in data) {
d.push(data[x]);
}
} else {
// this node is just the data as currently loaded to the grid (no change)
d.push(gridOptions.api.inMemoryRowModel.rootNode.allLeafChildren[x].data);
}
}
gridOptions.api.setRowData(d);
}
// set up the grid (standard stuff)
var columnDefs = [
{headerName: "Account", field: "account", rowGroupIndex: 0, cellRenderer: "group", cellRendererParams : {suppressCount: true} },
{headerName: "Info", field: "info"},
{headerName: "Amount", field: "amount", aggFunc:"sum"},
{headerName: "Transactions", field: "transactions", aggFunc:"sum"}
];
var gridOptions = {
columnDefs: columnDefs,
rowData: rowData,
groupSuppressAutoColumn: true,
onRowGroupOpened: getChildRows /* event created above */
}
document.addEventListener("DOMContentLoaded", function() {
var eGridDiv = document.querySelector('#myGrid');
new agGrid.Grid(eGridDiv, gridOptions);
});
</script>
</head>
<body>
<div id="myGrid" style="height: 100%;" class="ag-fresh"></div>
</body>
</html>
#Niall - any ideas on how to add the new rows more elegantly and retain status of group expansion?

Routing in SAPUI5: How to implement passing of URL? Model data not initialy loaded

My goal is to write a SAPUI5 Fiori app with routing support. One mail goal is to have passable URLs. For example in an E-Mail like "please approve this: link". The link is an URL matched by my rounting config, e.g.index.html#/applicants/8.
I use a typical sap.m.SplitApp kind of application. Clicking a list item in masterview changes the URL to index.html#/applicants/[id of entry in JSON]. I can click on the list, my defined routes are getting matched and the apps loads the (applicant) data as expected.
However, and here comes my question, this doeas not work when using an URL directly, say pasting [my url]/index.html#/applicants/8 into my browser. The app is launched but no detail data is loaded. I have to click on another list item again to get the data.
Actually, the controller is called when passing the URL, but it seems the model is not initiated and undefined. My JSON model is bound in the createContent function of my Component.js
// Update 2015-05-14
The problems seems to be around the getData() function. I have the model, it has the entries, but getData() returns undefined for the first time my app is loaded. I recently read getData() is deprecated. How should I improve my coding below?
// Component.js
ui5testing.Component.prototype.createContent = function(){
// create root view
var oView = sap.ui.view({
id : "app",
viewName : "ui5testing.view.Main",
type : "JS",
viewData : {
component : this
}
var oModel = new sap.ui.model.json.JSONModel("model/mock_applicants.json");
oView.setModel(oModel);
[...]
return oView;
});
// Master controller
handleApplicantSelect : function (evt) {
var oHashChanger = sap.ui.core.routing.HashChanger.getInstance();
var context = evt.getParameter("listItem").getBindingContext();
var path = context.getPath();
var model = this.getView().getModel();
var item = model.getProperty(path);
oHashChanger.setHash("applicants/" + item.id);
},
// Detail controller
onInit: function() {
this.router = sap.ui.core.UIComponent.getRouterFor(this);
this.router.attachRoutePatternMatched(this._handleRouteMatched, this);
},
_handleRouteMatched : function(evt){
var objectId = evt.getParameter("arguments").id;
var model = this.getView().getModel();
var data = model.getData()["applicants"];
var pathId;
if (data) {
for (var i = 0; data.length; i++) {
if ( objectId == data[i].id ) {
pathId = i;
break;
}
}
var sPath = "/applicants/" + pathId;
var context = new sap.ui.model.Context(model, sPath)
this.getView().setBindingContext(context);
}
},
As you've figured out that getData() returns undefined for the first time, which means the model data is still not yet loaded. So you can make use of attachRequestCompleted method of the model & fire an event from the component & listen to that event in the detail controller to ensure the routerPatternMatched() gets executed only after the data is loaded.
//Component.js
var oModel = new sap.ui.model.json.JSONModel("model/mock_applicants.json");
oModel.attachRequestCompleted(jQuery.proxy(function(){
this.fireEvent("MockDataLoaded"); // fireEvent through component
},this));
oView.setModel(oModel);
//Detail controller
onInit : function(){
this.router = sap.ui.core.UIComponent.getRouterFor(this);
var oComponent = this.getOwnerComponent();
oComponent.attachEvent("MockDataLoaded",jQuery.proxy(function(){
this.router.attachRoutePatternMatched(this._handleRouteMatched, this);
},this));
}
Or the simplest & but the dirty way would be to make an synchronous request instead of an async request to load data.
var oModel = new sap.ui.model.json.JSONModel();
oModel.loadData(""model/mock_applicants.json",{bAsync:false});
oView.setModel(oModel);

EXT JS store.loadData() is not appending the data

I am trying to use store.loadData(data, true) to append data to an existing store but for some reason it is clearing the store and replacing it with the new data which should only happen if the boolean is set to false which it is not. Is there something I am missing that I need to do to make sure the data is appended to the old data and not replacing it entirely?
Edit Additional code. Currently I am pulling a row from a grid and creating a new window with additional information for that object that is pulled from a database. The idea is that all the possible data for the rows is stored in one store and then when the window appears the store has a filter added so that you only see data that pertains to that particular object. At some point I iterate every single object in the grid and check to see if it has data that was edited. Which is an issue if I only have data from the last object that was edited.
editSelectedNode: function(grid, rowIndex, colIndex){
var store = Ext.getStore('EditStore');
var win = Ext.create('BOMGeneratorSencha.view.EditMenu', {});
var item = grid.getStore().getAt(rowIndex).get('original');
console.debug(item);
win.show();
var el = win.getEl();
store.clearFilter(true);
console.debug(store.getCount());
if(store.getCount() == 0){
el.mask('Loading Values');
console.debug(store.getCount());
Ext.Ajax.request({
url : 'EditPart.jsp',
timeout: 300000,
params : {
item: item
},
success: function (response, opt) {
el.unmask();
var res = Ext.JSON.decode(response.responseText);
if (res.success) {
console.debug(res.results);
store.loadData(res.results,true);
console.debug(store);
}
else {
console.debug("JSON failure");
Ext.Msg.alert('Error', 'Invalid part number');
}
},
failure: function(response,options){
console.debug("major failure");
el.unmask();
Ext.Msg.alert('Error', 'Connection failed<br>' + response.responseText);
}
});
}
}
I have a code that is similat to your one. But when i get response, I dont use
store.loadData(someData)
instead I am using following steps to load data(piece of my code placed here):
success: function(response, opts){
var obj = Ext.decode(response.responseText)
,data = obj.data
,$ = Ext.ComponentQuery;
var store = Ext.create('MyApp.store.SomeStore',{
data : data
});
$.query('SomeGrid')[0].bindStore(store);
$.query('SomeGrid')[0].refresh();
}

How can I set nested array values in meteor publish function?

I have two collection "contents" and "units". In the content collection is a field "unitID" which refers to the unit-collection. In the meteor publish function I want to add the unit type name of all new created contents:
Meteor.publish("contents", function () {
var self = this;
var handle = Contents.find().observe({
changed: function(contentdoc, contentid) {
var UnitName = Units.findOne({_id: contentdoc.unittypeid }, {fields: {type: 1}});
self.set("contents", contentid, {'content.0.typename': UnitName});
self.flush();
}
});
}
This works but it creates a new attribut "content.0.UnitName" instead of inserting the attribute "UnitName" in the first element of the content array:
[
{
_id:"50bba3ca8f3d1db27f000021",
'content.0.UnitName':
{
_id:"509ff643f3a6690c9ca5ee59",
type:"Drawer small"
},
content:
[
{
unitID:"509ff643f3a6690c9ca5ee59",
name: 'Content1'
}
]
}
]
What I want is the following:
[
{
_id:"50bba3ca8f3d1db27f000021",
content:
[
{
unitID:"509ff643f3a6690c9ca5ee59",
name: 'Content1',
UnitName:
{
_id:"509ff643f3a6690c9ca5ee59",
type:"Drawer small"
}
}
]
}
]
What am I doing wrong?
this.set within Meteor.publish only works on the top-level properties of an object, meaning it doesn't support Mongo-style dotted attributes. You'll have to call set with the entire new value of the contents array.
Caveat: What I am about to say is going to change in a future release of Meteor. We're currently overhauling the custom publisher API to make it easier to use, but in a way that breaks back-compatibility.
That said...
It looks like what you're trying to do is build a server-side join into the published collection "contents". Here, for reference, is the current code (as of 0.5.2) that publishes a cursor (for when your publisher returns a cursor object):
Cursor.prototype._publishCursor = function (sub) {
var self = this;
var collection = self._cursorDescription.collectionName;
var observeHandle = self._observeUnordered({
added: function (obj) {
sub.set(collection, obj._id, obj);
sub.flush();
},
changed: function (obj, oldObj) {
var set = {};
_.each(obj, function (v, k) {
if (!_.isEqual(v, oldObj[k]))
set[k] = v;
});
sub.set(collection, obj._id, set);
var deadKeys = _.difference(_.keys(oldObj), _.keys(obj));
sub.unset(collection, obj._id, deadKeys);
sub.flush();
},
removed: function (oldObj) {
sub.unset(collection, oldObj._id, _.keys(oldObj));
sub.flush();
}
});
// _observeUnordered only returns after the initial added callbacks have run.
// mark subscription as completed.
sub.complete();
sub.flush();
// register stop callback (expects lambda w/ no args).
sub.onStop(function () {observeHandle.stop();});
};
To build a custom publisher that is joined with another table, modify the added callback to:
check if the added object has the key you want to join by
do a find in the other collection for that key
call set on your subscription with the new key and value you want to be published, before you call flush.
Note that the above is only sufficient if you know the key you want will always be in the other table, and that it never changes. If it might change, you'll have to set up an observe on the second table too, and re-set the key on the sub in the changed method there.

extjs4 : chained combo

I'm trying to add some dynamic data into a "livesearch" combo box.
I've a set of 2 combos.
The first one allow you to select some data.
The second combo is a "livesearch" combo that should have a dynamic parameter from the first combo.
So the 2nd combo is linked to a model, which is linked to a datastore that queries the server and outputs the data. But that data has to be filtered according to the first combo parameter...
Anyone knows how to do that ?
I did that before. The key is to pass the value of the first combo with the request for the values of the second combo, and then filter the results on the server. Other approach would be to load both combos with all possible values and then set a filter on the second combo's store after a value is selected in the first combo.
EDIT: Here's the I used.
Ext.define('Ext.ux.FilteredCombo', {
extend: 'Ext.form.field.ComboBox',
alias: 'widget.filteredcombo',
queryCaching: false,
getParams: function (queryString) {
var params = this.callParent(arguments);
if (Ext.isArray(this.formParams)) {
var form = this.up('form');
if (form) {
var bf = form.getForm();
for (var i = 0; i < this.formParams.length; i++) {
var field = bf.findField(this.formParams[i]);
if (field)
params[this.formParams[i]] = field.getValue();
}
}
}
return params;
}
});

Resources