diff options
author | elmot <elmot@vaadin.com> | 2015-09-25 16:40:44 +0300 |
---|---|---|
committer | elmot <elmot@vaadin.com> | 2015-09-25 16:40:44 +0300 |
commit | a1b265c318dbda4a213cec930785b81e4c0f7d2b (patch) | |
tree | b149daf5a4f50b4f6446c906047cf86495fe0433 /documentation/advanced/advanced-dragndrop.asciidoc | |
parent | b9743a48a1bd0394f19c54ee938c6395a80f3cd8 (diff) | |
download | vaadin-framework-a1b265c318dbda4a213cec930785b81e4c0f7d2b.tar.gz vaadin-framework-a1b265c318dbda4a213cec930785b81e4c0f7d2b.zip |
Framework documentation IN
Change-Id: I767477c1fc3745f9e1f58075fe30c9ac8da63581
Diffstat (limited to 'documentation/advanced/advanced-dragndrop.asciidoc')
-rw-r--r-- | documentation/advanced/advanced-dragndrop.asciidoc | 567 |
1 files changed, 567 insertions, 0 deletions
diff --git a/documentation/advanced/advanced-dragndrop.asciidoc b/documentation/advanced/advanced-dragndrop.asciidoc new file mode 100644 index 0000000000..69335c6369 --- /dev/null +++ b/documentation/advanced/advanced-dragndrop.asciidoc @@ -0,0 +1,567 @@ +--- +title: Drag and Drop +order: 12 +layout: page +--- + +[[advanced.dragndrop]] += Drag and Drop + +((("Drag and Drop", id="term.advanced.dragndrop", range="startofrange"))) + + +Dragging an object from one location to another by grabbing it with mouse, +holding the mouse button pressed, and then releasing the button to "drop" it to +the other location is a common way to move, copy, or associate objects. For +example, most operating systems allow dragging and dropping files between +folders or dragging a document on a program to open it. In Vaadin, it is +possible to drag and drop components and parts of certain components. + +Dragged objects, or __transferables__, are essentially data objects. You can +drag and drop rows in [classname]#Table# and nodes in [classname]#Tree# +components, either within or between the components. You can also drag entire +components by wrapping them inside [classname]#DragAndDropWrapper#. + +Dragging starts from a __drag source__, which defines the transferable. +Transferables implement the [classname]#Transferable# interfaces. For trees and +tables, which are bound to [classname]#Container# data sources, a node or row +transferable is a reference to an [classname]#Item# in the Vaadin Data Model. +Dragged components are referenced with a [classname]#WrapperTransferable#. +Starting dragging does not require any client-server communication, you only +need to enable dragging. All drag and drop logic occurs in two operations: +determining ( __accepting__) where dropping is allowed and actually dropping. +Drops can be done on a __drop target__, which implements the +[classname]#DropTarget# interface. Three components implement the interface: +[classname]#Tree#, [classname]#Table#, and [classname]#DragAndDropWrapper#. +These accept and drop operations need to be provided in a __drop handler__. +Essentially all you need to do to enable drag and drop is to enable dragging in +the drag source and implement the [methodname]#getAcceptCriterion()# and +[methodname]#drop()# methods in the [classname]#DropHandler# interface. + +The client-server architecture of Vaadin causes special requirements for the +drag and drop functionality. The logic for determining where a dragged object +can be dropped, that is, __accepting__ a drop, should normally be done on the +client-side, in the browser. Server communications are too slow to have much of +such logic on the server-side. The drag and drop feature therefore offers a +number of ways to avoid the server communications to ensure a good user +experience. + +[[advanced.dragndrop.drophandler]] +== Handling Drops + +Most of the user-defined drag and drop logic occurs in a __drop handler__, which +is provided by implementing the [methodname]#drop()# method in the +[classname]#DropHandler# interface. A closely related definition is the drop +accept criterion, which is defined in the [methodname]#getAcceptCriterion()# +method in the same interface. It is described in +<<advanced.dragndrop.acceptcriteria>> later. + +The [methodname]#drop()# method gets a [classname]#DragAndDropEvent# as its +parameters. The event object provides references to two important object: +[classname]#Transferable# and [classname]#TargetDetails#. + +A [classname]#Transferable# contains a reference to the object (component or +data item) that is being dragged. A tree or table item is represented as a +[classname]#TreeTransferable# or [classname]#TableTransferable# object, which +carries the item identifier of the dragged tree or table item. These special +transferables, which are bound to some data in a container, are +[classname]#DataBoundTransferable#. Dragged components are represented as +[classname]#WrapperTransferable# objects, as the components are wrapped in a +[classname]#DragAndDropWrapper#. + +The [classname]#TargetDetails# object provides information about the exact +location where the transferable object is being dropped. The exact class of the +details object depends on the drop target and you need to cast it to the proper +subclass to get more detailed information. If the target is selection component, +essentially a tree or a table, the [classname]#AbstractSelectTargetDetails# +object tells the item on which the drop is being made. For trees, the +[classname]#TreeTargetDetails# gives some more details. For wrapped components, +the information is provided in a [classname]#WrapperDropDetails# object. In +addition to the target item or component, the details objects provide a __drop +location__. For selection components, the location can be obtained with the +[methodname]#getDropLocation()# and for wrapped components with +[methodname]#verticalDropLocation()# and [methodname]#horizontalDropLocation()#. +The locations are specified as either [classname]#VerticalDropLocation# or +[classname]#HorizontalDropLocation# objects. The drop location objects specify +whether the transferable is being dropped above, below, or directly on (at the +middle of) a component or item. + +Dropping on a [classname]#Tree#, [classname]#Table#, and a wrapped component is +explained further in the following sections. + + +[[advanced.dragndrop.treedrop]] +== Dropping Items On a [classname]#Tree# + +You can drag items from, to, or within a [classname]#Tree#. Making tree a drag +source requires simply setting the drag mode with [methodname]#setDragMode()#. +[classname]#Tree# currently supports only one drag mode, +[literal]#++TreeDragMode.NODE++#, which allows dragging single tree nodes. While +dragging, the dragged node is referenced with a [classname]#TreeTransferable# +object, which is a [classname]#DataBoundTransferable#. The tree node is +identified by the item ID of the container item. + +When a transferable is dropped on a tree, the drop location is stored in a +[classname]#TreeTargetDetails# object, which identifies the target location by +item ID of the tree node on which the drop is made. You can get the item ID with +[methodname]#getItemIdOver()# method in +[classname]#AbstractSelectTargetDetails#, which the +[classname]#TreeTargetDetails# inherits. A drop can occur directly on or above +or below a node; the exact location is a [classname]#VerticalDropLocation#, +which you can get with the [methodname]#getDropLocation()# method. + +In the example below, we have a [classname]#Tree# and we allow reordering the +tree items by drag and drop. + + +[source, java] +---- +final Tree tree = new Tree("Inventory"); +tree.setContainerDataSource(TreeExample.createTreeContent()); +layout.addComponent(tree); + +// Expand all items +for (Iterator<?> it = tree.rootItemIds().iterator(); it.hasNext();) + tree.expandItemsRecursively(it.next()); + +// Set the tree in drag source mode +tree.setDragMode(TreeDragMode.NODE); + +// Allow the tree to receive drag drops and handle them +tree.setDropHandler(new DropHandler() { + public AcceptCriterion getAcceptCriterion() { + return AcceptAll.get(); + } + + public void drop(DragAndDropEvent event) { + // Wrapper for the object that is dragged + Transferable t = event.getTransferable(); + + // Make sure the drag source is the same tree + if (t.getSourceComponent() != tree) + return; + + TreeTargetDetails target = (TreeTargetDetails) + event.getTargetDetails(); + + // Get ids of the dragged item and the target item + Object sourceItemId = t.getData("itemId"); + Object targetItemId = target.getItemIdOver(); + + // On which side of the target the item was dropped + VerticalDropLocation location = target.getDropLocation(); + + HierarchicalContainer container = (HierarchicalContainer) + tree.getContainerDataSource(); + + // Drop right on an item -> make it a child + if (location == VerticalDropLocation.MIDDLE) + tree.setParent(sourceItemId, targetItemId); + + // Drop at the top of a subtree -> make it previous + else if (location == VerticalDropLocation.TOP) { + Object parentId = container.getParent(targetItemId); + container.setParent(sourceItemId, parentId); + container.moveAfterSibling(sourceItemId, targetItemId); + container.moveAfterSibling(targetItemId, sourceItemId); + } + + // Drop below another item -> make it next + else if (location == VerticalDropLocation.BOTTOM) { + Object parentId = container.getParent(targetItemId); + container.setParent(sourceItemId, parentId); + container.moveAfterSibling(sourceItemId, targetItemId); + } + } +}); +---- + +[[advanced.dragndrop.treedrop.criteria]] +=== Accept Criteria for Trees + +[classname]#Tree# defines some specialized accept criteria for trees. + +[classname]#TargetInSubtree#(client-side):: Accepts if the target item is in the specified sub-tree. The sub-tree is specified by the item ID of the root of the sub-tree in the constructor. The second constructor includes a depth parameter, which specifies how deep from the given root node are drops accepted. Value [literal]#++-1++# means infinite, that is, the entire sub-tree, and is therefore the same as the simpler constructor. +[classname]#TargetItemAllowsChildren#(client-side):: Accepts a drop if the tree has [methodname]#setChildrenAllowed()# enabled for the target item. The criterion does not require parameters, so the class is a singleton and can be acquired with [methodname]#Tree.TargetItemAllowsChildren.get()#. For example, the following composite criterion accepts drops only on nodes that allow children, but between all nodes: ++ +[source, java] +---- +return new Or (Tree.TargetItemAllowsChildren.get(), new Not(VerticalLocationIs.MIDDLE)); +---- + +[classname]#TreeDropCriterion#(server-side):: Accepts drops on only some items, which as specified by a set of item IDs. You must extend the abstract class and implement the [methodname]#getAllowedItemIds()# to return the set. While the criterion is server-side, it is lazy-loading, so that the list of accepted target nodes is loaded only once from the server for each drag operation. See <<advanced.dragndrop.acceptcriteria>> for an example. + + +In addition, the accept criteria defined in [classname]#AbstractSelect# are +available for a [classname]#Tree#, as listed in +<<advanced.dragndrop.acceptcriteria>>. + + + +[[advanced.dragndrop.tabledrop]] +== Dropping Items On a [classname]#Table# + +You can drag items from, to, or within a [classname]#Table#. Making table a drag +source requires simply setting the drag mode with [methodname]#setDragMode()#. +[classname]#Table# supports dragging both single rows, with +[literal]#++TableDragMode.ROW++#, and multiple rows, with +[literal]#++TableDragMode.MULTIROW++#. While dragging, the dragged node or nodes +are referenced with a [classname]#TreeTransferable# object, which is a +[classname]#DataBoundTransferable#. Tree nodes are identified by the item IDs of +the container items. + +When a transferable is dropped on a table, the drop location is stored in a +[classname]#AbstractSelectTargetDetails# object, which identifies the target row +by its item ID. You can get the item ID with [methodname]#getItemIdOver()# +method. A drop can occur directly on or above or below a row; the exact location +is a [classname]#VerticalDropLocation#, which you can get with the +[methodname]#getDropLocation()# method from the details object. + +[[advanced.dragndrop.tabledrop.criteria]] +=== Accept Criteria for Tables + +[classname]#Table# defines one specialized accept criterion for tables. + +[classname]#TableDropCriterion#(server-side):: Accepts drops only on (or above or below) items that are specified by a set of item IDs. You must extend the abstract class and implement the [methodname]#getAllowedItemIds()# to return the set. While the criterion is server-side, it is lazy-loading, so that the list of accepted target items is loaded only once from the server for each drag operation. + + + + +[[advanced.dragndrop.acceptcriteria]] +== Accepting Drops + +((("Drag and Drop", "Accept Criteria", id="term.advanced.dragndrop.acceptcriteria", range="startofrange"))) + + +You can not drop the objects you are dragging around just anywhere. Before a +drop is possible, the specific drop location on which the mouse hovers must be +__accepted__. Hovering a dragged object over an accepted location displays an +__accept indicator__, which allows the user to position the drop properly. As +such checks have to be done all the time when the mouse pointer moves around the +drop targets, it is not feasible to send the accept requests to the server-side, +so drops on a target are normally accepted by a client-side __accept +criterion__. + +A drop handler must define the criterion on the objects which it accepts to be +dropped on the target. The criterion needs to be provided in the +[classname]#getAcceptCriterion()# method of the [classname]#DropHandler# +interface. A criterion is represented in an [classname]#AcceptCriterion# object, +which can be a composite of multiple criteria that are evaluated using logical +operations. There are two basic types of criteria: __client-side__ and +__server-side criteria__. The various built-in criteria allow accepting drops +based on the identity of the source and target components, and on the __data +flavor__ of the dragged objects. + +To allow dropping any transferable objects, you can return a universal accept +criterion, which you can get with [methodname]#AcceptAll.get()#. + + +[source, java] +---- +tree.setDropHandler(new DropHandler() { + public AcceptCriterion getAcceptCriterion() { + return AcceptAll.get(); + } + ... +---- + +[[advanced.dragndrop.acceptcriteria.client-side]] +=== Client-Side Criteria + +The __client-side criteria__, which inherit the +[classname]#ClientSideCriterion#, are verified on the client-side, so server +requests are not needed for verifying whether each component on which the mouse +pointer hovers would accept a certain object. + +The following client-side criteria are define in +[package]#com.vaadin.event.dd.acceptcriterion#: + +[classname]#AcceptAll#:: Accepts all transferables and targets. +[classname]#And#:: Performs the logical AND operation on two or more client-side criteria; accepts the transferable if all the given sub-criteria accept it. +[classname]#ContainsDataFlavour#:: The transferable must contain the defined data flavour. +[classname]#Not#:: Performs the logical NOT operation on a client-side criterion; accepts the transferable if and only if the sub-criterion does not accept it. +[classname]#Or#:: Performs the logical OR operation on two or more client-side criteria; accepts the transferable if any of the given sub-criteria accepts it. +[classname]#SourceIs#:: Accepts all transferables from any of the given source components +[classname]#SourceIsTarget#:: Accepts the transferable only if the source component is the same as the target. This criterion is useful for ensuring that items are dragged only within a tree or a table, and not from outside it. +[classname]#TargetDetailIs#:: Accepts any transferable if the target detail, such as the item of a tree node or table row, is of the given data flavor and has the given value. + + +In addition, target components such as [classname]#Tree# and [classname]#Table# +define some component-specific client-side accept criteria. See +<<advanced.dragndrop.treedrop>> for more details. + +[classname]#AbstractSelect# defines the following criteria for all selection +components, including [classname]#Tree# and [classname]#Table#. + +[classname]#AcceptItem#:: Accepts only specific items from a specific selection component. The selection component, which must inherit [classname]#AbstractSelect#, is given as the first parameter for the constructor. It is followed by a list of allowed item identifiers in the drag source. +[classname]#AcceptItem.ALL#:: Accepts all transferables as long as they are items. +[classname]#TargetItemIs#:: Accepts all drops on the specified target items. The constructor requires the target component ( [classname]#AbstractSelect#) followed by a list of allowed item identifiers. +[classname]#VerticalLocationIs.MIDDLE#,[classname]#TOP#, and[classname]#BOTTOM#:: The three static criteria accepts drops on, above, or below an item. For example, you could accept drops only in between items with the following: +[source, java] +---- +public AcceptCriterion getAcceptCriterion() { + return new Not(VerticalLocationIs.MIDDLE); +} +---- + + + + + +[[advanced.dragndrop.acceptcriteria.server-side]] +=== Server-Side Criteria + +The __server-side criteria__ are verified on the server-side with the +[methodname]#accept()# method of the [classname]#ServerSideCriterion# class. +This allows fully programmable logic for accepting drops, but the negative side +is that it causes a very large amount of server requests. A request is made for +every target position on which the pointer hovers. This problem is eased in many +cases by the component-specific lazy loading criteria +[classname]#TableDropCriterion# and [classname]#TreeDropCriterion#. They do the +server visit once for each drag and drop operation and return all accepted rows +or nodes for current [classname]#Transferable# at once. + +The [methodname]#accept()# method gets the drag event as a parameter so it can +perform its logic much like in [methodname]#drop()#. + + +[source, java] +---- +public AcceptCriterion getAcceptCriterion() { + // Server-side accept criterion that allows drops on any other + // location except on nodes that may not have children + ServerSideCriterion criterion = new ServerSideCriterion() { + public boolean accept(DragAndDropEvent dragEvent) { + TreeTargetDetails target = (TreeTargetDetails) + dragEvent.getTargetDetails(); + + // The tree item on which the load hovers + Object targetItemId = target.getItemIdOver(); + + // On which side of the target the item is hovered + VerticalDropLocation location = target.getDropLocation(); + if (location == VerticalDropLocation.MIDDLE) + if (! tree.areChildrenAllowed(targetItemId)) + return false; // Not accepted + + return true; // Accept everything else + } + }; + return criterion; +} +---- + +The server-side criteria base class [classname]#ServerSideCriterion# provides a +generic [methodname]#accept()# method. The more specific +[classname]#TableDropCriterion# and [classname]#TreeDropCriterion# are +conveniency extensions that allow definiting allowed drop targets as a set of +items. They also provide some optimization by lazy loading, which reduces server +communications significantly. + + +[source, java] +---- +public AcceptCriterion getAcceptCriterion() { + // Server-side accept criterion that allows drops on any + // other tree node except on node that may not have children + TreeDropCriterion criterion = new TreeDropCriterion() { + @Override + protected Set<Object> getAllowedItemIds( + DragAndDropEvent dragEvent, Tree tree) { + HashSet<Object> allowed = new HashSet<Object>(); + for (Iterator<Object> i = + tree.getItemIds().iterator(); i.hasNext();) { + Object itemId = i.next(); + if (tree.hasChildren(itemId)) + allowed.add(itemId); + } + return allowed; + } + }; + return criterion; +} +---- + + +[[advanced.dragndrop.acceptcriteria.indicators]] +=== Accept Indicators + +When a dragged object hovers on a drop target, an __accept indicator__ is +displayed to show whether or not the location is accepted. For +[parameter]#MIDDLE# location, the indicator is a box around the target (tree +node, table row, or component). For vertical drop locations, the accepted +locations are shown as horizontal lines, and for horizontal drop locations as +vertical lines. + +For [classname]#DragAndDropWrapper# drop targets, you can disable the accept +indicators or __drag hints__ with the [parameter]#no-vertical-drag-hints#, +[parameter]#no-horizontal-drag-hints#, and [parameter]#no-box-drag-hints# +styles. You need to add the styles to the __layout that contains__ the wrapper, +not to the wrapper itself. + + +[source, java] +---- +// Have a wrapper +DragAndDropWrapper wrapper = new DragAndDropWrapper(c); +layout.addComponent(wrapper); + +// Disable the hints +layout.addStyleName("no-vertical-drag-hints"); +layout.addStyleName("no-horizontal-drag-hints"); +layout.addStyleName("no-box-drag-hints"); +---- + + +(((range="endofrange", startref="term.advanced.dragndrop.acceptcriteria"))) + +[[advanced.dragndrop.dragging]] +== Dragging Components + +Dragging a component requires wrapping the source component within a +[classname]#DragAndDropWrapper#. You can then allow dragging by putting the +wrapper (and the component) in drag mode with [methodname]#setDragStartMode()#. +The method supports two drag modes: [parameter]#DragStartMode.WRAPPER# and +[parameter]#DragStartMode.COMPONENT#, which defines whether the entire wrapper +is shown as the drag image while dragging or just the wrapped component. + + +[source, java] +---- +// Have a component to drag +final Button button = new Button("An Absolute Button"); + +// Put the component in a D&D wrapper and allow dragging it +final DragAndDropWrapper buttonWrap = new DragAndDropWrapper(button); +buttonWrap.setDragStartMode(DragStartMode.COMPONENT); + +// Set the wrapper to wrap tightly around the component +buttonWrap.setSizeUndefined(); + +// Add the wrapper, not the component, to the layout +layout.addComponent(buttonWrap, "left: 50px; top: 50px;"); +---- + +The default height of [classname]#DragAndDropWrapper# is undefined, but the +default width is 100%. If you want to ensure that the wrapper fits tightly +around the wrapped component, you should call [methodname]#setSizeUndefined()# +for the wrapper. Doing so, you should make sure that the wrapped component does +not have a relative size, which would cause a paradox. + +Dragged components are referenced in the [classname]#WrapperTransferable#. You +can get the reference to the dragged component with +[methodname]#getDraggedComponent()#. The method will return [literal]#++null++# +if the transferable is not a component. Also HTML 5 drags (see later) are held +in wrapper transferables. + + +[[advanced.dragndrop.drop-on-component]] +== Dropping on a Component + +Drops on a component are enabled by wrapping the component in a +[classname]#DragAndDropWrapper#. The wrapper is an ordinary component; the +constructor takes the wrapped component as a parameter. You just need to define +the [classname]#DropHandler# for the wrapper with +[methodname]#setDropHandler()#. + +In the following example, we allow moving components in an absolute layout. +Details on the drop handler are given later. + + +[source, java] +---- +// A layout that allows moving its contained components +// by dragging and dropping them +final AbsoluteLayout absLayout = new AbsoluteLayout(); +absLayout.setWidth("100%"); +absLayout.setHeight("400px"); + +... put some (wrapped) components in the layout ... + +// Wrap the layout to allow handling drops +DragAndDropWrapper layoutWrapper = + new DragAndDropWrapper(absLayout); + +// Handle moving components within the AbsoluteLayout +layoutWrapper.setDropHandler(new DropHandler() { + public AcceptCriterion getAcceptCriterion() { + return AcceptAll.get(); + } + + public void drop(DragAndDropEvent event) { + ... + } +}); +---- + +[[advanced.dragndrop.drop-on-component.details]] +=== Target Details for Wrapped Components + +The drop handler receives the drop target details in a +[classname]#WrapperTargetDetails# object, which implements the +[classname]#TargetDetails# interface. + + +[source, java] +---- +public void drop(DragAndDropEvent event) { + WrapperTransferable t = + (WrapperTransferable) event.getTransferable(); + WrapperTargetDetails details = + (WrapperTargetDetails) event.getTargetDetails(); +---- + +The wrapper target details include a [classname]#MouseEventDetails# object, +which you can get with [methodname]#getMouseEvent()#. You can use it to get the +mouse coordinates for the position where the mouse button was released and the +drag ended. Similarly, you can find out the drag start position from the +transferable object (if it is a [classname]#WrapperTransferable#) with +[methodname]#getMouseDownEvent()#. + + +[source, java] +---- +// Calculate the drag coordinate difference +int xChange = details.getMouseEvent().getClientX() + - t.getMouseDownEvent().getClientX(); +int yChange = details.getMouseEvent().getClientY() + - t.getMouseDownEvent().getClientY(); + +// Move the component in the absolute layout +ComponentPosition pos = + absLayout.getPosition(t.getSourceComponent()); +pos.setLeftValue(pos.getLeftValue() + xChange); +pos.setTopValue(pos.getTopValue() + yChange); +---- + +You can get the absolute x and y coordinates of the target wrapper with +[methodname]#getAbsoluteLeft()# and [methodname]#getAbsoluteTop()#, which allows +you to translate the absolute mouse coordinates to coordinates relative to the +wrapper. Notice that the coordinates are really the position of the wrapper, not +the wrapped component; the wrapper reserves some space for the accept +indicators. + +The [methodname]#verticalDropLocation()# and +[methodname]#horizontalDropLocation()# return the more detailed drop location in +the target. + + + +[[advanced.dragndrop.external]] +== Dragging Files from Outside the Browser + +The [classname]#DragAndDropWrapper# allows dragging files from outside the +browser and dropping them on a component wrapped in the wrapper. Dropped files +are automatically uploaded to the application and can be acquired from the +wrapper with [methodname]#getFiles()#. The files are represented as +[classname]#Html5File# objects as defined in the inner class. You can define an +upload [classname]#Receiver# to receive the content of a file to an +[classname]#OutputStream#. + +Dragging and dropping files to browser is supported in HTML 5 and requires a +compatible browser, such as Mozilla Firefox 3.6 or newer. + + +(((range="endofrange", startref="term.advanced.dragndrop"))) + + |