I've an Async TreePanel that uses an RPC proxy to load data from server. I want to reload a node by using:
this.treeLoader.loadChildren(nodeModel);
Then, I want the loaded tree node to become expanded. I tried to:
treePanel.setExpanded(nodeModel, true, false);
but the first call is asynchronous so the "setExpanded" happens before the nodes get loaded.
A solution would be to use a LoadListener on the treeLoader and expand the node after it's children are loaded but the listener's loaderLoad(..) method can't know directly the reason for the reload: maybe the user expanded a node and this triggered the reload or maybe the user clicked on a menu option to reload the node.
Is there any way to improve this so it's easier to trigger the node expand after the user wants to reload a node?
Thanks.
Try removing the listener in the loaderLoad method, as well as in the loaderLoadException to avoid a leak
I suggest to store the node that was selected to be reloaded (add a onClick listener to the thee). Than in the loaderLoad check if the stored object equals the parent of loaded node:
loader.addLoadListener(new LoadListener() {
#Override
public void loaderLoad(LoadEvent loadEvent) {
ModelData parent = loadEvent.getConfig();
if(parent.equals(storedObject) {
// your code here
Related
I just started learning Flutter, and I'm going through the Udemy course "Flutter & Dart - The Complete Guide". In that course, there is a section about building a shopping app, which uses providers. In one instance of that app, where the user swipes to delete a product from the Cart page (or screen/route) with the help of the Dismissible widget, he uses a function inside the provider class, which takes a product ID, to delete the item from the cart.
Here is the thing that I don't understand. The Dismissible widget is connected to the provider via this code in the onDismissed property (which fires after the swipe):
Provider.of<Cart>(context, listen: false).removeItem(productId);
And it all works just fine like this. But if you remove the listen parameter (hence turning it into it's default state which is true), then the Dismiss animation still takes place, but the removeItem() method doesn't work, and the cart still stays the same.
Why does this happen?
When we use listen: false we are telling to not rebuild the widget after we remove an item, but they know that we are removing an item so we don't need to listen any value here it's just doing the action of removing
I'll refer to the method Provider.of(context, listen: true) as x throughout this answer.
x is expected to be used ONLY for properties of a Widget that is
expected to change; and
can be rebuilt. For example:
SizedBox(
width: Provider.of<MyLogic>(context, listen: true).width,
)
When used this way, x will ONLY be called when the context owner is being built/rebuilt.
To ensure that it is being used properly, x performs a sanity check every time it is called, making sure that the owner of the context you passed is actually being built/rebuilt. When you call x from within your onPressed or whatever method it is you call it from, x sees that the context owner is not in the "build" phase, and throws this error.
There are a few more details to this, but you don't actually need to know more about it (especially you're just beginning) unless you want to contribute to the package, in which case you should read their documentation.
Side note: you can now use context.watch() and context.read() instead of Provider.of().
I am using ag-grid/ag-grid-angular to provide an editable grid of data backed by a database. When a user edits a cell I want to be able to post the update to the backend service and if the request is successful update the grid and if not undo the user's changes and show an error.
I have approached this problem from a couple different angles but have yet to find the solution that meets all my requirements and am also curious about what the best practice would be to implement this kind of functionality.
My first thought was to leverage the cellValueChanged event. With this approach I can see the old and new values and then make a call to my service to update the database. If the request is successful then everything is great and works as expected. However, if the request fails for some reason then I need to be able to undo the user's changes. Since I have access to the old value I can easily do something like event.node.setDataValue(event.column, event.oldValue) to revert the user's changes. However, since I am updating the grid again this actually triggers the cellValueChanged event a second time. I have no way of knowing that this is the result of undoing the user's changes so I unnecessarily make a call to my service again to update the data even though the original request was never successful in updating the data.
I have also tried using a custom cell editor to get in between when the user is finished editing a cell and when the grid is actually updated. However, it appears that there is no way to integrate an async method in any of these classes to be able to wait for a response from the server to decide whether or not to actually apply the user's changes. E.g.
isCancelBeforeStart(): boolean {
this.service.updateData(event.data).subscribe(() => {
return false;
}, error => {
return true;
});
}
does not work because this method is synchronous and I need to be able to wait for a response from my service before deciding whether to cancel the edit or not.
Is there something I am missing or not taking in to account? Or another way to approach this problem to get my intended functionality? I realize this could be handled much easier with dedicated edit/save buttons but I am ideally looking for an interactive grid that is saving the changes to the backend as the user is making changes and providing feedback in cases where something went wrong.
Any help/feedback is greatly appreciated!
I understand what you are trying to do, and I think that the best approach is going to be to use a "valueSetter" function on each of your editable columns.
With a valueSetter, the grid's value will not be directly updated - you will have to update your bound data to have it reflected in the grid.
When the valueSetter is called by the grid at the end of the edit, you'll probably want to record the original value somehow, update your bound data (so that the grid will reflect the change), and then kick off the back-end save, and return immediately from the valueSetter function.
(It's important to return immediately from the valueSetter function to keep the grid responsive. Since the valueSetter call from the grid is synchronous, if you try to wait for the server response, you're going to lock up the grid while you're waiting.)
Then, if the back-end update succeeds, there's nothing to do, and if it fails, you can update your bound data to reflect the original value.
With this method, you won't have the problem of listening for the cellValueChanged event.
The one issue that you might have to deal with is what to do if the user changes the cell value, and then changes it again before the first back-end save returns.
onCellValueChanged: (event) => {
if (event.oldValue === event.newValue) {
return;
}
try {
// apiUpdate(event.data)
}
catch {
event.node.data[event.colDef.Field] = event.oldValue;
event.node.setDataValue(event.column, event.oldValue);
}
}
By changing the value back on node.data first, when setDataValue() triggers the change event again, oldValue and newValue are actually the same now and the function returns, avoiding the rather slow infinite loop.
I think it's because you change the data behind the scenes directly without agGrid noticing with node.data = , then make a change that agGrid recognises and rerenders the cell by calling setDataValue. Thereby tricking agGrid into behaving.
I would suggest a slightly better approach than StangerString, but to credit him the idea came from his approach. Rather than using a test of the oldValue/newValue and allowing the event to be called twice, you can go around the change detection by doing the following.
event.node.data[event.colDef.field] = event.oldValue;
event.api.refreshCells({ rowNodes: [event.node], columns: [event.column.colId] });
What that does is sets the data directly in the data store used by aggrid, then you tell it to refresh that grid. That will prevent the onCellValueChanged event from having to be called again.
(if you arent using colIds you can use the field or pass the whole column, I think any of them work)
I am trying to find the currently selected Project/File and all ways I found so far are using the ISelectionService. The way I found to get an instance of it is:
ISelectionService selectionService = PlatformUI.getWorkbench()
.getActiveWorkbenchWindow().getActivePage();
But unfortunatly .getActiveWorkbenchWindow() returns null since I am not inside the UI-Thread. Is there any good way to get the ISelectionService from outside the UI-Thread?
You can use:
Display.getDefault().synchExec(runnable);
to run a Runnable in the UI thread where you can get the selection service.
However most UI code expects to be run in the UI thread so you may need to use syncExec or asyncExec for anything that is to do with the UI.
Most likely you are following the wrong appraoch. You should obtain the selected project or file before you run the non-UI/background thread.
If your background thread is triggered by a user interaction and should operate on the selected resource (project/file) the selection may well have changed when the thread reaches the point where it queries the selection.
Instead, evaluate the selection on the UI thread and pass the extracted resource as a parameter to your background thread.
For example (simplified code):
// on the UI thread:
IResource resource = ( IResource )getSelectionService().getSelection().getFirstElement();
new Thread( new Runnable() {
public void run() {
resource.copy( ... ); /// or whatever should be done with the resource
}
} ).start();
I've got a GWT 2.4 app where I'm "swapping views" by switching out one Composite widget on the RootPanel for another, using the usual RootPanel.get().clear() and RootPanel.get().add(newWidget) to remove and add, respectively.
The first composite widget contains a PasswordTextBox. It listens for the Enter keypress, which triggers the swap. Nothing too fancy:
getDisplay().getPasswordBoxForKeyPresses().addKeyPressHandler(new KeyPressHandler() {
public void onKeyPress(KeyPressEvent event) {
if (event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER) {
swapWidgets(); // clear RootPanel and add new widget
}
}
});
The problem is that there is a DOM memory leak: after RootPanel.get().clear() is called, the old composite widget is stuck in the detached DOM tree because the HTMLInputElement for the PasswordTextBox has some strange reference to it which I cannot identify.
Update:
I took the helpful advice below, compiled at style=detailed and started trying to drive down the tree to look a reference to the element in JS. I'm pretty new with GWT, so it still isn't obvious to me what's going on. So starting with the second line in the retaining tree, I can see that lastEvent in _2 contains the nativeKeyTarget listed at the top of tree. But where do I go from there?
I tracked the reference down to SmartGWT. It tracks the last click event within ISC_Core.js. Further questions are
How will this behavior further affect memory usage in my app?
Can this behavior be bypassed if need be?
But those questions are for another post!
Looks like you may not be tracking the handler registrations which will cause memory leaks, prevent objects from being recovered, and cause phantom event captures.
In pure GWT, it looks like this
// class member
HandlerRegistration reg;
// save for recovery
reg = getDisplay().getPasswordBoxForKeyPresses().addKeyPressHandler(...);
public void onDetatch() {
// recover memory
reg.removeHandler();
reg = null;
}
GXT has a nice grouping feature to prevent registration sprawl, it looks like this
// class member
GroupingHandlerRegistration regs = new GroupingHandlerRegistration();
// save for later recovery
regs.add( getDisplay().getPasswordBoxForKeyPresses().addKeyPressHandler(...) );
// recover memory
regs.removeHandler();
Source code for GroupingHandlerRegistration
I've been looking at a view examples of the typical "raise dialog from viewmodel" problem, noting 3 main solutions:
use attached behaviors
use a mediator pattern
use a service
I'm getting a bit bogged down though and struggling to find a solution that easily fits into my problem space - which is a very simple file copy problem:
My viewmodel is processing a loop (copying a list of files)
When a file already exists at the destination I need to raise a modal dialog to get confirmation to replace
The vm needs to wait for and receive confirmation before continuing
The "modal dialog" is actually not a new window but a hidden overlay in my MainWindow, as per http://www.codeproject.com/KB/WPF/wpfmodaldialog.aspx (thanks Ronald!)
I'm mostly there but the biggest struggles I have are:
- how to pause the loop in the viewmodel while it waits for input
- how to get input back to the viewmodel within the loop so it can carry on
So far I'm leaning towards the service solution because it seems a direct method call with a return that the vm must wait for. However, it does mean the service needs to tie directly to the view in order to make an element visible?
If anyone can post some simple code that deals directly with this problem I (and the net) would be very happy! Thanks!
For example, you have a service called IDialogService with the following interface:
public interface IDialogService
{
bool ConfirmAction(string title, string confirmationText);
}
As you mentioned, in order for the service to be able to show the actual dialog it needs to have a reference to the view that will show the actual overlay element. But instead of directly referencing the view I prefer to reference it via an interface. Lets call it ICanShowDialog and it will have the following members:
public interface ICanShowDialog
{
void ShowDialog(object dialogContent);
void HideDialog();
}
This interface will be implemented by your view that owns the dialog overlay (e.g. your main window).
Now the interesting part: suspending the code execution while the dialog is shown. First of all, I would recommend you not to use overlay elements but use usual windows if possible. Then you will not have that problem. You can style the dialog window so it will look just like the overlay element.
Anyway, if you still want to use overlay elements then you can do the following trick to suspend the code execution:
Here is pseudo code of the ConfirmAction method of the IDialogService inteface:
public bool ConfirmAction(string title, string confirmationText)
{
ConfirmationDialogView dialogView = new ConfirmationDialogView(title, confirmationText);
DialogShower.ShowDialog(dialogView); // DialogShower is of type ICanShowDialog
while (!dialogView.ResultAvailable)
{
DispatcherUtils.DoEvents();
}
DialogShower.HideDialog();
return dialogView.Result;
}
Here is the code of DispatcherUtils.DoEvents() (that was taken from here: http://dedjo.blogspot.com/2007/08/how-to-doevents-in-wpf.html):
public static class DispatcherUtils
{
public static void DoEvents()
{
DispatcherFrame f = new DispatcherFrame();
Dispatcher.CurrentDispatcher.BeginInvoke(
DispatcherPriority.Background,
(SendOrPostCallback)delegate(object arg) {
DispatcherFrame fr = arg as DispatcherFrame;
fr.Continue=True;
}, f);
Dispatcher.PushFrame(frame);
}
}
But I must warn you. Using DoEvents can result in some subtle bugs caused by inner dispatcher loops.
As an alternative to suspending the code execution while a dialog is shown you can use callbacks:
public interface IDialogService
{
void ConfirmAction(string title, string confirmationText, Action<bool> dialogResultCallback);
}
But it will not be so convenient to use.