Best way to intercept click event on table item - cuba-platform

I'm using two tables, the first one contains "Teams", the second one "Team members" and is populating based on the first table selection. I'm also showing various stats depending on the selection, be it a team or a specific member. If no member is selected, team stats are showed, otherwise member stats are showed.
I'm using ItemChangeListeners on the tables to redraw the stats, but this prevents me to click on an already selected team to "deselect" a selected member from that team, since no event is triggered in that circumstance. As a solution I'm also using a ClickListener on the Team table, but it seems to work only if I click on the word (instead of working on the whole cell).
teamsTable.setClickListener("name", new Table.CellClickListener() {
#Override
public void onClick(Entity item, String columnId) {
if (teamsDs.getItem() == item) {
teamsDs.setItem(null);
teamsDs.setItem((Team) item);
} else {
teamsDs.setItem((Team) item);
teamsTable.setSelected((Team) item);
}
}
});
Is there a better way to catch a click on a table cell? Or is there a better way to approach the problem altogether?

Since CUBA Table is a wrapper of Vaadin table you can use ItemClickListener from Vaadin with CUBA table:
public class DemoScreen extends AbstractWindow {
#Inject
private Table<User> usersTable;
#Override
public void init(Map<String, Object> params) {
super.init(params);
com.vaadin.ui.Table vTable = usersTable.unwrap(com.vaadin.ui.Table.class);
vTable.addItemClickListener((ItemClickEvent.ItemClickListener) event ->
showNotification("Item " + event.getItemId())
);
}
}
It will be fired each time you click on a table cell.

Related

How to create a simple HTML table in Vaadin Flow 14

I would like to create a simple HTML table in Vaadin flow, but the component is not present anymore (used to be available as com.vaadin.ui.Table). The table is meant to show the detailed properties (key-value pairs) of an item selected in a Grid.
What Vaadin Flow component can I use to implement this? And why was the table removed in Vaadin Flow in the first place?
Table was actually removed already in Vaadin8. https://vaadin.com/blog/-/blogs/mission-rip-table-migrate-to-grid-intro
For implementing a table in Flow there are a couple choices.
One is to use the Element API and one is to create Components for table.
For the element API version it could be something like:
Element table = new Element("table");
For(item : item rows to add) {
Element tr = new Element("tr");
table.appendChild(tr);
For(int i = 0; i < dataColumns; i++) {
Element td = new Element("td");
// could perhaps append a span with text context.
td.setText(item text for column i);
tr.appendChild(td);
}
}
For the Component approach the basic case would then perhaps be to implement the 3 elements as something like:
#Tag("table")
public class Table extends Component implements HasComponents {
public Row addRow() {
Row row = new Row();
add(row);
return row;
}
public Row getRow(int row) {
final Optional<Component> rowOptional = getElement().getChild(row)
.getComponent();
if(rowOptional.isPresent())
return (Row) rowOptional.get();
return null;
}
}
#Tag("tr")
public class Row extends Component {
public void add(Cell cell) {
getElement().appendChild(cell.getElement());
}
public int getRow() {
return getElement().getParent().indexOfChild(getElement());
}
}
#Tag("td")
public class Cell extends Component {
public int getCol() {
return getElement().getParent().indexOfChild(getElement());
}
public int getRow() {
return ((Row) getParent().get()).getRow();
}
public void setText(String text) {
getElement().setText(text);
}
}
There can be multiple use cases for Table component. There are couple of alternative's in Vaadin's Directory of community components.
Table showing list of data (similar to Grid, but more light weight approach)
https://vaadin.com/directory/component/beantable
Table as layout component, which supports row-span, col-span etc. and you populate each cell individually.
https://vaadin.com/directory/component/html-table
As these usecases are quite different , they are better catered by different Java API, although the HTML DOM structure they produce is very similar. Neither of these add-ons attempt to reproduce API of the Vaadin 7 Table component.
There is also a recipe in Cookbook, how to generate Table in TemplateRenderer of the Grid details row.
https://cookbook.vaadin.com/grid-details-table

Create new records when searching for reference object

In my current project I would like to be able to create new objects when searching for a reference object. This happens in several places of the application.
For example, let's assume we have a City Entity and a Country Entity. The City entity has a mandatory reference to the Country entity.
In my use case, I would like to create a new City. When I do this, I will have to assign a Country to the new City. When I click on the lookup icon, I get the selection dialog with all existent countries. But if I don't have the Country I want, I have to abort the operation, get back to the countries list and create the new one I'd like to assign to my new city.
Would it be possible to create that new Country from the selection dialog with all countries?
If it is possible, is the country being added to the list right after it has been created?
Would it be possible to one define a range for the countries list? For example, showing only countries in Europe, if the user is in Europe.
I could imagine, that this would be a lot to ask from the framework. But I am just giving a shot and perhaps also giving a new feature idea, which would be nice to have.
Customization of the LOV dialog :
You can easily customize the LOV dialog by creating your own class of the LOV action that is installed next to the reference fields.
Adding a new action in the dialog (the create action) :
public class LovActionWithCreate<E, F, G> extends LovAction<E, F, G> {
private IDisplayableAction createAction;
#Override
protected void feedContextWithDialog(IReferencePropertyDescriptor<IComponent> erqDescriptor,
IQueryComponent queryComponent, IView<E> lovView, IActionHandler actionHandler,
Map<String, Object> context) {
super.feedContextWithDialog(erqDescriptor, queryComponent, lovView, actionHandler, context);
List<IDisplayableAction> defaultLovDialogActions = (List<IDisplayableAction>) context.get(
ModalDialogAction.DIALOG_ACTIONS);
defaultLovDialogActions.add(1, getCreateAction());
}
/**
* Gets create action.
*
* #return the create action
*/
protected IDisplayableAction getCreateAction() {
return createAction;
}
/**
* Sets create action.
*
* #param createAction
* the create action
*/
public void setCreateAction(IDisplayableAction createAction) {
this.createAction = createAction;
}
}
The key point is to override the feedContextWithDialog method in order to install the new action into the dialog.
Next step is to install your new LOV action. You can either do it globally for whole application or per reference view :
replacing the LOV action globally is just a matter of declaring an action named 'lovAction' into your application frontend.groovy, i.e. :
action('lovAction', parent: 'lovActionBase', class:'test.LovActionWithCreate',
custom: [createAction_ref:'theCreateAction']
)
replacing the LOV action on a certain reference field in a form can be done by using the referencePropertyView (in a form or in a table) and its 'lovAction' property, e.g. :
action('lovActionWithCreate', parent: 'lovActionBase', class:'test.LovActionWithCreate',
custom: [createAction_ref:'theCreateAction']
)
form('ACertainForm'){
fields {
...
referencePropertyView name:'country', lovAction:'lovActionWithCreate'
...
}
}
Creating an entity in the LOV dialog :
In the next step, we create the action that will be responsible for opening an extra dialog in order to create the new entity, persist it and, if successful, add it to the LOV result view. This is a little more complicated but not that much.
First of all, we have to open a new dialog.
For doing this, we will inherit the built-in EditComponentAction. The goal of this action is to edit a model in a modal dialog. The only difficulty here is that our model is only known at runtime. No problem though as we will use the dynamic nature of Jspresso.
public class CreateEntityFromLOVAction<E, F, G> extends EditComponentAction<E,F,G> {
#Override
protected Object getComponentToEdit(Map<String, Object> context) {
IEntityFactory entityFactory = getBackendController(context).getEntityFactory();
IQueryComponent lovQueryComponent = (IQueryComponent) context.get(IQueryComponent.QUERY_COMPONENT);
Class<IEntity> entityToCreateContract = lovQueryComponent.getQueryContract();
IEntity entityInstance = entityFactory.createEntityInstance(entityToCreateContract);
setActionParameter(Arrays.asList(entityInstance), context);
return entityInstance;
}
#Override
protected IViewDescriptor getViewDescriptor(Map<String, Object> context) {
IEntityFactory entityFactory = getBackendController(context).getEntityFactory();
IQueryComponent lovQueryComponent = (IQueryComponent) context.get(IQueryComponent.QUERY_COMPONENT);
Class<IEntity> entityToCreateContract = lovQueryComponent.getQueryContract();
IComponentDescriptor<?> entityToCreateDescriptor = entityFactory.getComponentDescriptor(entityToCreateContract);
BasicComponentViewDescriptor formViewDescriptor = new BasicComponentViewDescriptor();
formViewDescriptor.setModelDescriptor(entityToCreateDescriptor);
return formViewDescriptor;
}
}
If you look at the code above, our new action takes care of the following :
Get the type of entity to create from the context. For this, we are just exploring the query component which is the model of the LOV dialog.
Create the entity instance and set it as action parameter in the context for the chain to continue working on it (save, close dialog).
Create a form to display in the creation dialog.
Points 1 and 2 are handled by the getComponentToEdit method and point 3 by the getViewDescriptor method.
Next, when the user clicks Ok, we have to save the entity, add it to the LOV result list and close the creation dialog.
For this, we will create a new action and chain it to the saveAction and closeDialogAction built-in actions.
public class CreateEntityFromLOVPersistAction<E, F, G> extends FrontendAction<E,F,G> {
#Override
public boolean execute(IActionHandler actionHandler, Map<String, Object> context) {
if (super.execute(actionHandler, context)) {
IQueryComponent lovQueryComponent = (IQueryComponent) context.get(IQueryComponent.QUERY_COMPONENT);
List<IEntity> createdEntityInstance = getActionParameter(context);
lovQueryComponent.setQueriedComponents(createdEntityInstance);
return true;
}
return false;
}
}
And the final wiring in SJS frontend.groovy:
action('createEntityFromLovOkAction', parent: 'okDialogFrontAction',
class:'test.CreateEntityFromLOVPersistAction',
wrapped: 'saveBackAction', next: 'closeDialogAction')
action('createEntityFromLovAction', parent: 'editComponentAction',
class: 'test.CreateEntityFromLOVAction',
name:'add.name', custom: [
okAction_ref: 'createEntityFromLovOkAction'
]
)
action('lovAction', parent: 'lovActionBase',
class:'test.LovActionWithCreate',
custom: [createAction_ref:'createEntityFromLovAction']
)
A long answer for less than 100 lines of code, but now you have a fully generic LOV action where the user can create any missing master data without leaving his current screen.
Presetting some data in the LOV filter depending on the user context :
For this, we generally use the initialization mapping that allows for setting some restrictions (either static or dynamic) on a reference property when it is queried in a LOV. For instance, let's consider the following use case :
You have 2 entities, Contract and Tariff, that are linked together through a 1-N relationship, i.e. a Contract is linked to 1 Tariff.
Contract and Tariff both have a country property and a Tariff can be assigned to a Contract if and only if they belong to the same country.
Tarrif has a status property and can only be used in a Contract if its status is ACTIVE.
You can simply enforce these rules in the LOV by setting the initialization mapping on the reference property the following way :
Entity('Contract', ...) {
...
reference 'tariff', ref: 'Tariff',
initializationMapping: [
'country': 'country',
'status': 'ACTIVE'
]
...
}
Thinking about it, this kind of behavior might very well find its way to the framework, so please, feel free to ope an enhancement request in the Jspresso GitHub.

How to Create ViewModel with multiple related tables and Save Form

I am trying to figure out the best way to accomplish this given the modern versions. I have am using VS2012 MVC4 EF5 and have built a edmx file from my database. I built a form that will allow submission of vendor information. The main table is Vendor table that contains mainly contact information and there are additional tables that store their multiple category choices (checkbox list) and another that stores their minority info (collection of radio buttons). So my ViewModel is the vendor table and I populate the checkboxes and radio buttons with view bags that query the lookup tables for their values.
So I assume I should either build the categories and minority parts into the ViewModel and somehow wire up the magic so that the database knows how to save the returned values or should I just use viewbags and then somehow on post read those values and loop through them to store them to the database? Either way I am stuck and don't know how to do this.
I have serached numerous examples online but none of them fit this situation. The is not a complex data model but should be rather common real world situation. I am new to MVC so forgive me if I am missing something obvious.
Any guidance is appreciated.
UPDATE: Here is the baseic code to save the ViewModel to the db but how do you save the checkbox list and radio buttons. I think there are two approaches 1) to somehow include them in the ViewModel or 2) perform a separate function to save the form checkbox and radio button values.
[HttpPost]
public ActionResult Form(VendorProfile newProfile)
{
if (ModelState.IsValid)
{
newProfile.ProfileID = Guid.NewGuid();
newProfile.DateCreated = DateTime.Now;
_db.VendorProfiles.Add(newProfile);
_db.SaveChanges();
return RedirectToAction("ThankYou", "Home");
}
else
{
PopuplateViewBags();
return View(newProfile);
}
}
Perhaps another way of stating my problem is what if you had to build an form to where people would sign up and select all their favorite flavors of ice cream from a list of 31 flavors. You need to save the person's contact information in the primary table and then save a collection of their flavor choices in another table (one-to-many). I have a ViewModel for the contact form and a list of flavors (checkbox list) displayed from a lookup table. How do you write code to save this form?
SOLUTION: There might be a better way, but wanted to post what I discovered. You can pass in the collection of checkboxes and then send them to another method that handles the db inserts.
[HttpPost]
public ActionResult Form(VendorProfile newProfile, int[] categories)
{
if (ModelState.IsValid)
{
newProfile.ProfileID = Guid.NewGuid();
newProfile.DateCreated = DateTime.Now;
_db.VendorProfiles.Add(newProfile);
_db.SaveChanges();
InsertVendorCategories(newProfile.ProfileID, categories);
return RedirectToAction("ThankYou", "Home");
}
else
{
PopuplateViewBags();
return View(newProfile);
}
}
private void InsertVendorCategories(Guid ProfileID, int[] categories)
{
try
{
var PID = new SqlParameter("#ProfileID", ProfileID);
var CID = new SqlParameter("#CatID", "");
foreach (int c in categories)
{
CID = new SqlParameter("#CatID", c);
_db.Database.ExecuteSqlCommand("Exec InsertVendorCategory #ProfileID, #CatID", PID, CID);
}
}
catch { Exception ex; }
}

Apache Wicket - Implementing AbstractToolbar with DefaultDataTable

I am trying to add an AbstractToolBar to a DefaultDataTable.
The toolbar has a button on click of which the selected rows should get deleted.
My table has a checkbox column, to select the rows.
AbstractToolBar implementation looks like this -
public class GridToolBar extends AbstractToolbar {
/**
*
*/
private static final long serialVersionUID = -2126515338632353253L;
Button btnDelete;
List<Contact> selected;
public GridToolBar(final DataTable<?> table) {
super(table);
// TODO Auto-generated constructor stub
btnDelete = new Button("delete",new Model("Delete"));
btnDelete.setOutputMarkupId(true);
btnDelete.add(new AjaxEventBehavior("onclick") {
private static final long serialVersionUID = 6720512493017210281L;
#Override
protected void onEvent(AjaxRequestTarget target) {
System.out.println(selected);
((UserProvider)table.getDataProvider()).remove(selected);
target.add(table);
}
});
add(btnDelete);
}
public void setSelected(List inList){
selected = inList;
}
}
The toolbar has been added to table as follows -
GridToolBar tb = new GridToolBar(table);
tb.setOutputMarkupId(true);
table.addTopToolbar(tb);
The code works fine, except on click of delete button it adds an additional delete button below the table. On inspecting it with firebug, the ids of both the buttons match exactly. On sorting the table though, the extra button is removed from the view.
Could someone help me how can I avoid creation of extra button on every click?
Why is it being created in the first place?
Any help is appreciated.
Thanks,
Sonam
You add the button directly to the table. This is incorrect, as you cannot have a button in a table. You need a <td> element. You can create one using a WebMarkupContainer. See also the source of for example NoRecordsToolbar

NHibernate save / update event listeners: listening for child object saves

I have an Area object which has many SubArea children:
public class Area
{
...
public virtual IList<SubArea> SubAreas { get; set; }
}
he children are mapped as a uni-directional non-inverse relationship:
public class AreaMapping : ClassMap<Area>
{
public AreaMapping()
{
HasMany(x => x. SubAreas).Not.Inverse().Cascade.AllDeleteOrphan();
}
}
The Area is my aggregate root. When I save an area (e.g. Session.Save(area) ), the area gets saved and the child SubAreas automatically cascaded.
I want to add a save or update event listener to catch whenever my areas and/or subareas are persisted. Say for example I have an area, which has 5 SubAreas. If I hook into SaveEventListeners:
Configuration.EventListeners.SaveEventListeners =
new ISaveOrUpdateEventListener[] { mylistener };
When I save the area, Mylistener is only fired once only for area (SubAreas are ignored). I want the 5 SubAreas to be caught aswell in the event listener. If I hook into SaveOrUpdateEventListeners instead:
Configuration.EventListeners.SaveOrUpdateEventListeners =
new ISaveOrUpdateEventListener[] { mylistener };
When I save the area, Mylistener is not fired at all. Strangely, if I hook into SaveEventListeners and SaveOrUpdateEventListeners:
Configuration.EventListeners.SaveEventListeners =
new ISaveOrUpdateEventListener[] { mylistener };
Configuration.EventListeners.SaveOrUpdateEventListeners =
new ISaveOrUpdateEventListener[] { mylistener };
When I save the area, Mylistener is fired 11 times: once for the area, and twice for each SubArea! (I think because NHIbernate is INSERTing the SubArea and then UPDATING with the area foreign key).
Does anyone know what I'm doing wrong here, and how I can get the listener to fire once for each area and subarea?
Not 100% related to your question but if you map with inverse="true" on your collection you atleast dont get the insert AND update statements.
NH issues INSERT statement in order to know the Id of the object if it cannot known (IDENTITY or SEQUENCE for example).
So if you want to void that you need to use an id generator that do not require roundtript to the DB (such as guid or guid.combo).