module Graphics.UI.Threepenny.DragNDrop (
    -- * Synopsis
    -- | API for handling drag and drop operations.
    -- 
    -- See the documentation below for details on the drag and drop model.
    -- 
    -- WARNING: Events in this module may not behave as expected.
    -- The model is currently implemented in terms of HTML 5 drag and drop,
    -- but unfortunately,
    -- the HTML 5 specification for drag and drop is horrible and
    -- browser implementations are buggy.
    
    -- * Documentation
    draggable, droppable, dragData,
    DragData,
    drag, dragStart, dragEnd, drop, dragEnter, dragLeave, dragOver,
    ) where

import Prelude hiding (drop)
import Control.Monad
import Graphics.UI.Threepenny.Core

{-----------------------------------------------------------------------------
    Attributes
------------------------------------------------------------------------------}
-- | Enable or disable whether the element can be dragged by the user.
--
-- An element with draggable set to 'True' will receive
-- 'drag', 'dragStart' and 'dragEnd' events.
draggable :: WriteAttr Element Bool
draggable = mkWriteAttr set
    where
    set v = set' (attr "draggable") $ if v then "true" else "false"

-- | Set the data that is transferred when dragging this element.
dragData :: WriteAttr Element DragData
dragData = mkWriteAttr set
    where
    set v = set' (attr "ondragstart") $
        "event.dataTransfer.setData('dragData', '" ++ v ++ "')"

-- | Enable or disable whether the element accepts drops.
--
-- An element with 'droppable' set to 'True' will receive
-- 'drop', 'dragOver', 'dragEnter' and 'dragLeave' events.
--
-- Child elements of a 'droppable' element may also be 'droppable'.
-- When dragging something over an element, the closest ancestor element
-- that is 'droppable' will be the target and receive corresponding
-- events.
droppable :: WriteAttr Element Bool
droppable = mkWriteAttr enable
    where
    enable v = void . if v then allowDrop else blockDrop
    allowDrop el =
        element el
            # set (attr "ondragover") "event.preventDefault()"
            # set (attr "ondrop"    ) "event.preventDefault()"
    blockDrop el =
        element el
            # set (attr "ondragover") ""
            # set (attr "ondrop"    ) ""

{-----------------------------------------------------------------------------
    Events
------------------------------------------------------------------------------}
-- | Data carried by a dragged element. 
--
-- FIXME: Empty data is currently encoded by the empty String.
-- Change this to 'Maybe String' instead.
type DragData = String

withDragData = fmap (extract . unsafeFromJSON)
    where
    extract [s] = s
    extract _   = ""

-- | Occurs periodically while the element is being dragged around.
drag :: Element -> Event DragData
drag = withDragData . domEvent "drag"

-- | Dragging the element starts.
dragStart :: Element -> Event DragData
dragStart = withDragData . domEvent "dragstart"

-- | Dragging the element ends.
--
-- WARNING: This event can occur both before and after a corresponding 'drop' event.
dragEnd :: Element -> Event DragData
dragEnd = withDragData . domEvent "dragend"

-- | The element is now the current target element for a 'drop'.
-- 
-- WARNING: This element is buggy when moving the mouse over child elements.
dragEnter :: Element -> Event DragData
dragEnter = withDragData . domEvent "dragenter"

-- | Occurs periodically while the element is the current target element.
dragOver :: Element -> Event DragData
dragOver = withDragData . domEvent "dragover"

-- | The element is no longer the current target element for a 'drop'.
--
-- WARNING: This event is also fired when the mouse is moved over a child element.
dragLeave :: Element -> Event DragData
dragLeave = withDragData . domEvent "dragleave"

-- | The drag and drop operation is being completed on this element.
drop :: Element -> Event DragData
drop = withDragData . domEvent "drop"