PDA

View Full Version : GXT & gwt-dnd



DavidHoffer
27 Dec 2011, 2:37 PM
I'm trying to use GXT with gwt-dnd because I need to perform some custom DnD behavior. I'm using the GXT Window as my DnD widget and for some reason this fails to call the onMove() method on the AbstractPositioningDropController, if I switch to a GWT widget like HTML then it works correctly.

Perhaps I can do this with just GXT DnD I'm not sure...I need to allow DnD on a specified grid pattern where each Window can be the size of one or more grid cells.

Anyone have any ideas why the GXT Window doesn't work with gwt-dnd or know how to do this with just GXT?

Colin Alworth
29 Dec 2011, 3:31 PM
I've always implemented custom DnD behavior with GXT using either the events available from the components (the Window class has a getDraggable() to get access to the events), or by extending Draggable or the DragSource/DropTarget classes.

Window's own setDraggable code might be interfering with the events from being consumed by gwt-dnd - if you must use gwt-dnd, make sure that you call setDraggable(false) on the window instance.

DavidHoffer
30 Dec 2011, 12:15 PM
Do you have some examples of extending DragSource/DropTarget you could send?

I'm trying to use this but it isn't working right for some reason, I'm following the code in the online examples. However the DragSource's onDragStart() gets fired but the DropTarget's onDragDrop() generally does not...I say generally a few times it did but I have yet to figure out why it fired a couple times. In the examples they use simple HTML widgets, I have complex objects such as Windows I am drag and dropping. Also how to set the allow drop status?

Most examples I find online are for 2.x which are different. If someone has 3.x examples that would be great.

Colin Alworth
30 Dec 2011, 12:49 PM
The DnD code hasn't changed significantly in 3, so examples should still be relevant.

Perhaps instead of my building/pointing to examples of how to do it, you can share what you have so far, and what it does or doesn't do - with regard to having onDragDrop called, it will only be called if the status is set to true, and there is currently a DropTarget active (i.e. about to be dropped on).

If you are building some kind of tiling window manager, you'll probably want the DragSource/DropTarget classes. If you just need to track/change window sizes/positions, the window.getDraggable() should expose most of what you need. Again, a clearer description of what you are after, what you've tried will help to assist further.

DavidHoffer
30 Dec 2011, 1:22 PM
I found that in 3.x several of the 2.x types don't exist anymore.. In any case here is an example of the code. What is happening is that I can drag and drop the Windows in the GridPanel however GridPanelDropTarget#onDragDrop() is not being called and I need it to because I want to control the drop location. Also while being dragged it shows the red circle so I assume that is why GridPanelDropTarget#onDragDrop() is not being called but I don't know how to control when and what is drag and dropable.

Also what is happening now is that the contents of the Windows are also drag and dropable and it does show the green checkbox indicating that it is drag and dropable but I don't want it to be, nothing in the Window should have DnD enabled but it currently does. Again I'm not understanding how to control what and when items are drag and dropable.


public class GridPanelDropTarget extends DropTarget {

private int standardConversationX;
private int standardConversationY;
private final AbsolutePanel gridPanel;

public GridPanelDropTarget(AbsolutePanel gridPanel) {
super(gridPanel);
this.gridPanel = gridPanel;

setGroup("grid");
setOverStyle("drag-ok");
}

@Override
protected void onDragDrop(DndDropEvent event) {
super.onDragDrop(event);
Window window = (Window) event.getData();

Point location = calculateClosestDropLocation(window);
gridPanel.add(window, location.getX(), location.getY());
}

public void setStandardConversationX(int standardConversationX) {
this.standardConversationX = standardConversationX;
}

public void setStandardConversationY(int standardConversationY) {
this.standardConversationY = standardConversationY;
}

private Point calculateClosestDropLocation(Widget target) {

if (standardConversationX == 0 || standardConversationY == 0){
throw new IllegalStateException("must set x & y");
}

final int absoluteLeft = target.getAbsoluteLeft();
final int absoluteTop = target.getAbsoluteTop();

int x = (int) Math.round((double) absoluteLeft / (double) standardConversationX) * standardConversationX;
int y = (int) Math.round((double) absoluteTop / (double) standardConversationY) * standardConversationY;

return new Point(x, y);
}
}

public class GridPanelDragSource extends DragSource {

private final Window window;

public GridPanelDragSource(Window window) {
super(window);
this.window = window;

setGroup("grid");
}

protected void onDragStart(DndDragStartEvent event) {
super.onDragStart(event);

event.setData(window);
}
}

public class GridPanel extends SimplePanel {
private final Resources resources;
private final AbsolutePanel gridPanel;
private static final int MARGIN = 4;
private GridPanelDropTarget dropTarget;

public GridPanel(Resources resources) {
this.resources = resources;

// Create a boundary panel to constrain all DnD operations
gridPanel = new AbsolutePanel();
gridPanel.setPixelSize(1200, 1200);

dropTarget = new GridPanelDropTarget(gridPanel);

setWidget(gridPanel);
}

private ContentPanel buildConversationPanel(String title, final int width, final int height) {
final Window window = new Window();

window.setMinHeight(200);
window.setMinWidth(200);
window.setPixelSize(width, height);
window.setCollapsible(false);
window.getHeader().setIcon(resources.webtasavailable_24());
window.setBodyStyleName("pad-text");
window.setHeadingText(title);
window.setResize(true);
window.setClosable(false);

applyHeaderTools(window);

BorderLayoutContainer borderLayoutContainer = new BorderLayoutContainer();

final ListView<String, String> list = getSampleConversation();
borderLayoutContainer.setCenterWidget(list, new MarginData(0));

final BorderLayoutContainer.BorderLayoutData southLayoutData = new BorderLayoutContainer.BorderLayoutData(30);
southLayoutData.setMargins(new Margins(0));
southLayoutData.setCollapsible(true);
southLayoutData.setSplit(true);
southLayoutData.setCollapseMini(false);
borderLayoutContainer.setSouthWidget(buildSendConversationPanel(), southLayoutData);

window.add(borderLayoutContainer, new MarginData(0));

// I want users to be able to drag & drop windows by DnD it's title bar.
DragSource source = new GridPanelDragSource(window);

return window;
}

public void onInitialLoad() {

int left = 0;
int top = 0;

final ContentPanel conversationPanel1 = buildConversationPanel("#private", 200, 200);
PanelDimensions panelDimensions = getActualPanelDimensions(conversationPanel1);
gridPanel.add(conversationPanel1, left, 0);

dropTarget.setStandardConversationX(panelDimensions.getWidth() + MARGIN);
dropTarget.setStandardConversationY(panelDimensions.getHeight() + MARGIN);

left += panelDimensions.getWidth() + MARGIN;
top += panelDimensions.getHeight() + MARGIN;

final ContentPanel conversationPanel2 = buildConversationPanel("bob", 200, 200);
panelDimensions = getActualPanelDimensions(conversationPanel2);
gridPanel.add(conversationPanel2, left, 0);
left += panelDimensions.getWidth() + MARGIN;

final ContentPanel conversationPanel3 = buildConversationPanel("#tom", 400, top + 200);
gridPanel.add(conversationPanel3, left, 0);

left = 0;

final ContentPanel conversationPanel4 = buildConversationPanel("randy", 200, top + 200);
panelDimensions = getActualPanelDimensions(conversationPanel4);
gridPanel.add(conversationPanel4, left, top);
left += panelDimensions.getWidth() + MARGIN;

final ContentPanel conversationPanel5 = buildConversationPanel("#foo", 200, 200);
gridPanel.add(conversationPanel5, left, top);
}

private PanelDimensions getActualPanelDimensions(ContentPanel conversationPanel) {
RootPanel.get().add(conversationPanel, -500, -500);
int offsetWidth = conversationPanel.getOffsetWidth();
int offsetHeight = conversationPanel.getOffsetHeight();
conversationPanel.removeFromParent();
return new PanelDimensions(offsetWidth, offsetHeight);
}

private HBoxLayoutContainer buildSendConversationPanel() {
final HBoxLayoutContainer sendLayoutContainer = new HBoxLayoutContainer(HBoxLayoutContainer.HBoxLayoutAlign.STRETCH);
// set content here...
return sendLayoutContainer;
}

private void applyHeaderTools(ContentPanel cp1) {
cp1.getHeader().insertTool(buildCollabButton(), 0);
cp1.getHeader().insertTool(buildClipboardButton(), 1);
cp1.getHeader().insertTool(buildSaveButton(), 2);
cp1.getHeader().insertTool(buildClearButton(), 3);
cp1.getHeader().insertTool(buildCloseButton(), 4);
}

private TextButton buildCloseButton() {
final TextButton close = new TextButton("", resources.close_20());
close.setPixelSize(24, 24);
return close;
}

private TextButton buildClearButton() {
final TextButton clear = new TextButton("", resources.clearconversation_20());
clear.setPixelSize(24, 24);
return clear;
}

private TextButton buildSaveButton() {
final TextButton save = new TextButton("", resources.save_20());
save.setPixelSize(24, 24);
return save;
}

private TextButton buildClipboardButton() {
final TextButton clipboard = new TextButton("", resources.clipboard());
clipboard.setPixelSize(24, 24);
return clipboard;
}

private TextButton buildCollabButton() {
final TextButton collab = new TextButton("", resources.collab_20());
collab.setPixelSize(24, 24);
return collab;
}

private ListView<String, String> getSampleConversation() {
final ListStore<String> listStore = new ListStore<String>(new ModelKeyProvider<String>() {
@Override
public String getKey(String s) {
return "A";
}
});
final ListView<String, String> list = new ListView<String, String>(listStore, new ValueProvider<String, String>() {
@Override
public String getValue(String s) {
return "(Dec 12 2011) Dave: Conversation log...";
}

@Override
public void setValue(String s, String s1) {
}

@Override
public String getPath() {
return null;
}
});
list.setSelectionModel(null);

listStore.add("A");
listStore.add("B");
listStore.add("C");
listStore.add("D");
listStore.add("E");
listStore.add("F");
listStore.add("G");
listStore.add("H");
listStore.add("I");
listStore.add("J");
return list;
}
}

DavidHoffer
30 Dec 2011, 1:55 PM
Also I should note that if I replace the previous code with just a DragHandler on the Window's Draggable that does work as expected but I don't know if that will be enough because I would like to show a drop target in the UI so users know exactly where the window will be positioned. (With just a DragHandler it just goes to the closest grid cell.)

Colin Alworth
30 Dec 2011, 2:00 PM
GridPanelDropTarget is only present in old versions of GXT 2 that still have the GridPanel, deprecated and replaced by Grid, GridDragSource, GridDropTarget. These types should all be present in GXT 3. Additionally, TreePanel, TreePanelDragSource, TreePanelDropTarget from 2 have been renamed Tree, TreeDragSource, TreeDropTarget.

If you create DragSource instances for the Window, the GridDropTarget might try to accept it as data to be dropped within the grid - if that is not what you mean, then I don't understand how a Window can be dropped within a Grid.

What are you trying to customize, beyond the ability for the Window to be dragged by its header to another position?

Colin Alworth
30 Dec 2011, 2:06 PM
Also I should note that if I replace the previous code with just a DragHandler on the Window's Draggable that does work as expected but I don't know if that will be enough because I would like to show a drop target in the UI so users know exactly where the window will be positioned. (With just a DragHandler it just goes to the closest grid cell.)

Okay, so you are intending to drop a Window into a position in a Grid - can you elaborate on what you mean by grid, and grid cell? Are we talking about the GXT grid, or your own layout container? From your earlier post , I see now you seem to be using an AbsolutePanel as your 'grid panel', and all of the positioning is within that.

The DragHandler is probably what you mean - you can use the onDragMove to see every time the user moves the window, and try to show what it will look like if they drop there with some sort of proxy.

Windows almost certainly shouldn't be added to another parent - they already know how to manage themselves as floating windows. Instead, consider wrapping up a ContentPanel with a Draggable, so it doesnt try to add itself. The ContentPanel's header should be the draggable portion.

DavidHoffer
30 Dec 2011, 2:50 PM
The GridPanel is custom, e.g.
GridPanel extends SimplePanel so it has nothing to do with a GXT grid. The reason I can't use GXT grid (correct me if I am wrong here) is that in my case the grid cells that go in the grid will be of varying size, e.g. some will be 1 cell x 1 cell some 2x1 some 3x3, etc. So yes I was trying to make my own grid layout using an AbsolutePanel, if there is a better way please advise.

Okay I will try to use a ContentPanel wrapped with Draggable instead of Window, I don't yet understand why Windows shouldn't be used but I will try the other approach. Wait...I do need the cell/widget to be resizable...I think I started with some other container and it wasn't resizable so I switched to using Window. Can I make a ContentPanel wrapped with Draggable resizeable?

Colin Alworth
30 Dec 2011, 3:05 PM
The main reason to wrap a ContentPanel instead of a Window is that the Window class really isn't intended for use in this manner, rendered as a child of another component - it expects to be responsible for adding/removing itself from the dom, and watching for other windows it needs to remain above/below.

The edges of a ContentPanel or other Component can be made into resizing bars using the com.sencha.gxt.fx.client.Resizable class. Note that this accepts handlers for its resize events.

Grid won't work, correct - it is intended to be used as a way to display tabular data, not to layout and position other widgets. It excels in drawing large amounts of data provided to it by a ListStore, but positioning widgets should be left to a panel, as you are doing.

DavidHoffer
30 Dec 2011, 3:08 PM
I just tried using a ContentPanel wrapped with Draggable instead of a Window as the conversation/cell widget. The problems are:

- Now the user can grab any location in the panel and perform the DnD, I want them to just be able to grab the title bar for DnD.
- Also because of the previous behavior the user cannot select/access any of the content IN the conversation/call because clicking anywhere is seen as the start of a drag operation. For instance there is a scroll panel in the cell...that is impossible to use now. Do I need to capture where the user's mouse is and disable dragable somehow?
- The panel is not resizable. I need them to be able to at least resize via the lower right corner and stretch the panel. Can this be done with a ContentPanel?

Colin Alworth
30 Dec 2011, 3:18 PM
If you can paste code that isn't working, I (and other community members) can be of more help...

As mentioned in an earlier post, and in the Draggable javadoc, use the Draggable(Widget,Widget) constructor to specify that the entire ContentPanel can be dragged via the header.


ContentPanel panel = new ContentPanel();
Draggable d = new Draggable(panel, panel.getHeader());

Take a look at com.sencha.gxt.widget.core.client.Window.getDraggable() to see how this is done in the Window class.

DavidHoffer
30 Dec 2011, 9:21 PM
Your suggestion of:



Draggable draggable = new Draggable(contentPanel, contentPanel.getHeader());
draggable.setConstrainClient(true);
draggable.setSizeProxyToSource(true);
draggable.addDragHandler(gridPanelDragHandler);

Does seem to allow the DnD to work but I'm not sure what difference the following makes.



draggable.setConstrainClient(true);
draggable.setSizeProxyToSource(true);

however I still don't understand how to make the contentPanel resizable. You said to use Resizable but how can I wrap the contentPanel with both the Draggable and Resizable?

Colin Alworth
30 Dec 2011, 9:26 PM
I'd highly recomend taking a look at the javadoc for both Draggable and Resizable - if it is unclear, let me know, but it seems clear to me for both of the setters you mention. Otherwise, try turning them off and on, and see what effect each has.

To make something both resizable and draggable, simply create one of each - something like this (config, handlers ommitted for brevity)


ContentPanel panel = new ContentPanel();
Draggable d = new Draggable(panel, panel.getHeader());
Resizable r = new Resizable(panel);//add one or more directions to limit how it can be resized, see the javadocs for details

outerPanel.add(panel, ...);

DavidHoffer
30 Dec 2011, 10:44 PM
I did try with and without...


draggable.setConstrainClient(true);
draggable.setSizeProxyToSource(true);

And I didn't see any difference yet in my app (perhaps because these are the defaults).

Regarding making a content panel drag-able and re-sizable, I now see that I can wrap the same content panel with Draggable and with Resizable but the code seems odd to me, i.e.

private ContentPanel someMethod()
ContentPanel contentPanel = new ContentPanel();
Draggable draggable = new Draggable(contentPanel, contentPanel.getHeader());
Resizable resizable = new Resizable(contentPanel);
return contentPanel

draggable & resizable both fall out of scope...not very Java/OO programing norm...one would think that Draggable & Resizable would do nothing in this case ...when you say wrap an object I naturally think the wrapped object must be the input of the next object/method. But although strange code this seems to be working so I will see if I can refine it for my needs.

Colin Alworth
31 Dec 2011, 8:27 AM
If you don't yourself maintain a refernce to the object, then yes, it falls out of scope, and _you_ have no way of accessing it, but why should it do nothing? Other code still has references to it - take a look in each of the Resizable and Draggable constructors, and notice how they add handlers to watch for events.


Do you keep references to all event handlers you write? Or do you add them to the component, and leave it at that? This is the same idea.

As for the setters, yes, the defaults are true, which is why i suggested turning them both on and off - from their descriptions in javadoc constrainClient should keep the thing you are dragging within the bounds of the window, and sizeProxyToSource should make the draggable proxy (if any) the same size as the original object. Both setters trindicate that the default is true already.

DavidHoffer
31 Dec 2011, 8:59 AM
Yes I see the constructors do a lot of work...but they shouldn't IMHO. Just like event listeners you don't expect to pass something into its constructor you expect to create the event listener instance and attach it to something...then it's up to you if you keep a reference to the object. E.g.




ContentPanel panel = new ContentPanel();
Draggable d = new Draggable(panel, panel.getHeader());
Resizable r = new Resizable(panel);
outerPanel.add(panel, ...);


should be something like...


ContentPanel panel = new ContentPanel();
panel.supportDraggable(new Draggable(...));
panel.supportResizable(new Resizable(...));
outerPanel.add(panel, ...);

or just...


ContentPanel panel = new ContentPanel(new Draggable(...), new Resizable(...));
outerPanel.add(panel, ...);

Colin Alworth
31 Dec 2011, 9:49 AM
We're straying significantly from the discussion at hand, but there are a few reasons that will make things worse. First, you'll be multiplying the number of args required to create the thing - and you'll need to find some common interface all of these will adhere to, which may have the effect of just changing

ContentPanel panel = new ContentPanel();
Draggable d = new Draggable(panel, panel.getHeader());
new Draggable(panel, panel.getHeader());
new Resizable(panel, Dir.N, Dir.E, Dir.S, Dir.W);

to

ContentPanel panel = new ContentPanel();
//need to still specify the header is the actual draggable part
//hypothetical Behavior interface would specify some way to indicate the compoent that gets the behavior
panel.addBehavior(new Draggable(panel.getHeader()));
panel.addBehavior(new Resizable(Dir.N, Dir.E, Dir.S, Dir.W);

This concept of making it part of the ContentPanel (probably implemented at Component)'s responsibility means that the Behavior interface can handle all possible use cases for any behavior added to it - how the user can move it, how things can be dropped on to it (see DragSource and DropTarget for these kinds of cases), and getting into the realm of touch events will add more complexity, since more than one touch can be happening at a time. Meanwhile Draggable and Resizable are simple to use, documented, and examples are provided (both on the examples page and in code, see Window, DragSource, ColumnHeader, http://www.sencha.com/examples-dev/#ExamplePlace:draggable and http://www.sencha.com/examples-dev/#ExamplePlace:logos). Additionally, Draggable is written so it can apply to any/all Widgets, not just those which extend GXT's Component class. Resizable should do the same, I've filed an issue to make sure we support this.

We're far enough into GXT 3 that I don't think major modifications will be made at this time to how these are working, especially as Draggable and Resizable are mostly unchanged from 2.x (and 1.x I believe) - philosophical discussions of perfect APIs aside, they do the job more than adaquetly. Keep in mind that holding a reference to these can be very beneficial across the life of the object - both classes have a release() method to remove handlers and prevent the effect from continuing. Additionally, if one of these is attached to a detached ContentPanel (or any Widget), and the only reference to it are from the Resizable/Draggable, it will be garbarge collected, thanks to GWT's dom event listener bookkeeping occuring on detach.

That said, if you have a complete suggestion to how this can be built while still supporting all existing use cases, we'd be interested in discussing it. Feel free to join us on irc.freenode.net at #extgwt for more discussion.

DavidHoffer
31 Dec 2011, 10:19 AM
I concur we are straying now from the topic but...

My point here is basically that the way things are now may work, like you say, but the problem is it's not intuitive from an API perspective. Now the things are completely decoupled and that can have some advantages but it requires deep knowledge of the GXT library to know how to get things to work. Normally one expects to see methods on objects (or constructor params) that provide all the functionality one can do with an object. Furthermore there is a setResize() method on ContentPanel that does absolutely nothing (from what I can tell), so not knowing that a separate decoupled Resizable existed I switched to using a Window instead of ContentPanel because it in the the resizing worked as expected. How is one to know that resizing is completely decoupled? What other completely decoupled things am I missing? Again, for new folks using a library the natural thing to do is start with an object and look to its methods to know what it can and can't do, not browse for all completely decoupled objects and see if I can combine them somehow.

Colin Alworth
31 Dec 2011, 11:01 AM
SimpleContainer.setResize/isResize should be used by doLayout, but this seems to not be called as normal due to how ContentPanel.onResize overrides things. I'll look into how that should behave. However, if you look at the setResize javadoc, it clearly has nothing to do with the user resizing and dragging:

True to resize the child widget to match the container size (defaults to true).

We are still missing a good example with Resizable from http://www.sencha.com/examples-dev/ - we'll add that in a future beta. That said, searching for draggable on that page quickly brings up an example using Draggable with ContentPanel, configuring it in different ways. While I admit that we are missing javadoc and examples (again, this is the first beta for this release), we do have a number of examples already present. Many points I've brought up would have been clear to you from reading the javadoc already present, or the examples already available. Based on this thread, I'll be working to make them more visible or easier to find.

It is not a design goal of GXT to live as a self-sufficient unit, divorced from other GWT libraries, and the basic GWT runtime - in fact, we strive to do the opposite. Many of the changes in 3 are done to offer better interoperability with existing and future GWT concepts - UiBinder, Editor framework, Cells, and working directly with any/all Widgets instead of requiring Component subclasses. We cannot redefine the Widget class or the Cell interface as much as we'd like to, to provide functionality that we think is obvious or useful, so are limited in how we can add features. This is a basic issue of OOP in Java - we can't add mixin methods - other languages get around this by supporting redefining existing methods on existing classes like JavaScript does - but most GWT projects are using GWT because of the maintainability details it encourages - so developers can't just redefine/add methods in certain places as it suites them.

DavidHoffer
31 Dec 2011, 1:58 PM
I wasn't trying to imply you should divorce GXT from GWT it should be interoperable. I didn't look for draggable example because that was working with Window, I was using Window because I couldn't figure out how to make resizable work with ContentPanel...until this discussion where you indicated I shouldn't be using Window...like you said you will add example of resizable. I understand you can't change GWT classes...but my point is...if there are not methods on objects that make it clear what can be done...it comes down to having really good docs...both online showcase examples and good javadocs. I understand you are in beta and it's a work in progress.

Colin Alworth
31 Dec 2011, 2:10 PM
Yep, this all makes sense. Keep asking questions to learn, and to point out where we are lacking, and the betas will keep getting better.

DavidHoffer
31 Dec 2011, 2:27 PM
I really appreciate your willingness to reply to these posts...and even on new year's eve! It really helps us get up-to-speed on GXT (w/o help we would make many wrong turns). In my case I am creating a demo for management at my company to see. We are moving away from Flex and the big question is what to use...some have already chosen Dojo...I prefer to use GWT but it's critical it can do what our apps need...and that's why I'm using GXT...yours seems to be the most full featured GWT library.