aboutsummaryrefslogtreecommitdiffstats
path: root/documentation/advanced/advanced-pushstate.asciidoc
blob: 5797366dec4657520a6722b1cf7b20a1f983d123 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
---
title: Manipulating browser history
order: 11
layout: page
---

[[advanced.pushstate]]
= Manipulating browser history

A major issue in AJAX applications is that as they run in a single web page.
Bookmarking the application URL (or more generally the __URI__) can only
bookmark the application, not an application state. This is a problem for many
applications, such as product catalogs and discussion forums, in which it would
be good to provide links to specific products or messages. The solution is to 
modify the URI of the browser using https://developer.mozilla.org/en-US/docs/Web/API/History_API[History APIs]
[methodname]#pushState# or [methodname]#replaceState# functions, whenever developer
wants to simulate a logical page change. There is a server side API for those 
methods and a mechanism to listen to changes in the client side URI in the
[classname]#Page# object. 

Vaadin offers two ways to modify URIs: the high-level
[classname]#Navigator# utility described in
<<dummy/../../../framework/advanced/advanced-navigator#advanced.navigator,"Navigating
in an Application">> and the low-level API described here.

[[advanced.urifu.setting]]
== Setting the URL displayed in the browser

You can set the current fragment identifier with the
[methodname]#pushState()# method in the [classname]#Page# object.


[source, java]
----
Page.getCurrent().pushState("mars");
----

The parameter (both String and URI are supported) is resolved on the current URL. Both relative and absolute URIs are supported, but note that browsers accept only URLs of the same origin as the current URL. 

A call to _pushState_ creates a new entry to browsers history. If you wish to avoid this, and just replace the current URL in the browser, use the related [methodname]#replaceState# method.


[[advanced.pushstate.popstate]]
== Listening for "in-page" URI Changes

If your application uses pushState to update the location and after that the user uses browsers back/forward button, a full page reload does not happen and the UIs init method is not called like when entering the page for the first time. To detect these change you can use [interfacename]#PopChangeListener#.

For example, we could define the listener as follows in the [methodname]#init()#
method of a UI class:


[source, java]
----
public class MyUI extends UI {
    @Override
    protected void init(VaadinRequest request) {
        getPage().addPopStateListener( e -> enter() );
        
        // Read the initial URI fragment
        enter();
    }

    void enter() {
        URI location = getPage().getLocation();
        ... initialize the UI ...
    }
}
----
reprocFile */ .highlight .c1 { color: #888888 } /* Comment.Single */ .highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */ .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ .highlight .ge { font-style: italic } /* Generic.Emph */ .highlight .gr { color: #aa0000 } /* Generic.Error */ .highlight .gh { color: #333333 } /* Generic.Heading */ .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ .highlight .go { color: #888888 } /* Generic.Output */ .highlight .gp { color: #555555 } /* Generic.Prompt */ .highlight .gs { font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #666666 } /* Generic.Subheading */ .highlight .gt { color: #aa0000 } /* Generic.Traceback */ .highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */ .highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */ .highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */ .highlight .kp { color: #008800 } /* Keyword.Pseudo */ .highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */ .highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */ .highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */ .highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */ .highlight .na { color: #336699 } /* Name.Attribute */ .highlight .nb { color: #003388 } /* Name.Builtin */ .highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */ .highlight .no { color: #003366; font-weight: bold } /* Name.Constant */ .highlight .nd { color: #555555 } /* Name.Decorator */ .highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */ .highlight .nl { color: #336699; font-style: italic } /* Name.Label */ .highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */ .highlight .py { color: #336699; font-weight: bold } /* Name.Property */ .highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #336699 } /* Name.Variable */ .highlight .ow { color: #008800 } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */ .highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ .highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
---
title: Drag and Drop
order: 12
layout: page
---

[[advanced.dragndrop]]
= Drag and Drop

((("Drag and Drop", id="term.advanced.dragndrop", range="startofrange")))

IMPORTANT: This feature is currently being developed and only available in the Framework 8.1 prerelease versions, starting from 8.1.0.alpha1.

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. Framework version 8.1 adds support for https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API[HTML5 drag and drop] features. This makes it possible to set components as drag sources that user can drag and drop, or to set them as drop targets to drop things on.

[NOTE]
====
Note that the HTML5 drag and drop feature is not supported on touch devices.
====

== Drag Source

Any component can be made a drag source that has textual data that is transferred when it is dragged and dropped.

To make a component a drag source, you apply the [classname]#DragSourceExtension# to it. Then you can define the text to transfer, and the allowed drag effect.

[source, java]
----
Label draggableLabel = new Label("You can grab and drag me");
DragSourceExtension<Label> dragSource = new DragSourceExtension<>(draggableLabel);

// set the allowed effect
dragSource.setEffectAllowed(EffectAllowed.MOVE);
// set the text to transfer
dragSource.setDataTransferText("hello receiver");
----

The __effect allowed__ specifies the allowed effects that must match the __drop effect__ of the drop target. If these don't match, the drop event is never fired on the target. If multiple effects are allowed, the user can use the modifier keys to switch between the desired effects. The default effect and the modifier keys are system and browser dependent.

The __data transfer text__ is textual data, that the drop target will receive in the __drop event__.

The [classname]#DragStartEvent# is fired when the drag has started, and the [classname]#DragEndEvent# event when the drag has ended, either in a drop or a cancel.

[source, java]
----
dragSource.addDragStartListener(event ->
    event.getComponent().addStyleName("dragged")
);
dragSource.addDragEndListener(event -> {
    event.getComponent().removeStyleName("dragged")

    if (event.isCanceled()) {
        Notification.show("Drag event was canceled");
    }
});
----

You can check whether the drag was canceled using the `isCanceled()` method.

It is possible to transfer any Object as server side data to the drop target if both the drag source and drop target are placed in the same UI. This data is available in the drop event via the `DropEvent.getDragData()` method.

[source, java]
----
dragSource.addDragStartListener(event ->
    dragSource.setDragData(myObject);
);
dragSource.addDragEndListener(event ->
    dragSource.setDragData(null);
};
----

[NOTE]
====
The browsers allow the user to select and drag and drop text, which may cause some issues when trying to drag a component that has text. You can fix this by setting the following style rule to the drag source component to prevent dragging of the text instead of the whole component.
[source, css]
----
user-select: none;
----
====

=== CSS Style Rules

The drag source element, additional to it's primary style name, have a style name with the `-dragsource` suffix. For example, a Label component would have the style name `v-label-dragsource` when the drag source extension is applied to it.
Additionally, the elements also have the `v-draggable` style name that is independent of the component's primary style.

[[advanced.dragndrop.drophandler]]
== Drop Target

The drag operations end when the mouse button is released on a valid drop target. It is then up to the target to react to the drop event and the data associated with the drag, set by the drag source.

To make a component be a drop target, you apply the [classname]#DropTargetExtension# to it. The extension allows you to control when the drop is acceptable and then react to the drop event.

[source, java]
----
VerticalLayout dropTargetLayout = new VerticalLayout();
dropTargetLayout.setCaption("Drop things inside me");
dropTargetLayout.addStyleName(ValoTheme.LAYOUT_CARD);

// make the layout accept drops
DropTargetExtension<VerticalLayout> dropTarget = new DropTargetExtension<>(dropTargetLayout);

// the drop effect must match the allowed effect in the drag source for a successful drop
dropTarget.setDropEffect(DropEffect.MOVE);

// catch the drops
dropTarget.addDropListener(event -> {
    // if the drag source is in the same UI as the target
    Optional<AbstractComponent> dragSource = event.getDragSourceComponent();
    if (dragSource.isPresent() && dragSource.get() instanceof Label) {
        // move the label to the layout
        dropTargetLayout.addComponent(dragSource.get());
        
        // get possible transfer data
        String message = event.getDataTransferText();
        Notification.show("DropEvent with data transfer: "+ message);

        // handle possible server side drag data, if the drag source was in the same UI
        event.getDragData().ifPresent(data -> handleMyDragData((MyObject) data));
    }
});
----

When data is dragged over a drop target, the __v-drag-over__ class name is applied to the root element of the drop target component automatically.

=== Controlling When The Drop is Acceptable

The __drop effect__ allows you to specify the desired drop effect, and for a succesful drop it must match the allowed effect that has been set for the drag source. Note that you can allow multiple effects, and that you should not rely on the default effect since it may vary between browsers.

The __drag over criteria__ allows you determine whether the current drag source is allowed as a drop target, when the source is moved on top of the target. It is a script that is executed always when the `dragover` event is fired for the first time for this source, and returning `false` will prevent showing any drop effect. The script gets the `dragover` event as a parameter named `event`.

The __drop criteria__ is similar to __drag over criteria__, but it is executed when the user has dropped the data by releasing the mouse button. The script gets the `drop` event as a parameter named `event`. Returning `false` will prevent the drop and no drop event is fired on the server side.

=== CSS Style Rules

When dragging data over a drop target and the drag over criteria passes, a style name is applied to indicate that the element accepts drops. This style name is the primary style name with `-drag-center` suffix, e.g. `v-label-drag-center`.

////
TODO Add an example of drag over criteria and drop criteria
////

===

////
TODO add back when supported with new API ?
[[advanced.dragndrop.external]]
== Dragging Files from Outside the Browser

The [classname]#DropTargetExtension# allows dragging files from outside the
browser and dropping them on a target component.

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.

////

[[advanced.dragndrop.grid]]
== Drag and Drop Rows in Grid

It is possible to drag and drop the rows of a Grid component. This allows reordering of rows, dragging rows between different Grids, dragging rows outside of a Grid or dropping data onto rows.

=== Grid as a Drag Source

A Grid component's rows can be made draggable by applying [classname]#GridDragSource# extension to the component. The extended Grid's rows become draggable, meaning that each row can be grabbed and moved by the mouse individually.
When the Grid's selection mode is `SelectionMode.MULTI` and multiple rows are selected, it is possible to drag all the visible selected rows by grabbing one of them. However, when the grabbed row is not selected, only that one row will be dragged.

[NOTE]
====
It is important to note that when dragging multiple rows, only the visible selected rows will be set as dragged data.
====

The following example shows how you can define the allowed drag effect and customize the drag data with the drag data generator.

[source,java]
----
Grid<Person> grid = new Grid<>();
// ...
GridDragSource<Person> dragSource = new GridDragSource<>(grid);

// set allowed effects
dragSource.setEffectAllowed(EffectAllowed.MOVE);

// set the drag data generator
dragSource.setDragDataGenerator(person -> {
    JsonObject data = Json.createObject();
    data.put("name", person.getFirstName() + " " + person.getLastName());
    data.put("city", person.getAddress().getCity());
    return data;
});
----

The _drag data generator_ defines what data should be transferred when a row is dragged and dropped. The generator is executed for every inserted item and returns a `JsonObject` containing the data to be transferred for that item. The generated data is transferred as a JSON array using the HTML5 DataTransfer's data parameter of type `"text"`.
When no generator is set, the whole row data is transferred as JSON, containing all the data generated by the attached [classname]#DataGenerator# instances, such as the row's content and its key.

[NOTE]
====
Note that calling the inherited `setDataTransferText(String data)` method is not supported, since the drag data is set for each row based on the data provided by the generator.
====

The [classname]#GridDragStartEvent# is fired when dragging a row has started, and the [classname]#GridDragEndEvent# when the drag has ended, either in a drop or a cancel.

[source,java]
----
dragSource.addGridDragStartListener(event ->
    // Keep reference to the dragged items
    draggedItems = event.getDraggedItems()
);

// Add drag end listener
dragSource.addGridDragEndListener(event -> {
    // If drop was successful, remove dragged items from source Grid
    if (event.getDropEffect() == DropEffect.MOVE) {
        ((ListDataProvider<Person>) grid.getDataProvider()).getItems()
                .removeAll(draggedItems);
        grid.getDataProvider().refreshAll();

        // Remove reference to dragged items
        draggedItems = null;
    }
});
----

The dragged rows can be accessed from both events using the `getDraggedItems()` method.

==== CSS Style Rules

A drag source Grid's rows have the `v-grid-row-dragsource` and the `v-draggable` style names applied to indicate that the rows are draggable.

=== Grid as a Drop Target

To make a Grid component's rows accept a drop event, apply the [classname]#GridDropTarget# extension to the component. When creating the extension, you need to specify where the transferred data can be dropped on.

[source,java]
----
Grid<Person> grid = new Grid<>();
// ...
GridDropTarget<Person> dropTarget = new GridDropTarget<>(grid, DropMode.BETWEEN);
dropTarget.setDropEffect(DropEffect.MOVE);
----

The _drop mode_ specifies the behaviour of the row when an element is dragged over or dropped onto it. Use `DropMode.ON_TOP` when you want to drop elements on top of a row and `DropMode.BETWEEN` when you want to drop elements between rows.

The [classname]#GridDropEvent# is fired when data is dropped onto one of the Grid's rows. The following example shows how you can insert items into the Grid at the drop position. If the drag source is another Grid, you can access the generated drag data with the event's `getDataTransferText()` method.

[source,java]
----
dropTarget.addGridDropListener(event -> {
    // Accepting dragged items from another Grid in the same UI
    event.getDragSourceExtension().ifPresent(source -> {
        if (source instanceof GridDragSource) {
            // Get the target Grid's items
            ListDataProvider<Person> dataProvider = (ListDataProvider<Person>)
                    event.getComponent().getDataProvider();
            List<Person> items = (List<Person>) dataProvider.getItems();

            // Calculate the target row's index
            int index = items.indexOf(event.getDropTargetRow()) + (
                    event.getDropLocation() == DropLocation.BELOW ? 1 : 0);

            // Add dragged items to the target Grid
            items.addAll(index, draggedItems);
            dataProvider.refreshAll();

            // Show the dropped data
            Notification.show("Dropped row data: " + event.getDataTransferText());
        }
    });
});
----

==== CSS Style Rules

When dragging data over a drop target Grid's row, depending on the drop mode and the mouse position relative to the row, a style name is applied to the row to indicate the drop location.
`v-grid-row-drag-center` indicates ON_TOP, `v-grid-row-drag-top` indicates ABOVE and `v-grid-row-drag-bottom` indicates BELOW locations.

(((range="endofrange", startref="term.advanced.dragndrop")))