Why AngularJS multi-level accordion collapses on data update? - angularjs

I needed an AngularJS based multi-level accordion to bind a tree data structure from the back end and I came across this one https://github.com/LukaszWatroba/v-accordion.
It worked pretty well, except it fully collapses (closes any expanded sub accordions) every time data are updated in the controller. I have a timer in the controller that calls the back end to pool data at certain time intervals (e.g., every 3 minutes). I don't know if this is the accordion's built-in behavior or not, but if yes, how this could be fixed?
Any suggestions / help would be appreciated.

Resolved
I reached out to Łukasz Wątroba, the author of this plugin, and with his kind help I was able to resolve this task. Would like to share his feedback in case anybody has a similar goal.
Solution:
To your JSON data structure you would need to add two more properties,
1) the "id" of the pane and
2) the state of the pane "isExpanded" - true/false
These two properties are specifically designed for the front end, to maintain the expand-collapse state of any pane you might have interacted with.
[
{
id: 'pane-1b',
isExpanded: true,
.................
all your initial properies...
.....................
},
{
id: 'pane-2b'',
isExpanded: false
.................
all your initial properies
.....................
},
{
id: 'pane-3b,',
isExpanded: false
.................
all your initial properies
.....................
subpanes: [
{
id: 'subpane-1b',
isExpanded: false
.................
all your initial properies
.....................
},
{
id: 'subpane-2b',
isExpanded: false
.................
all your initial properies
.....................
}
]
}
]
Your HTML would have the following structure, where "panes" is your JSON data.
<v-pane id="pane.id" ng-repeat="pane in panes" expanded="pane.isExpanded">
.................
<!--For multi level HTML you would have a nested ng-repeat-->
<v-pane id="subpane.id" ng-repeat="subpane in pane.subpanes" expanded="subpane.isExpanded">
</v-pane>
.............
</v-pane>

Related

Ag-grid master detail prevent detail row from closing on data refresh

I'm currently doing a trial on AG-Grid master detail feature. Things are working fine but my data will be refreshed every 10 seconds. This caused the details to close when the data is refresh and I have to open the detail rows again.
Are there any options to save the state of the details that was opened?
Plunkr
Data is set to refresh every 5 seconds , expand the detail row and when the data refreshes the detail will be collapse. I've set rememberGroupStateWhenNewData : true
https://plnkr.co/edit/SgYD3vH8CXW9W9B8HD6N?p=preview
var gridOptions = {
rememberGroupStateWhenNewData:true,
columnDefs: columnDefs,
masterDetail: true,
detailCellRendererParams: {
detailGridOptions: {
rememberGroupStateWhenNewData:true,
columnDefs: [
{field: 'callId'},
{field: 'direction'},
{field: 'number'},
{field: 'duration', valueFormatter: "x.toLocaleString() + 's'"},
{field: 'switchCode'}
],
onFirstDataRendered(params) {
params.api.sizeColumnsToFit();
}
},
getDetailRowData: function (params) {
params.successCallback(params.data.callRecords);
}
},
onFirstDataRendered(params) {
params.api.sizeColumnsToFit();
}
};
A little late, but this may help others.. If you use Immutable data mode, and set the refresh mode of your detail to 'rows', your master and detail will update in-place.
Check these links for more info:
https://www.ag-grid.com/react-data-grid/immutable-data/
https://www.ag-grid.com/react-data-grid/master-detail-refresh/
The problem is that you're using api.setRowData to update the data.
https://www.ag-grid.com/javascript-grid-data-update/
This is the simplest of the update methods. When you call
api.setRowData(newData), the grid discards all previous selections and
filters, and completely overwrites the old data with the new. This was
the first way the grid worked and is the most 'brute force' way.
Use this method if you want to load the grid with a brand new set of
data.
This description does not match what you're trying to do, so you should use one of the other methods. Try api.updateRowData(transaction), there are plenty of examples for it in the demos.
Did you try rememberGroupStateWhenNewData?
https://www.ag-grid.com/javascript-grid-grouping/#keeping-group-state
have the same issue here, rememberGroupStateWhenNewData only works on row grouping, not master/detail grids.

Multiple filters on a collection with angular-meteor

I'm following the Meteor To-Do App tutorial with Angular integration and am learning about filtering collections. I've been able to implement a simple filter on a collection for the app I'm working on by following the principles in the tutorial, but now I'm stuck trying to figure out how to add multiple queries to the filter.
In the example, you can view incomplete tasks by toggling a checkbox. This is implemented in the controller by watching $scope.hideCompleted for changes and passing it as a Mongo query to filter the Meteor collection.
Watcher
$scope.$watch('hideCompleted', function() {
if ($scope.hideCompleted)
$scope.query = {checked: {$ne: true}};
else
$scope.query = {};
});
Collection filter
$scope.tasks = $meteor.collection(function() {
return Tasks.find($scope.getReactively('query'), {sort: {createdAt: -1}})
});
How do I make the query support multiple filters? For example, say I've chosen to extend the example and have ranked each to-do item by priority. I then would have an an input field for the user to filter the collection by priority, whose value is bound to $scope.priority. Now, if I wanted to filter the to-do list by incomplete and priority=$scope.priority tasks, I understand the Mongo query would need to be something along the lines of Tasks.find({ $and: [{ checked: {$ne: true} },{ priority: $scope.priority }]},{ sort: { createdAt: -1 } }).
In my app, I've been able to make two watchers properly track changes to two scope variables, analogous to my example with $scope.hideCompleted and $scope.priority, but I don't know how to take the next step to merge the queries when filtering the collection. I've also tinkered around a little with this package, since I'll eventually hope to be able to filter and sort by many criteria, but I didn't get too far with it before switching to the concepts I've described here.
I'd appreciate any help with this. Thank you!
This would be my approach:
$meteor.autorun($scope, function() {
// uncomment subscribe after you've got to that point
// $scope.$meteorSubscribe('yourSubscription').then(function() {
$scope.tasks = $scope.$meteorCollection(function() {
return Tasks.find({
checked: $scope.getReactively('model.hideCompleted'),
priority: $scope.getReactively('model.priority')
}, { sort: { createdAt: -1 } });
});
// });
});
A couple of things here:
Once you have removed autopublish you can uncomment the $scope.$meteorSubscribe method and replace "yourSubscription" with the name of your actual subscription.
$meteor.autorun will fire every time any getReactively variable changes.
$scope.$meteorSubscribe and $scope.$meteorCollection are favored as they will remove the subscriptions and object/collection when the scope is destroyed.
If you have any issues then perhaps I can setup a demo for you to look at.
Well, I guess I was a lot closer than I had expected, so I'll answer my question and share what I did to implement multiple filters with regards to the hypothetical extension of the to-do app.
I made hideCompleted and priority scope variables into properties of a scope object model, and used a single watcher with the argument true at the end to check for object equality (for any changes to model or its properties). I then generated $scope.query by stitching together "sub-queries." I've added the code below.
This seems to be working fine for now, but I'm not sure if it's the best solution, so I will continue experimenting, and I will update my answer if I find anything better. I'd be interested in any other approaches, though!
Watcher
var initQuery=true;
var subQueries=[];
$scope.$watch('model', function() {
if (!initQuery){
subQueries=[];
if ($scope.model.hideCompleted)
subQueries.push({checked: {$ne: true}});
if ($scope.model.priority)
subQueries.push({priority: $scope.model.priority});
$scope.query = { $and: subQueries};
} else {
initQuery = false;
$scope.query = {};
}
}, true);
Filter Collections (unchanged)
$scope.tasks = $meteor.collection(function() {
return Tasks.find($scope.getReactively('query'), {sort: {createdAt: -1}})
});

Kendo Grid datasource never updating

Kendo Grid is new to me, so I apologize for the ignorance. I'm writing an angular app that uses a separate service to update a local array. It is store in $scope.searchResults variable. I've initialized the grid using the dataSource ->transport property in the hopes that when the array mentioned above is updated, so too will the datasource and the grid updated accordingly. This is not the case. The array is updated, without any problems, but the datasource is never updated. I'll do my best to paste all the code snippets, and console output below.
Html:
<div class="margin-top-25" ng-show="searchResults">
<div id="report-grid" kendo-grid="grid" options="mainGridOptions"></div>
</div>
DataSource proper of the Grid configuration:
dataSource: {
transport: {
read: function read(options) {
options.success($scope.searchResults);
}
},
schema: {
model: {
id: "id",
fields: {
name: {type: "string"},
dataSource: {type: "string"}
}
}
},
pageSize: 10
}
Function for updating the datasource:
function runSearch() {
RetrieveReportsService.query({name: vm.searchData.name, dataSource: vm.searchData.dataSource},
function success(result) {
$log.log($scope.grid.dataSource);
$log.log($scope.searchResults);
$scope.searchResults = result.elements;
$log.log($scope.searchResults);
$scope.grid.dataSource.read();
$log.log($scope.grid.dataSource);
});
}
Now console output:
First time logging the data source:
O…e.e…d.init {options: Object, _map: Object, _prefetch: Object, _data: ObservableArray.extend.init[2], _pristineData: Array[2]…}
First time logging $scope.searchResults:
[Object, Object]
Second time logging $scope.searchResults:
[Object]
Second time logging the data source:
O…e.e…d.init {options: Object, _map: Object, _prefetch: Object, _data: ObservableArray.extend.init[2], _pristineData: Array[2]…}
Note that each data source has an observable array length of 2, before and after the $scope.searchResults has been updated.
I can drill down into the output if it is needed, but didn't want this post to get overwhelming.
Thanks!
Because you are referencing your data that is declared inside your options object by pointing k–options at your options object when the grid initially binds to the options object it's not populated with the data yet. If you reference your data source object separately using k-data-source it will bind to your data source and update your grid when the data source changes. If you want to make changes to your options object trigger a rerender you need to use k–rebind or alternatively the setOptions method. Be sure to read the docs for the latter as there are some caveats.
Well, I don't know why it is working the way that it is, but with the current project configuration this is the solution.
The grid configuration and the function for updating the data source are in the same module. The grid itself is initialized in another controller. I had move the function for updating to the controller that contained the grid and now it works perfectly.
If anyone knows why, feel free to chime in.

Angular Resource - Default Model Structure

So I'm using this Rest API with ngResource to do get, query, post and update requests. What I'm looking for, is a way to define the structure for each entity.
For example, assuming we have:
module.factory('app.entity.item', function($resource) {
return $resource('http://xmpl.io/items/:itemId', { itemId: '#id' });
});
I want to instantiate it in a controller like:
module.controller('AddItemCtrl', ['app.entity.item', function(Item) {
$scope.item = new Item();
});
and bind it to the respective form in my template.
The actual problem that I have run into, is that I have to deal with 1:m tables.
An example of the entity structure would be:
{
"name": "",
"categories": [],
"list": [
{
"value": "",
"list": [
{
"value": "",
"list": [
{
"value": ""
}
]
}
]
}
]
}
(A more thorough example in the fiddle below)
Now the first two fields are obviously not the problem. It is the third one. The list. Each one of these lists can have a variable number of items.
I am currently using ngRepeat and an add(type, context) method, which adds a new set of fields to the scope (value field in this example and child lists for the first two levels), which will appear in UI by ngRepeat so the user can fill it up and submit it to the service.
First off, I have to define the structure, so the UI would not be empty when the page loads.
module.controller('AddItemCtrl', ['app.entity.item', function(Item) {
$scope.item = new Item({
"name": "",
"categories": [],
"list": [
{
"value": "",
"list": [
{
"value": "",
"list": [
{
"value": ""
}
]
}
]
}
]
});
});
But that is redundant. I have to do it everywhere!
Another issue is that when the item.$save is called, the model is emptied (perhaps re-instantiated?) and the fields inside the list property (managed by the ngRepeat directive) are gone.
So I'm wondering, what would you do under such circumstances.
Is there a way to define the entity (resource) structure?
SAMPLE: http://jsfiddle.net/g15sqd5s/3/
trying to give simple answer - for simple structures I would use something like
module.factory('Item', function($resource) {
var resource = $resource('http://xmpl.io/items/:itemId', { itemId: '#id' },
// you can also define transformRequest here:
{ transformRequest: function(data) {
// data can be transformed here
return angular.toJson(data);
}});
return angular.extend(resource.prototype,
{
name: null,
categories: []
});
});
but then be aware of need to 'flatten' the object.
and for the more complex model I would check restangular
similar topic is also discussed here:
How can I extend the constructor of an AngularJS resource ($resource)?
I would go ahead and revise my model structure in the backend in the first place - the models on the client side should merely follow the ones already defined, rather than being re-defined in a transform block. So, to answer your question, the "default" model structure comes from the server. What you get in your $resource objects has the structure of what your server returns.
To start off, is it really ok to invoke $save on the Item model when the user has populated some values? What we want to save are obviously the lists associated with an item, not the item itself. A separate resource defined in the backend, say items/<item_id>/list, may be a cleaner solution. It may not scale very well, as you'll have to make a separate GET request for each item to fetch its list, but that's the proper RESTful way to do it.
Extending this approach to the example in your fiddle, I imagine a routing scheme like buildings/<building_id>/floors/<floor_id>/units/<unit_id> would be a proper solution. Making a GET request to buildings/ should yield you a list of buildings; each building in the array returned should be an instance of a Building model, which has the proper URL set so the user can perform a single POST and update only the building name, instead of sending back the whole structure back to the server. Applying this recursively to the nested resources should give you a clean and concise way to deal with model changes.
Regarding the UI part - I would go ahead and define three directives for buildings, floors and units, and let each one manage an array with the respective resources, also taking care for the UI bindings to the model values.
So how could a Building model look like?
var BuildingResource = $resource('/buildings/:id', { id: '#id' });
Invoking BuildingResource.query() should yield an array of existing buildings. Adding a new building could look like this:
var newBuilding = new BuildingResource();
newBuilding.$save().then(function(building) {
$scope.buildings.push(building);
}, function(errData) {
//Handle error here...
});
It should be easy to extend this pattern for the rest of the resources - note that what the server needs to return for every building is just the name and the id; knowing the id is sufficient to construct an URL (and a $resource object, respectively) to fetch the needed child resources (in this case, floors).

Extjs 4.0.7, Editor Grid - how to get updated cell value?

I need to get(retrieve) updated cell value in controller. (MVC)
So I tried this,
var modified = this.getItemGrid().getStore().getUpdatedRecords();
console.log(modified); // return [] empty array
var modified = this.getItemList_Store().getUpdatedRecords();
console.log(modified); // return [] empty array
but always it returns empty array even I updated some cell value.
anybody know what I am doing wrong?
Here is my part of view code,
Ext.define("App.view.orders.ItemList_view", {
extend: "Ext.grid.Panel",
alias: "widget.itemList_view",
plugins: [
Ext.create('Ext.grid.plugin.CellEditing', {
clicksToEdit: 1
})
],
initComponent: function () {
this.store = "ItemList_store";
this.columns = [
{
xtype: 'checkcolumn', text: "Ship", width: 50, dataIndex: "DR"
},
{ header: "test", width: 100, dataIndex: "test",
editor: {
xtype : 'textfield'
}
}
];
this.selModel = Ext.create("Ext.selection.CheckboxModel");
//this.selModel = Ext.create("Ext.selection.CellModel"); // It does not works either.
this.callParent(arguments);
},
.
.
.
Thank you!
[EDIT]
Thank you very much for your answer! I have some more question about editor grid.
Its much different from Ext3. so I'm very confusing now :(
Q1. How to collect edited record data (once click button)?
the event fired once the grid cell be changed.
but I want collect edited grid record once I click the 'Update edited cell' button, and I want to update all together at the once.
In Ext3, I did like this,
(button) click : function(){
var modified = mygridStore.getModifiedRecords();
var recordsToSend = [];
Ext.each(modified, function(record){
recordsToSend.push(record.data);
});
var grid = Ext.getCmp('grid');
grid.el.mask('Updating','x-mask-loading');
grid.stopEditing();
recordsToSend = Ext.encode(recordsToSend);
Ext.Ajax.request({
url : '/test/test',
params : {
data : recordsToSend
},
success : function(response){
grid.el.unmask();
alert(response.responseText);
mygridStore.commitChanges();
},
failure : function(response){
mygridStore.rejectChanges();
}
});
}
How can I change the code for Extjs4 ?
Q2. I don't know still how to find out for changed checkcolumn.
I tried this, but I does not work for checkcolumn (of cause I tested after change checkbox)
// grid coumn
{
xtype: 'checkcolumn', header: "My Check Column", width: 50, dataIndex: "CH"
}
-
// in control
'myGrid': {
validateedit: function (plugin, edit) {
console.log(edit);
},
checkchange: function (plugin, edit) {
console.log(edit);
console.log(edit.value);
}
}
Q3. When I click the cell to edit, the show some HTML tag in -_-;;
I really appreciate for your help. and thank you very much for your valuable time!
The editors (cell editors or row editors) do not commit their values to the store until you complete the edit - which means pressing ENTER or blurring the active cell editor by clicking elsewhere on the page, or clicking the save button on the row editor form .
If your purpose for reading the updated value in your editor is to perform some kind of validation I would suggest simply listening to the validateedit event in your grid's controller, as described here.
The second argument that this event passes to your handler contains a lot of data about the edit that you can then perform validation with. If the edit doesn't pass your validation you can return false from your handler and the value in the celleditor will revert to it's original value. The validateedit event gets fired from the editor grid itself so you would add an event handler in your controller for it like this:
Ext.define('MyApp.controller.MyController', {
init: function() {
this.control({
'mygridpanel': {
validateedit: function(plugin, edit) {
// silly validation function
if (edit.value != 'A Valid Value') {
return false;
}
},
},
});
},
});
But you should check out the link above to see all the different objects available in that second argument I named edit.
The validateedit event is fired right before the record is committed into the store - after the user has already clicked ENTER or blurred the editor, i.e., while the editor is closing.
If you are trying to get the celleditor's value before it starts to close, for some reason other than validation for example, you could get the active celleditor's value like this:
// myGrid is a reference to your Ext.grid.Panel instance
if (myGrid.editingPlugin.editing) {
var value = myGrid.editingPlugin.getActiveEditor().field.value
console.log('value: ' + value);
}
If there is no active editor then myGrid.editingPlugin.getActiveEditor().field would throw an error, that's why I wrapped a conditional around it.
One other point I should make, for validation in editor grids, I found that it is easiest to just put a validator config in the grid column's editor definition. That will give you all the handy validation CSS while the user is setting the field's value and alert him if there is a problem with the value before he tries to save it.
To get an idea of what I mean, try entering letters in the date column of this example. Mouse over the editor cell and you will get the error message.
EDIT
It seems I misunderstood you original question, I'll break down my answers to your questions above though,
Question 1
Once you have completed an edit (clicked ENTER or ), your call to mygridStore.getModifiedRecords() should be working fine because the record will have been committed to the store. I see that it was not working, I will cover that in a moment.
I should point out that ExtJS4 has a store.sync() method as covered here.
Instead of extracting the modified records from the store, encoding them, manually doing an ajax request to save them to the server and then manually committing them you can call this sync method and it will take care of all of these actions for you.
If you have different URLs to handle the different create, read, update, destroy operations fired off by your store's load and sync methods, you can use the store's proxy api config to map your URLs to these operations as covered here. Or you can set-up your server side controller to be able to differentiate between your store's load request (read operations default to HTTP GET) and it's sync requests (create, update and delete operations default as HTTP POST).
There could be many different ways to go about doing this on the server side, the way I usually do it is to have one SQL stored procedure for GET requests and one for POST requests for any given store. I include the store name as an extra param and then my server side controller runs the appropriate stored procedure based on whether it is a GET or a POST request.
Question 2
Cell editing doesn't support checkcolumn edits. You have to make a different handler to listen to changes on that, something like this:
checkchange: function (column, rowIndex, checked) {
var record = store.getAt(rowIndex),
state = checked ? 'checked' : 'unchecked'
console.log('The record:');
console.log(record)
console.log('Column: ' + column.dataIndex);
console.log('was just ' + state)
}
Your call to mygridStore.getModifiedRecords() should be able to pick up the check changes also however, they get committed to the grid's store right away after being checked. store.sync() would also pick up changes to checkcolumn.
Question 3
I can't completely tell what is causing that problem but it may be something strange going on with your validateedit event, your handler should be returning true or false.
As I said earlier, I misunderstood the reason you originally asked this question. I thought you were trying to do some kind of validation while an edit was in progress. Now I understand that you are trying to get all of the modified records from the store after all the editing is completed in order to save them to the database, I was thrown off because in ExtJS 4 a store is usually saved to the database with the sync method as I mentioned.
In other words, you don't need the validateedit event or checkchange event to get a list of modified records.
The actual problem you are having might be trouble with the store's getter methods (getModifiedRecords, getUpdatedRecords) in some 4.07 versions, take a look at this post and this one.
So with all that said, the best advice I can give you is 1) try out the stores sync method for saving modified data to the database and 2) upgrade to ExtJS 4.1, there were a lot of bugs that were straightened out between 4.07 and 4.1 which you should be able to take advantage of, I cut out about 75% of the overrides I was using to make things work when I switched over to 4.1.
EditedGrid.plugins[0].completeEdit();
This will make the active changes commit and call edit event also.
listeners: {
validateedit: function (editor, e) {
//console.log(editor);
var oldVal = editor.originalValue;
var newVal = editor.value;
}
}

Resources