How to sort records (with code) in a grouped ListGrid? - smartgwt

This is the scenario: I'm working with a listgrid that needs to be grouped, and also needs to have its records ordered within each group. I've already used the ListGrid.sort() and the ListGrid.sort(String, SortDirection) methods but none of them works properly.
This problem doesn't show up when the grid isn't grouped (it makes the sort perfectly); and when the sort (with the listgrid is grouped) is made by clicking the column header, works fine but I need to sort it by code (without user interaction) because the header sort option needs to be disabled (and context menu too).
I'm using SmartGWT 4.0
Here is the class I'm using:
public class Access extends ListGrid {
public Access() {
super();
setWidth("30%");
setHeight100();
// setShowHeaderContextMenu(false);
setCanResizeFields(false);
// setCanSort(false);
setAutoFitWidthApproach(AutoFitWidthApproach.BOTH);
setWrapCells(true);
setFixedRecordHeights(false);
setShowRecordComponents(true);
setShowRecordComponentsByCell(true);
ListGridField id = new ListGridField("id", "ID");
ListGridField user = new ListGridField("user", "User");
ListGridField access = new ListGridField("access", "Access");
id.setHidden(true);
user.setWidth("60%");
access.setWidth("40%");
access.setType(ListGridFieldType.BOOLEAN);
access.setCanEdit(true);
setFields(id, user, access);
groupBy("access");
access.setGroupTitleRenderer(new GroupTitleRenderer() {
public String getGroupTitle(Object groupValue, GroupNode groupNode, ListGridField field, String fieldName,
ListGrid grid) {
return (String) groupValue + " - " + groupNode.getGroupMembers().length;
}
});
getField("access").setGroupValueFunction(new GroupValueFunction() {
public Object getGroupValue(Object value, ListGridRecord record, ListGridField field, String fieldName,
ListGrid grid) {
Boolean access = (Boolean) value;
if (access)
return "With access";
else
return "Without access";
}
});
ListGridRecord lgr1 = new ListGridRecord();
lgr1.setAttribute("id", 1);
lgr1.setAttribute("user", "ewgzx");
lgr1.setAttribute("access", true);
ListGridRecord lgr2 = new ListGridRecord();
lgr2.setAttribute("id", 2);
lgr2.setAttribute("user", "Bgfths");
lgr2.setAttribute("access", false);
ListGridRecord lgr3 = new ListGridRecord();
lgr3.setAttribute("id", 3);
lgr3.setAttribute("user", "utcvs");
lgr3.setAttribute("access", true);
ListGridRecord lgr4 = new ListGridRecord();
lgr4.setAttribute("id", 4);
lgr4.setAttribute("user", "gfdjxc");
lgr4.setAttribute("access", false);
ListGridRecord lgr5 = new ListGridRecord();
lgr5.setAttribute("id", 5);
lgr5.setAttribute("user", "763");
lgr5.setAttribute("access", true);
ListGridRecord lgr6 = new ListGridRecord();
lgr6.setAttribute("id", 6);
lgr6.setAttribute("user", "2");
lgr6.setAttribute("access", false);
ListGridRecord lgr7 = new ListGridRecord();
lgr7.setAttribute("id", 7);
lgr7.setAttribute("user", "35");
lgr7.setAttribute("access", false);
ListGridRecord lgr8 = new ListGridRecord();
lgr8.setAttribute("id", 8);
lgr8.setAttribute("user", "123");
lgr8.setAttribute("access", true);
ListGridRecord lgr9 = new ListGridRecord();
lgr9.setAttribute("id", 9);
lgr9.setAttribute("user", "2342");
lgr9.setAttribute("access", true);
ListGridRecord lgr10 = new ListGridRecord();
lgr10.setAttribute("id", 10);
lgr10.setAttribute("user", "aqwc");
lgr10.setAttribute("access", false);
setRecords(new ListGridRecord[] { lgr1, lgr2, lgr3, lgr4, lgr5, lgr6, lgr7, lgr8, lgr9, lgr10 });
sort("user", SortDirection.ASCENDING);
}
}

I have been having a similar issue. Disclaimer: if the "grouping data" message is not appearing when you group then the following solution may not help.
In my case the sorting of a grouped column was screwed because of the "grouping data" pop up.
Let me clarify.
The "grouping data" pop up appears when trying to group a ListGrid that is displaying more than 50 records.
It appears because the ListGrid, internally, is doing the grouping operation asynchronously to avoid the "script running slowly" message from the browser.
What I did was to set the grouping async threshold to a higher value. The risk of doing this is getting the "script running slowly" browser message, even though this is likely to happen only with IE8/9.
In the end , in the grid constructor, just add (I used 500 as a threshold):
setInitialSort(new SortSpecifier[] {new SortSpecifier("user", SortDirection.ASCENDING)}));
setGroupByField("access");
setGroupByAsyncThreshold(500);
Also set the initial sort and the grouped column as shown above.
PROGRAMMATICALLY, FIRST SORT, THEN GROUP.
Hope this helps.

This is due to sort() being called before rendering the grid, and setRecords() complicates things further.
Initial rendering of the grid happens along with its parents when rootCanvas.draw() is called (in onModuleLoad or similar).
As setRecords() can be used to change data set in the grid anytime, it tries to redraw the grid regardless of whether its initial stage or not.
If in the real scenario, sort is triggered after UI initialization, it should work as given in following code sample.
Remove the sort() call at the end of the constructor.
final Access access = new Access();
Button button = new Button("Sort");
button.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
// toggle sort direction, using two different ways to do it
SortSpecifier sortSpecifier = access.getSortSpecifier("user");
if (sortSpecifier == null || SortDirection.DESCENDING.equals(sortSpecifier.getSortDirection())) {
access.sort("user", SortDirection.ASCENDING);
} else {
access.setSort(new SortSpecifier[]{
new SortSpecifier("user", SortDirection.DESCENDING)
});
}
}
});
Check http://www.smartclient.com/smartgwt/showcase/#grid_multilevel_sort to see how to use listGrid.setInitialSort().
Having setRecords() in the constructor could lead to other initialization issues as well.
Update
To have the grid grouped by and sorted on load, set an initial sort and a group by field as indicated below.
// along with other configuration methods, can not use after grid is drawn
SortSpecifier sortSpecifier = new SortSpecifier("user", SortDirection.ASCENDING);
setInitialSort(new SortSpecifier[]{sortSpecifier});
// use following instead of groupBy(), which is used to group the grid programmatically
// groupBy() causes a redraw
setGroupByField("access");
An overloaded ListGrid.setGroupByField(String... field) method is available to group by multiple fields.

Related

Adding new line to top of Grid with data does not display prefilled values in non-editable columns (vaadin7)

Let's say I load data into a Grid. That works perfectly, everything is displayed. I can see it just fine, even after I call editItem(objectId); to edit data for any given line in the Grid.
Then let's say I have a button that adds a new line with mostly empty elements. In other words, the corresponding bean is mostly empty except for some default values. For some reason this behaves weird when I call editItem(objectId);. Here are the situations and their results:
If column is editable (column.setEditable(true);), my default data displays just fine
If column is not editable (column.setEditable(false);), my default data does NOT display. It is definitely in the bean, just not displayed. I see it once I click "Save" or "Cancel" in the edit form.
If I just show the line, do NOT enter the edit form (don't call editItem(objectId);), it display the default data just fine. I mention this just to point out what happens in the "display only" situation.
I even tried making the editField read only, and even that hid the data. So what is happening?
Example of data being displayed (see circled red):
Figure 1: Non-Empty data, but editable column
Example of data NOT being displayed (see circled red):
Figure 2: Empty data inside edit form
Figure 3: Non-Empty data after clicking save.
Note that it does not matter if the column is a ComboBox or just a regular text element, if I make it non-editable, it will not show the value I put in that column on this new line until after I click Save or Cancel.
Here is how I load the list of beans initially, where gridContainer is defined as BeanItemContainer<T> gridContainer:
public void updateList( List<T> dataList, T defaultData ) {
updateList( dataList, defaultData, new GridLoader<T>() {
#Override
public void loadGrid(List<T> dataList) {
gridContainer.removeAllItems();
if( dataList instanceof List && !dataList.isEmpty() )
gridContainer.addAll(dataList);
}
});
}
This works fine for non-editable columns, all data being displayed as expected. My pictures sort of hide it, but it is displaying just fine, and works in edit mode just fine as well.
And here is how I add a new line:
private void addRouting() {
Routing emptyData = Routing.create();
emptyData.setKey(null);
if( facilityId instanceof String && !facilityId.trim().isEmpty() )
emptyData.setFacilityId(facilityId.trim());
if(wmsItem instanceof String && !wmsItem.trim().isEmpty())
{
emptyData.setWmsItem(wmsItem);
// gridComponent.hideColumn("wmsItem", true);
// gridComponent.hideColumn("wmsItemDisplay", false);
}
else
{
// gridComponent.hideColumn("wmsItem", false);
// gridComponent.hideColumn("wmsItemDisplay", true);
}
if(workCenter instanceof String && !workCenter.trim().isEmpty())
{
emptyData.setWorkCenter(workCenter);
// gridComponent.hideColumn("workCenter", true);
// gridComponent.hideColumn("workCenterDisplay", false);
}
else
{
// gridComponent.hideColumn("workCenter", false);
// gridComponent.hideColumn("workCenterDisplay", true);
}
gridComponent.addItemAt(0, emptyData);
gridComponent.select(emptyData);
if(gridComponent.isEditorEnabled())
gridComponent.editItem(emptyData);
}
And in GridComponent, we have addItemAt defined as follows (BTW, GridComponent just wraps a layout with a toolbar at top and a Grid for the data, and so exposes various methods I need from the toolbar and Grid):
public BeanItem<T> addItemAt(int index, T bean) throws IllegalStateException {
BeanItemContainer<T> gridContainer = getGridContainer();
if(gridContainer instanceof BeanItemContainer)
{
/* Clear filter first because adding an item will break this.
* TODO: Maybe restore prior filter with "saveLastFilter();" and then "reapplyLastFilter();" after "add"
*/
saveLastFilter();
clearFilter();
BeanItem<T> newItem = gridContainer.addItemAt(index, bean);
reapplyLastFilter();
return newItem;
}
else
throw new IllegalStateException("Missing bean grid container");
}

Update autoComplete JavaFx?

I'm currently working on a JavaFX project.I'm using Autcomplete TextField of ControlFx .Each time i add new rows in database table, it should to update Autocomplete ,i did this but my problem is showing double Context-Menu ,we can say double autocompletes because i call method that create autocomplete each adding of new elements in table.
When i click a tab editBill i call this method :
public void showEditBill() {
if (!BillPane.getTabs().contains(EditBillTab)) {
BillPane.getTabs().add(EditBillTab);
}
SingleSelectionModel<Tab> selectionModel = BillPane.getSelectionModel();
selectionModel.select(EditBillTab);
/*it should remove the old autocomplete from textfield*/
pushBills(); //Call for cheking new items
}
pushBills method () :
public void pushBills() {
ArrayList list = new ArrayList<>();
bills = new BillHeaderDao().FindAll();
for (int i = 0; i < bills.size(); i++) {
list.add(bills.get(i).getIdClient());
}
//How can i remove the old bind before bind again
autoCompletionBinding = TextFields.bindAutoCompletion(SearchBill, SuggestionProvider.create(list));
}
How i can remove the old autocomplete and bind new automplete?
Just in any case if you need to keep instance of AutoCompletionTextFieldBinding object, thus avoiding use of:
autoCompleteBinding = TextFields.bindingAutoCompletion(TextField,List);
, which will change the instance, we could go a little bit deeper and use this:
// let's suppose initially we have this possible values:
Set<String> autoCompletions = new HashSet<>(Arrays.asList("A", "B", "C"));
SuggestionProvider<String> provider = SuggestionProvider.create(autoCompletions);
new AutoCompletionTextFieldBinding<>(textField, provider);
// and after some times, possible autoCompletions values has changed and now we have:
Set<String> filteredAutoCompletions = new HashSet<>(Arrays.asList("A", "B"));
provider.clearSuggestions();
provider.addPossibleSuggestions(filteredAutoCompletions);
So, through SuggestionProvider, we have "updated" auto completion values.
To avoid doubling of suggestions menu, don't use again (for the 2nd time):
TextFields.bindAutoCompletion(..)
In order to provide updates to the auto-complete suggestion list, retain a reference to the SuggestionProvider and update the suggestion provider instead:
TextField textField = new TextField();
SuggestionProvider suggestionProvider = SuggestionProvider.create(new ArrayList());
new AutoCompletionTextFieldBinding<>(textField, suggestionProvider);
When you want to update the suggestion list:
List<String> newSuggestions = new ArrayList();
//(add entries to list)
suggestionProvider.clearSuggestions();
suggestionProvider.addPossibleSuggestions(newSuggestions);
This will do the trick:
Instead of: TextFields.bindAutoCompletion(textField, list);
, try this:
List<String> strings = new ArrayList<>();
Then create binding between your textField with the list through:
new AutoCompletionTextFieldBinding<>(textField, SuggestionProvider.create(strings));
So any changes, including removing, from the list, will be reflected in the autoCompletion of the textField;
And you will have dynamic filtering of suggestions, showed in pop-up, when user enter some text in textField;
I had the same problem some time ago I try to do as #MaxKing mentions, but it didnt work. I managed to give it a soluciĆ³n even though I don't think it's the right way.
// Dispose the old binding and recreate a new binding
autoCompleteBinding.dispose();
autoCompleteBinding = TextFields.bindingAutoCompletion(TextField,List);
try this:
public void pushBills() {
ArrayList list = new ArrayList<>();
bills = new BillHeaderDao().FindAll();
for (int i = 0; i < bills.size(); i++) {
list.add(bills.get(i).getIdClient());
}
autoCompletionBinding.dispose();
autoCompletionBinding = TextFields.bindAutoCompletion(SearchBill, SuggestionProvider.create(list));
}

Vaadin Grid Row Index

In a vaadin table if we do
table.setRowHeaderMode(RowHeaderMode.INDEX);
we get a column with the row index.
Is it possible to to the same with a vaadin grid?
So far I haven't seen such an option, but you should be able to fake it with a generated column. Please see below a naive implementation which should get you started (improvements and suggestions are more than welcome):
// our grid with a bean item container
Grid grid = new Grid();
BeanItemContainer<Person> container = new BeanItemContainer<>(Person.class);
// wrap the bean item container so we can generated a fake header column
GeneratedPropertyContainer wrappingContainer = new GeneratedPropertyContainer(container);
wrappingContainer.addGeneratedProperty("rowHeader", new PropertyValueGenerator<Long>() {
private long index = 0;
#Override
public Long getValue(Item item, Object itemId, Object propertyId) {
return index++;
}
#Override
public Class<Long> getType() {
return Long.class;
}
});
// assign the data source to the grid and set desired column order
grid.setContainerDataSource(wrappingContainer);
grid.setColumnOrder("rowHeader", "name", "surname");
// tweak it a bit - definitely needs more tweaking
grid.getColumn("rowHeader").setHeaderCaption("").setHidable(false).setEditable(false).setResizable(false).setWidth(30);
// freeze the fake header column to prevent it from scrolling horizontally
grid.setFrozenColumnCount(1);
// add dummy data
layout.addComponent(grid);
for (int i = 0; i < 20 ; i++) {
container.addBean(new Person("person " + i, "surname " + i));
}
This will generate something similar to the image below:
There is a Grid Renderer that can be used to do this now. It is in the grid renderers add-on https://vaadin.com/directory/component/grid-renderers-collection-for-vaadin7. It is compatible with Vaadin 8 as well.
Here is how it could be used (there are a few different options for how to render the index).
grid.addColumn(value -> "", new RowIndexRenderer()).setCaption("Row index");
Worth to mention that I use the following with Vaadin 18 flow and works perfectly.
grid.addColumn(TemplateRenderer.of("[[index]]")).setHeader("#");
Ok, it took me more than a while to figure this out. I don't know why you need this, but if your purpose is to find which grid row was clicked, then you can get the index from the datasource of your control via the itemClick event of your listener.
In my case, my datasource is an SQLContainer, and I already had it available (see ds var) so I did it this way:
grid.addListener(new ItemClickEvent.ItemClickListener() {
#Override
public void itemClick(ItemClickEvent event) {
Object itemId = event.getItemId();
int indexOfRow = ds.indexOfId(itemId);
}
});
You usually add a datasource to your control when you initialize it, via constructor or by setting the property. If you got you Grid from somewhere with an already-attached datasource, you can always get it with something like this:
SQLContainer ds = (SQLContainer)gred.getContainerDataSource();
I use this trick:
int i = 0;
grid.addComponentColumn(object -> {
i++;
return new Label("" + i);
}).setCaption("");

Umbraco Published Event Performance

I have a comments type structure where users are able to post replies to an Article. (One article can have many discussion replies). When a user adds a reply, I want the parent articles last updated date to also change so that the article is placed at the top of the list when viewed from the frontend indicating that it has had recent activity. To achieve this, the comment is added through a custom controller and then I have used the ContentService Published event to update the parent though am finding my event to be a bit of a bottle neck and taking up to six seconds to run
public void OnApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
ContentService.Published += ContentServicePublished;
}
private void ContentServicePublished(IPublishingStrategy sender, PublishEventArgs<IContent> e)
{
foreach (var node in e.PublishedEntities)
{
//Handle updating the parent nodes last edited date to address ordering
if (node.ContentType.Alias == "DiscussionReply")
{
var contentService = new Umbraco.Core.Services.ContentService();
var parentNode = contentService.GetById(node.ParentId);
int intSiblings = parentNode.Children().Count() + 1;
if(parentNode.HasProperty("siblings"))
{
parentNode.SetValue("siblings", intSiblings);
contentService.SaveAndPublishWithStatus(parentNode, 0, false);
}
}
}
}
Is there anything obvious with this code that may be causing the performance issue?
Many thanks,
You should be using the Services Singleton for accessing the various services including ContentService.
One way to do so is to access the Services on ApplicationContext.Current like so:
var contentService = ApplicationContext.Current.Services.ContentService;
However, your bottleneck is going to be in retrieving the parent node and it's properties which requires multiple calls to the database. On top of that, you're retrieving the parent's children here:
int intSiblings = parentNode.Children().Count() + 1;
The better solution is to use the PublishedContent cache which doesn't hit the database at all and provides significantly superior performance.
If you're using a SurfaceController use it's Umbraco property (and you also have access to Services as well):
// After you've published the comment node:
var commentNode = Umbraco.TypedContent(commentNodeId);
// We already know this is a DiscussionReply node, no need to check.
int intSiblings = commentNode.Parent.Children.Count() + 1;
if (commentNode.Parent.HasProperty("siblings"))
{
// It's only now that we really need to grab the parent node from the ContentService so we can update it.
var parentNode = Services.ContentService.GetById(commentNode.ParentId);
parentNode.SetValue("siblings", intSiblings);
contentService.SaveAndPublishWithStatus(parentNode, 0, false);
}
If you're implementing a WebApi based on UmbracoApiController then the same Umbraco and Services properties are available to you there as well.
I'm using Umbraco 7.3.4 and here's my solution:
// Create a list of objects to be created or updated.
var newContentList = new List<MyCustomModel>() {
new MyCustomModel {Id: 1, Name: "Document 1", Attribute1: ...},
new MyCustomModel {Id: 2, Name: "Document 2", Attribute1: ...},
new MyCustomModel {Id: 3, Name: "Document 3", Attribute1: ...}
};
// Get old content from cache
var oldContentAsIPublishedContentList = (new UmbracoHelper(UmbracoContext.Current)).TypedContent(ParentId).Descendants("YourContentType").ToList();
// Get only modified content items
var modifiedItemIds = from x in oldContentAsIPublishedContentList
from y in newContentList
where x.Id == y.Id
&& (x.Name != y.Name || x.Attribute1 != y.Attribute1)
select x.Id;
// Get modified items as an IContent list.
var oldContentAsIContentList = ApplicationContext.Services.ContentService.GetByIds(modifiedItemIds).ToList();
// Create final content list.
var finalContentList= new List<IContent>();
// Update or insert items
foreach(var item in newContentList) {
// For each new content item, find an old IContent by the ID
// If the old IContent is found and the values are modified, add it to the finalContentList
// Otherwise, create a new instance using the API.
IContent content = oldContentAsIContentList.FirstOrDefault(x => x.Id == item.Id) ?? ApplicationContext.Services.ContentService.CreateContent(item.Name, ParentId, "YourContentType");
// Update content
content.Name = item.Name;
content.SetValue("Attribute1", item.Attribute1);
finalContentList.Add(content);
// The following code is required
content.ChangePublishedState(PublishedState.Published);
content.SortOrder = 1;
}
// if the finalContentList has some items, call the Sort method to commit and publish the changes
ApplicationContext.Services.ContentService.Sort(finalContentList);

ListGrid Duplicates created after adding records onload then dragging the same again

I have a View where I have Drag and Drop working between 2 ListGrid-s, and after dragging a few records I then save them to a POJO type object upon clicking a button "Save".
When I'm accessing again that view it calls a method loadGrid that pulls those values from the POJO and adds them back into the ListGrid that they were dragged to earlier, so they can see what they already have added previously, however when I drag and drop again it lets me add the same primary keys creating duplicates records in the ListGrid.
How can I make it so that it sees these records as the same? The primary key is the same, the types are the same, not sure what it could be...
I'm using transferSelectedData to add the new privileges to the assigned list grid and setPreventDuplicates(true).
ListGrid avPrivGrid = null;
ListGrid assPriv = null;
TransferImgButton but = null;
avPrivGrid = new ListGrid();
PrivilegesDataSource privDataSource = new PrivilegesDataSource();
avPrivGrid.setDataSource(privDataSource);
avPrivGrid.setAutoFetchData(false);
ListGridField propUsername = new ListGridField("privName", "Available Priv");
propUsername.setType(ListGridFieldType.TEXT);
avPrivGrid.setFields(propUsername);
assPriv = new ListGrid();
assPriv.setCanAcceptDroppedRecords(true);
assPriv.setCanEdit(false);
assPriv.setAutoFetchData(false);
assPriv.setPreventDuplicates(true);
assPriv.setDuplicateDragMessage("Can not add duplicates!");
assPriv.setCanSelectAll(false);
assPriv.setAlternateRecordStyles(true);
assPriv.setLeaveScrollbarGap(true);
assPriv.setMinHeight(100);
ListGridField propUserN = new ListGridField("privName", "Assigned Priv");
propUserN.setWidth("30%");
propUserN.setType(ListGridFieldType.TEXT);
ListGridField propId = new ListGridField("privId");
propId.setWidth("30%");
propId.setType(ListGridFieldType.TEXT);
propId.setHidden(true);
assPriv.setFields(propId, propUserN);
but = new TransferImgButton(TransferImgButton.RIGHT);
but.addClickHandler(new ClickHandler()
{
public void onClick(ClickEvent event)
{
//Duplicate checking will happen automagially!
assPriv.transferSelectedData(avPrivGrid);
}
});
avPrivGrid.fetchData();
Check if the ID field set as the primary key in the Datasource.
[IdField].setPrimaryKey(true);
I have the same problem and I have [IdField].setPrimaryKey(true);
Up to here, my conclusion is that whenever there is a databound list of records, the setDragDataAction(DragDataAction.MOVE) is not working. If it's not databound, it works.

Resources