Giter Site home page Giter Site logo

zaboco / elm-draggable Goto Github PK

View Code? Open in Web Editor NEW
65.0 2.0 16.0 546 KB

An easy way to make DOM elements draggable

Home Page: http://package.elm-lang.org/packages/zaboco/elm-draggable/latest

License: BSD 3-Clause "New" or "Revised" License

Elm 93.80% Shell 6.20%
elm dragging

elm-draggable's Introduction

elm-draggable

An easy way to make DOM elements draggable

elm version Build Status

Install

Have elm installed.

elm install zaboco/elm-draggable

Live examples

Usage

This library is meant to be easy to use, by keeping its internal details hidden and only communicating to the parent application by emitting Event messages. So, each time the internals change and something relevant happens (such as "started dragging", "dragged at", etc.), a new message is sent as a Cmd and handled in the main update function. To better understand how this works, see the snippets below and also the working examples.

Basic

In order to make a DOM element draggable, you'll need to:

1. Import this library

import Draggable

2. Define your model

Include:

  • The element's position.
  • The internal Drag state. Note that, for simplicity, the model entry holding this state must be called drag, since the update function below follows this naming convention. A future update could allow using custom field names. Please note that for the sake of example, we are specifying String as the type to tag draggable elements with. If you have only one such element, () might be a better type.
type alias Model =
    { position : ( Int, Int )
    , drag : Draggable.State String
    }

3. Initialize the Drag state and the element's position

initModel : Model
initModel =
    { position = ( 0, 0 )
    , drag = Draggable.init
    }

4. Define the message types that will be handled by your application

  • OnDragBy is for actually updating the position, taking a Draggable.Delta as an argument. Delta is just an alias for a tuple of (Float, Float) and it represents the distance between two consecutive drag points.
  • DragMsg is for handling internal Drag state updates.
type Msg
    = OnDragBy Draggable.Delta
    | DragMsg (Draggable.Msg String)

5. Setup the config used when updating the Drag state

For the simplest case, you only have to provide a handler for onDragBy:

dragConfig : Draggable.Config String Msg
dragConfig =
    Draggable.basicConfig OnDragBy

6. Your update function must handle the messages declared above

  • For OnDragBy, which will be emitted when the user drags the element, the new position will be computed using the Delta (dx, dy)
  • DragMsg will be forwarded to Draggable.update which takes care of both updating the Drag state and sending the appropriate event commands. In order to do that, it receives the dragConfig. As mentioned above, this function assumes that the model has a drag field holding the internal Drag state.
update : Msg -> Model -> ( Model, Cmd Msg )
update msg ({ position } as model) =
    case msg of
        OnDragBy ( dx, dy ) ->
            let
                ( x, y ) =
                    position
            in
                ( { model | position = ( round (toFloat x + dx), round (toFloat y + dy) ) }, Cmd.none )

        DragMsg dragMsg ->
            Draggable.update dragConfig dragMsg model

7. In order to keep track of the mouse events, you must include the relevant subscriptions

subscriptions : Model -> Sub Msg
subscriptions { drag } =
    Draggable.subscriptions DragMsg drag

8. Triggering drag

Inside your view function, you must somehow make the element draggable. You do that by adding a trigger for the mousedown event. You must also specify a key for that element. This can be useful when there are multiple drag targets in the same view.

Of course, you'll also have to style your DOM element such that it reflects its moving position (with top: x; left: y or transform: translate)

view : Model -> Html Msg
view { position } =
    Html.div
        [ Draggable.mouseTrigger "my-element" DragMsg
        -- , Html.Attributes.style (someStyleThatSetsPosition position)
        ]
        [ Html.text "Drag me" ]

For working demos, see the basic example or the examples with multiple targets

9. Triggering on touch

If you want to trigger drags on touch events (i.e. on mobile platforms) as well as mouse events, you need to add touchTriggers to your elements. Building on the previous example, it looks like this.

view : Model -> Html Msg
view { position } =
    Html.div
        [ Draggable.mouseTrigger "my-element" DragMsg
        -- , Html.Attributes.style (someStyleThatSetsPosition position)
        ] ++ (Draggable.touchTriggers "my-element" DragMsg)
        [ Html.text "Drag me" ]

The basic example demonstrates this as well.

Advanced

Custom config

Besides tracking the mouse moves, this library can also track all the other associated events related to dragging. But, before enumerating these events, it's import to note that an element is not considered to be dragging if the mouse was simply clicked (without moving). That allows tracking both click and drag events:

  • "mouse down" + "mouse up" = "click"
  • "mouse down" + "mouse moves" + "mouse up" = "drag"

So, the mouse events are:

  • onMouseDown - on mouse press.
  • onDragStart - on the first mouse move after pressing.
  • onDragBy - on every mouse move.
  • onDragEnd - on releasing the mouse after dragging.
  • onClick - on releasing the mouse without dragging.

All of these events are optional, and can be provided to Draggable.customConfig using an API similar to the one used by VirtualDom.node to specify the Attributes. For example, if we want to handle all the events, we define the config like:

import Draggable
import Draggable.Events exposing (onClick, onDragBy, onDragEnd, onDragStart, onMouseDown)

dragConfig : Draggable.Config String Msg
dragConfig =
    Draggable.customConfig
        [ onDragStart OnDragStart
        , onDragEnd OnDragEnd
        , onDragBy OnDragBy
        , onClick CountClick
        , onMouseDown (SetClicked True)
        ]

Note: If we need to handle mouseup after either a drag or a click, we can use the DOM event handler onMouseUp from Html.Events or Svg.Events.

See the full example

Custom Delta

By default, OnDragBy message will have a Draggable.Delta parameter, which, as we saw, is just an alias for (Float, Float). However, there are situations when we would like some other data type for representing our delta.

Luckily, that's pretty easy using function composition. For example, we can use a Vec2 type from the linear-algebra library, which provides handy function like translate, scale and negate.

import Math.Vector2 as Vector2 exposing (Vec2)

type Msg
    = OnDragBy Vec2
--  | ...

dragConfig : Draggable.Config Msg
dragConfig =
    Draggable.basicConfig (OnDragBy << (\( dx, dy ) -> Vector2.vec2 dx dy))

There is actually an example right for this use-case

Custom mouse trigger

There are cases when we need some additional information (e.g. mouse offset) about the mousedown event which triggers the drag. For these cases, there is an advanced customMouseTrigger which also takes a JSON Decoder for the MouseEvent.

import Json.Decode as Decode exposing (Decoder)

type Msg
    = CustomMouseDown (Draggable.Msg ()) (Float, Float)
--  | ...

update msg model =
    case msg of
        CustomMouseDown dragMsg startPoint ->
            { model | startPoint = startPoint }
                |> Draggable.update dragConfig dragMsg

view { scene } =
    Svg.svg
        [ Draggable.customMouseTrigger () mouseOffsetDecoder CustomMouseDown
--      , ...
        ]
        []

mouseOffsetDecoder : Decoder (Float, Float)
mouseOffsetDecoder =
    Decode.map2 (,)
        (Decode.field "offsetX" Decode.float)
        (Decode.field "offsetY" Decode.float)

Full example

elm-draggable's People

Contributors

abingham avatar crazymykl avatar jhrcek avatar madonnamat avatar paul avatar zaboco avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

elm-draggable's Issues

Add Github Pages with the examples

Need to figure out a way to put all the examples on one page (ideally without duplicating the elm runtime for each one) Currently building each html individually.

customMouseTrigger with multiple targets

This module seems to be just right for my project--very nice! I am running into a problem, however. Is there a way to combine customMouseTrigger (which seems to require Msg () as the 'customEnvelope') with a multiple-target application (using Msg String to identify the dragged objects)?

Make exported types really opaque

  • expose all functions from Config in Draggable
  • remove Config from exposed modules
  • move Config.elm -> Internal/Config.elm & Internal -> Internal/Update.elm
  • add tests for Config.elm

UPDATE

  • Also for Msg and Drag

Clicks are hard to handle with multiple targets

When dealing with multiple targets, the onMouseUp event is triggered before the onClick event.

As far as I understood, the onMouseDownKeyed event is used to register the current target, which may then be used by onDragBy and onClick. The onMouseUp is then used to remove the current target. If onMouseUp is triggered before onClick, however, we can't tell who the target was.

Is this behaving as intended? Should I be using a different pattern to determine the target of the click event?

Looking at Internal.elm (line 70), it seems as if onClick should occur before onDragBy. Maybe the order of the list is being changed somewhere, or maybe Cmd.batch doesn't guarantee an order?

My use case for this was a graph editor. Nodes can be dragged around, and their labels could be edited when clicked.

Publish package

Done after reaching milestone 1.0.0, of course.

  • add trigger for elm-package bump & publish for Travis added a publish script which is supposed to be run manually on master for new releases
  • maybe use elm-package diff to make a CHANGELOG.md will keep that as a note for the first time there will be at least a minor version update

Additional information about the events

When working on a complex use case, I needed more information about the dragStart and click events. I'm writing an editor for directed graphs, which requires handling clicks and drags differently if the shift key was pressed, as well as obtaining the position where a click was made (relative to a parent SVG element).

I'm not sure if this sort of use case is the goal of this library, but elm-draggable is at least a very good starting point to implement it. Therefore, in order to implement the editor, I created a fork of this library and created an alternative API which allows the caller to pass a custom JSON decoder to mouseTrigger, which will use it to extract information from MouseEvents.

While this alternative API may be too complex for simple application, providing something like it alongside the current API might be useful, and shouldn't affect the underlying implementation too much.

Implementing custom click effects on draggable elements

I am trying to add different actions for left, middle and right clicking an element that can also be dragged.
First I tried adding another handler for it using elm-pointer-events, but I realize that shouldn't work.
Next I tried to add onClick to Draggable.customConfig, but that seems to activate only for the left mouse button.

So I assume that something needs to be modified in the library to allow this.

Getting alt key when dragging

I want to change how dragging works depending on if alt is pressed or not. Right now I can use customMouseTrigger to get state of alt key at the start of dragging. However, this doesn't allow reacting to the changes in alt key while dragging.

I could listen keyboard events to and change some flag in the model to detect changes in alt key, but that doesn't feel great idea. As far as I can see if I could add custom decoder to onMouseMove that the library uses to create onDragBy messages this could be done in better way.

So I am wondering does it fit to the library to include this kind of mechanism? I guess the answer is negative, but I thought it would be worth asking.

SVG 100% height not working outside of elm reactor

something elm reactor is doing allows for the svg height 100% to act as expected.

however, when compiling the MultipleTargetsExample into a standalone index.html with elm make (or using elm live), the svg element (grey background) is only about 2.5 boxes in height (150 px). tested on OS X 10.12 with firefox, chrome and safari.

apologies that this isn't an elm-draggable specific bug, but i thought i'd mention it in case there was some trivial fix.

i've googled around for svg specific problems, but svg height specific symptoms supposedly were bugfixed years ago. in the browser debugger the computed values look similar to me; i cannot see what elm reactor does different.

to be sure i wasn't crazy, i rewrote the example to replace the Svg rects with Html divs with position absolute, and it works without issue.

Bad behavior when elements are right-clicked

When elements are right-clicked in Chromium, they follow the mouse after the context menu is closed. This was noticed in Chromium 55.0.2883.87 (64-bit), running on Arch Linux.

Travis integration

  • add integration
  • make a script for tests (both just elm-test & elm-make --docs documentation.txt)

Expose absolute position of mouse when dragging

I had a trouble trying to add constraints such as restricting movement of elements to certain area and restricting elements to a grid.
I eventually overcame both by tracking the absolute position of the initial click with Draggable.customMouseTrigger and then summing up the drag deltas and using these recreated the absolute position of the cursor.

So it would be nice if the library itself exposed the position of the cursor instead of needing to recreate it.

Simplify API

Make the overall API simpler by:

  • replacing Event messages sent as commands with some kind of OutMsg which would not require commands
  • getting the Drag State directly in the messages.
  • removing Config since it will no longer be needed

A (rough) example of the new API:

type Msg
    = UpdateDrag Draggable.State DragEventMsg
    | StartDrag Draggable.State


update : Msg -> Model -> Model
update msg model =
    case msg of
        StartDrag dragStart ->
            { model | drag = dragStart }

        UpdateDrag dragState dragEventMsg ->
            { model | drag = dragState }
                |> updateOnDrag dragEventMsg


updateOnDrag : DragEventMsg -> Model -> Model
updateOnDrag dragEventMsg model =
    case dragEventMsg of
        Start ->
            model

        MoveBy delta ->
            model

        End ->
            model

        Click ->
            model


subscriptions : Model -> Sub Msg
subscriptions { drag } =
    Draggable.eventSubscription UpdateDrag drag


view : Model -> Html Msg
view { xy } =
    Html.div
        [ Draggable.mouseTrigger StartDrag ]
        [ Html.text "Drag me" ]

Improve events

  • remove onMouseDown since it can be handled via {Html,Svg}.Events
  • add key + Position to onDragStart (also ignore initial onDragBy event, since it is batched with onDragStart and order is not guaranteed)
  • add key + Position to onClick

Work with Pointer events as well

I tried the demos in Chrome using the phone emulation mode. This uses pointer events I think and the result was that nothing was draggable. Any thoughts about supporting this?

Possibility to set `VirtualDom.Options`

Hey

I'd like to start with a "thank you" for this library :)

I could really use something like:

customMouseTrigger : VirtualDom.Options -> Decoder a -> (Msg () -> a -> msg) -> VirtualDom.Property msg
customMouseTrigger options customDecoder customEnvelope =
    VirtualDom.onWithOptions "mousedown"
        options
        (Decode.map2 customEnvelope (positionDecoder ()) customDecoder)

I would build it outside of this library, but I don't have access to the Internals module.

Detecting mouse button in onClick

After #51 one can detect which mouse button was used when dragging. However this is not the case for onMouseDown or onClick. How would one go about detecting mouse button in that case?

How to get the element where the drop happened

It doesn't seem like its possible to know "where" the drop occurred (as a particular element), is that correct? I'm trying to implement a table with draggable rows (within the table to re-order, and into other tables on the page). I need to customize what the dragged object looks like, which this library allows, but the HTML5 Drag & Drop API does not. However, the HTML5 "ondrop" event provides a target which is the element that the mouse was over when the drop happened. I'm still fairly new to Elm, but it seems like the onDragEnd event provides no extra data at all, so the only thing I have to go by is the position stored in my model?

If this library doesn't provide that, what's the best way to get that? Subscribe to onMouseMove for the whole window?

Rename some exposed types

  • Drag -> State
  • States: NotDragging, DraggingTentative, Dragging
  • Messages: StartDragging, StopDragging
  • Events: onStartDragging, onStopDragging

2.1.0 broken mouseTrigger / onDragStart

Hello @zaboco!

Thx for nice package. However I have to report that since upgrade to 2.1.0 this stopped working for me.

The root of the problem is that I always receive empty string in my DragStart Msg (set as Msg constructor for onDragStart in customConfig. I'm using mouseTrigger event.

Downgrading back to 2.0.2 solved this issue for me.

Let me know if you need any help with debugging and resolving this. I think I can dig deeper and try to solve this in PR since we are going to use this lib in production.

Cheers!
Marek

Add examples for constrained/custom dragging

  • a draggable box whose movement is constrained, depending on what key is pressed: x -> horizontal, y -> vertical
  • a number selector widget: a number label that changes its value according to the vertical component of dragging (and does not move the element itself). No longer needed to illustrate custom dragging, it will probably be a separate package

Allow manually trigger dragging

I want to be able to create an element with left mouse button and immediately start dragging it. I haven't been able to figure out how to do this with the library, but it seems that the library would need to add support for it.

Easiest and most flexible way to allow this that I can see is to allow triggering dragging manually.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.