I am writing a scout application, and I stumbled upon some problem.
In my standard Outline I have more than one page. In page A I have some editable table with save button. What is in page B is not important for this discussion.
Outline
page A
page B
If page A is selected and I edit some data I would like to be notified if I click on page B that some data are not saved.
So before Outline switch between page A and B I would like to have control to not switch to page B because same data in A are not saved.
I have actually solve this problem with extending pages, but I an looking if there is some standard pre-defined way for this.
Unfortunately, there is no way to prevent the node selection from really happening. As you mentioned, you can listen for activation and deactivation events in your Page by overriding the methods execPageActivatedand execPageDeactivated, respectively. But by using this approach, you cannot take control over node switching.
A bit more of control you get by providing your own implementation of createPageChangeStrategy in your Outline class by injecting a custom DefaultPageChangeStrategy. So you get informed every time a node change happens with a respective pageChange event. As long as your page is invalid, you prevent the page switching from happening and restore the origin tree selection.
Please take a look at the following example:
#Override
IPageChangeStrategy createPageChangeStrategy() {
return new DefaultPageChangeStrategy() {
#Override
public void pageChanged(IOutline outline, IPage deselectedPage, IPage selectedPage) {
if (deselectedPage instanceof APage && !((APage) deselectedPage).isValid()) { // #isValid is your check method for validity.
// Do not propagate the PageChangeEvent and restore the selection of the invalid page.
// Uninstall the PageChangeStrategy to ignore the event of restoring the selection.
final IPageChangeStrategy pageChangeStrategy = this;
setPageChangeStrategy(null);
// Restore the selection and install the PageChangeStrategy anew.
new ClientSyncJob("Restore node selection", ClientSession.get()) {
#Override
protected void runVoid(IProgressMonitor monitor) throws Throwable {
YourOutline.this.selectNode(deselectedPage);
setPageChangeStrategy(pageChangeStrategy);
}
}.schedule();
}
else {
super.pageChanged(outline, deselectedPage, selectedPage);
}
}
};
}
Related
I am working on an editable table in Angular application with ag-grid library. I would like to keep editing cells (in full row edit mode) until I finish with it and then close the editors manually via API. The problem is that the editor is closing on other cell click/focus (on some other line) as described here:
The grid will stop editing when any of the following happen:
Other Cell Focus: If focus in the grid goes to another cell, the editing will stop.
I cannot figure out how to disable this, if it is possible. Installing the onCellMouseDown() hook does not help, because the cellFocused event is fired before cellMouseDown. Therefore, the editing stops before I have a chance to intercept the mousedown event.
Here is my stackblitz little extract with related pieces of code.
The need for such scenario is that I want to validate the entry and not to allow a used to quit the editing if the form is not valid. The only workaround I found so far is that on any click outside of editing cells when the editor closing I reopen it right away in onRowEditingStopped() hook unless the editor has been closed via 'OK' button.
After all, I have managed to provide a custom solution that fits exactly into this problem which I was facing also.
First thing is to disable pointer events to non edited rows when a specific row is currently being edited. On Ag-grid's 'cellEditingStarted' callback I have added the following code:
public cellEditingStarted(event: any): void {
//not all rows are on dom ag-grid takes care of it
const nonSelectedGridRows = document.querySelectorAll('.ag-grid-custom-row:not(.ag-row-selected):not(.ag-row-editing):not(.pointer-events-none)');
forEach(nonSelectedGridRows, row => {
row.classList.add("pointer-events-none");
});
}
Because not all rows exist on dom (Ag-grid creates and destroys while you are scrolling )when a specific cell is being edited, I have also added a rowClassRule which is applied when rows are being created:
this.rowClassRules = {
'pointer-events-none': params => {
if (params.api.getEditingCells().length > 0) {
return true;
}
return false;
}
};
scss:
.pointer-events-none {
pointer-events: none
}
By disabling pointer events, when you click on a non edited cell the cell won't get focus and thus the currently edited cell will stil remain on edit mode. You can provide your own custom validation solution and close the editor manually through API. When you are done, you have to enable pointer events to all grid rows back again:
private enablePointerEvents(): void {
//not all rows are on dom ag-grid takes care of it
const nonSelectedGridRows = document.querySelectorAll('.ag-grid-custom-row.pointer-events-none');
forEach(nonSelectedGridRows, row => {
row.classList.remove("pointer-events-none");
});
}
I implemented the same above approach in Ag-Grid React.
I used getRowStyle callback for adding the css pointerEvents: none on dynemic basis.
It seems to be working for me fine.
Please refer the below code
const getRowStyle = (params) => {
// this is not initialized in read mode
// condition for me ==> currentEditRowIndex.current !== null && params.node.rowIndex !== currentEditRowIndex.current
if (someCondition for Row other than inline edit row) {
return { pointerEvents: "none" };
}
return null;
};
After adding this whenver you start the editing..You will need to call redrawRows so that css changes can be applied.
Hope this will help. Thank You!!
Thought I would share another solution that has been working out okay for me so far.
Using 'pointer-events-none' as suggested in the other answer is flawed because the Enter key can also close the editor.
In my case, I want to prevent the editor from closing when client side validation has failed and the data is invalid. When my conditions are met, I call stopPropagation() on the events to prevent the editor close from happening in the first place. It still has potential problems:
It cancels mousedown, dblclick, keydown, focusout and click for all elements that have a class name starting with ag- so if you happen to use this class prefix for other controls on the page, it could interfere. It also means any controls within the grid (sorting, resizing, etc.) don't work while the condition is met.
Calling stopPropagation() could potentially interfere with your own custom controls. So far I've been okay if I dont use the ag- prefix within the markup from my own custom cell editors and renderers
I hope they can add a proper API function to cancel the row/cell stopEditing function in the future.
["mousedown", "dblclick", "keydown", "focusout", "click"].forEach(function (eventName) {
document.addEventListener(eventName, function (e) {
if ( conditionForCancelingIsMet() ) {
// this appears to cancel some events in agGrid, it works for
// preventing editor closing on clicking other cells for example.
// It would be ideal if this worked for all ag-grid specific events
// and had a proper public API to use!
e["__ag_Grid_Stop_Propagation"] = true;
}
// convert element classList to normal js array so we can use some()
var classArray = [].slice.apply(e.target.classList);
if ( conditionForCancelingIsMet() && classArray.some(c => c.startsWith("ag-")) ) {
// unfortunately some events like pressing the 'enter' key still
// require stopPropagation() and could potentially interfere with unrelated controls
e.stopPropagation();
}
}, true);
});
What am I trying to do?
I have an existing page (generated by system automatically and I don't have any control on it) in which I am injecting GWT code to modify the behaviour of the page after it loads based on certain columns and augment the functionality of the page. For example after adding my GWT code, cells in one of the table columns become clickable and when the user clicks it, additional information is displayed to the user in a pop-up panel. All that is working fine.
What is the issue?
The generic page in which I am injecting my code has paginated table which shows 15 rows at a time. Now, when I load/refresh the page, my GWT code kicks in and sinks events in the specific column which adds functionality (described above) to the cells. However, when the user uses the left and right buttons to navigate the paginated result, the page does not refresh as it is an asynchronous call. The specific column in the new set of 15 rows is now without the sunk events as the GWT code does not know that the page changed.
I am trying to find a way to tell my GWT code that page has changed and it should sink events to the cells of specific column for the new 15 rows but unable to find any method or mechanism to help me capture a DOM/Document change event. I tried doing this but did not help:
new ChangeHandler(){
#Override
public void onChange(ChangeEvent event) {
Window.alert("Something Changed");
}
It is possible I am missing something very obvious. Posting this question to know if there is an easy way to figure out DOM changes in GWT. Have searched for DOM/Document change/mutation/ etc. without luck.
If anyone knows how to detect DOM changes in GWT would really appreciate otherwise would go ahead writing native code using native mutation observers.
You can try something like this:
First get the input elements with:
InputElement goOn = DOM.getElementById("IdOfGoOnButton").cast();
InputElement goBack = DOM.getElementById("IdOfGoBackButton").cast();
Next add a native EventHandler:
Event.addNativePreviewHandler(new Event.NativePreviewHandler() {
#Override
public void onPreviewNativeEvent(Event.NativePreviewEvent event) {
if (event.getTypeInt() == Event.ONCLICK) {
if (event.getNativeEvent()
.getEventTarget() != null) {
Element as = Element.as(event.getNativeEvent()
.getEventTarget());
if (as.getTagName()
.toLowerCase()
.equals("input")) {
InputElement clickedElement = as.cast();
if (clickedElement.getId().equals(goOn.getId()) ||
clickedElement.getId().equals(goBack.getId())) {
// one of the paging button is pressed
}
}
}
}
}
});
Hope that helps.
i had this task: I have Presenter/View Couple (lets call it ItemListPresenter) showing a Celltable. Now i wanted to edit an item by double-clicking it (or pressing a button, whatever). Then a popup dialog should appear (Lets call it PopupWidget), letting me edit my item properties.
i found a solution how to do it, but i am not sure it is the "right" way. Since i am trying to learn the philosophy behind GWT/GWTP, i would appreciate if you could give me hints what i did right and what i did wrong:
In the onbind method of ItemListPresenter, i wire the CellTable with a DoubleClick handler:
getView().getCellTable().setSelectionModel(selectionModel);
getView().getCellTable().addDomHandler(new DoubleClickHandler() {
#Override
public void onDoubleClick(final DoubleClickEvent event) {
DeviceDto selectedDeviceDto = selectionModel.getSelectedObject();
//TODO: Figure out how to best handle editing of DeviceDto
if (selectedDeviceDto != null) {
devicesDialog.setCurrentDeviceDTO(selectedDeviceDto);
addToPopupSlot(devicesDialog);
}
} }, DoubleClickEvent.getType());
What does not feel right is setting the object i want to edit (selectedDeviceDto) in the Dialog Presenter Widget. Is this the "right" way?
My popup presenter which is defined as
public class DeviceEditDialogPresenterWidget extends PresenterWidget<DeviceEditDialogPresenterWidget.MyView> implements
DeviceEditDialogUiHandlers {
is still ugly, since i just set every property into a text box and after editing, i recollect the properties and rebuild the object. This is messy and i guess i should GWT Editors for that. However, when i click the "Save" Button in the dialog, an UiHandler is triggered:
#UiHandler("okButton")
void okButtonClicked(ClickEvent event) {
DeviceDto dev = new DeviceDto(idBox.getText(), deviceIdBox.getText(), typeBox.getText(), firmwareVersionBox.getText(), userBox.getText(), statusBox.getText());
getUiHandlers().updateDevice(dev);
hide();
}
This triggers my DeviceEditDialogPresenterWidget, which itself fires and event:
#Override
public void updateDevice(DeviceDto device) {
eventBus.fireEvent(new DeviceUpdatedEvent(device));
}
This event is caught by a handler in the "mother" presenter with the CellTable, which is wired in the onBind method again:
addRegisteredHandler(DeviceUpdatedEvent.TYPE, new DeviceUpdatedEvent.DeviceUpdatedHandler() {
#Override
public void onDeviceUpdatedEvent(DeviceUpdatedEvent event) {
updateDevice(event.getDevice());
}
});
I would really like to avoid going down a road of messy mistakes, so any hints would be appreciated.
Thanks
Arthur
PresenterWidgets are usually designed to have a setter which is used to set a Model or DTO that the PresenterWidget works on (the same way you did it).
Alternatively you can avoid a PresenterWidget and use an Editor (extends Composite) that you manually add to a PopupPanel or DialogBox in your ListItemEditor.
This way you avoid the complexity of a PresenterWidget. But you have to handle the clicks (i.e. save button) from the ListItemPresenter. I would always try to start small (use a composite) and if you realize that you might need the functionality also in other places, create a PresenterWidget.
Also you don't need the updateDevice method because you pass a reference to your DTO. You only need to fresh the CellTable.
But apart from that your approach looks fine.
I'm currently working with IceFaces 1.8, and have been trying to find an simple way to chain effects on UI Components. For example, I have a "Show Help" link at the top right of the page. When clicked, help text will appear below certain controls for users. I'd like this text to appear by sliding down, then highlighting.
I have a basic isRenderHelp() method on my bean that returns true or false, and use that to render effects using the fire attribute on the <ice:effect> tag, so it looks something like this:
<ice:effect effectType="slidedown" fire="#{myBean.renderHelp}">
<ice:effect effectType="slideup" fire="#{!myBean.renderHelp}">
This works causing the help section to slide in and out as the help link toggles the renderHelp flag in the bean. There is the small exception that renderHelp returns null before the link is clicked for the first time to prevent the slideup animation from firing on the first page render.
Now, I noticed looking through the showcase code for 1.8 that there is an EffectQueue class that extends Effect. This allows me to add mutliple Effects to the queue in my bean, and return the queue from a getEffect method that I can then assign to a panelGroup effect attribute. However, it does not execute the events in the queue, despite having their priorities set. I'm sure I'm not using it properly, and I'm wondering how it should be used.
Normally I'd use jQuery for this type of thing, but the UI uses many partial submits. Our page is displayed via a Liferay portlet, so on any partialSubmit the view is rerendered, undoing any modifications to the DOM by jQuery.
So is there any simple way to chain effects in IceFaces 1.8? Suggestions?
here is how I implemented the effectQueue to appear and fade the text.
private EffectQueue effectQueue;
public Effect getSaveSettingsEffect() {
return effectQueue;
}
public void fireEffect(ActionListener e) {
if(effectQueue == null) {
effectQueue = new EffectQueue("apperAndFade");
Appear appear = new Appear();
appear.setDuration(2);
effectQueue.add(appear);
Fade fade = new Fade();
fade.setDuration(3);
effectQueue.add(fade);
effectQueue.setTransitory(true);
}
effectQueue.setFired(false);
}
facelet:
<ice:commandButton value="fireEffect" action="#{bean.fireEffect}"/>
<ice:outputText value="text" style="display: none;" effect="#{bean.effectQueue}"/>
I have an issue trying to persist entities to the DB using code-first technique. For example of what I am doing you may look at the following MSDN Sample. The app generally works as intended except for one case.
If I have an existing entity and I bind it to a page that has a TextBox to hold the Title field and a AppBar icon to Save (similar to the 'New Task' screenshot in the above link, but with values pre-filled with existing entity with Two-Way binding), the following issue occurs. If I have the TextBox selected and I change the title and hit the save button, it updates the entity in-memory so that the full list now displays the new title. But the new title is not persisted to the DB (it does not auto-detect changes). This is weird, not just because the object in-memory has changed, but also because if I deselect the TextBox and then hit save, it will persist the changes to the DB.
I have seen other questions on SO with some change detection issues, they suggest adding this.Focus() or focusing some other element at the beginning of the save method. This does not help in my case. Unless I tap on screen to deselect the TextBox and hide the keyboard (or press Return key on the keyboard, which I bound to do this.Focus()), it won't detect the object as changed.
How can I address this? What exactly is stopping EF from detecting the object change when the keyboard is still visible?
Not sure if I follow exactly what you described but I think the problem is that the property you have bound your textbox to does not get updated until the TextChanged is fired on the textbox, and this is done first when you leave the Textbox, basically it will lose Focus if you tap somewhere else.
There is a simple workaround for this and it behaviors. By making a small behavior you can force the textbox to update the binding on each keystroke - so everything is updated while you type and keyboard is still there.
Behavior:
/// <summary>
/// Update property on every keystroke in a textbox
/// </summary>
public class UpdateTextSourceTriggerBehavior : Behavior<TextBox>
{
protected override void OnAttached()
{
this.AssociatedObject.TextChanged += OnTextBoxTextChanged;
}
void OnTextBoxTextChanged(object sender, TextChangedEventArgs e)
{
var bindingExpression = AssociatedObject.ReadLocalValue(TextBox.TextProperty) as BindingExpression;
if (bindingExpression != null)
{
bindingExpression.UpdateSource();
}
}
protected override void OnDetaching()
{
this.AssociatedObject.TextChanged -= OnTextBoxTextChanged;
}
}
Now just attach this behavior to your textbox like this:
<TextBox Text="{Binding YourPropertyName, Mode=TwoWay}">
<i:Interaction.Behaviors>
<UpdateTextSourceTriggerBehavior/>
</i:Interaction.Behaviors>
</TextBox>
This will keep the property on your viewmodel updated all the time, so that when you tap on save directly after typing in the textbox it will save the correct value. Hope it helps!
Cheers,
Anders