This tutorial is a follow-up to an earlier post on HTML5 Drag and Drop in Elm.

The starting point for this tutorial is an unfinished version of the game Towers of Hanoi. The rules of this game are:

The game, old school

There are 3 poles

The left poles has a number of disks on it, varying in size, which the smallest size on top.

You can move disk from one pole to another pole, but only if there is no disk on the other pole, or if the top disk on the other pole is larger than the one you are moving.

Getting started

All documents for this tutorial can be found over on github. To get started:

Clone or download the elm-html5-drag-drop repository over on github.

Open the folder labeled examples .

. The file tutorial-starter.elm is the starting point.

If you just want to look at the finished example, check out tutorial-finished.elm in the same folder. You can read the code and run the example using elm-reactor.

The starting point for now is an unfinished version of the game Towers of Hanoi. Specifics of the game logic are extracted into their own module Hanoi.elm.

The setup of main tutorial-starter.elm app is as follows:

Model

type alias Model =

{ poles : Hanoi.Poles -- alias for List (List Int)

, movingDisk : Maybe Hanoi.Disk

}

The model holds a list of (3) poles, and each pole has a list of up to 7 disks (sorted from small to large). Each disk is an Int , to indicate the size of the disk. In addition, the model holds an indicator if and which disk is currently being moved.

Msg and update

The update function to start with simply returns an unchanged model. There is 1 message placeholder NoOp , that doesn’t do anything and is never invoked. This will be filled in later.

View

The main view function calls a helper function viewPole for each pole in the model. In turn, viewPole calls viewDisk for each disk (if any) on the pole.

If you run the tutorial-starter, you should see this:

The (rather boring) starting point of the Towers of Hanoi game

It’s the starting setup of the game. Right now, the user cannot do anything yet. You will fix that with drag and drop 😉.

Step 1. Make an item draggable

Making an item draggable happens in the view function. If a disk is on top of the set on a pole and if there are other poles where the disk can move to, then the user should be able to drag them.

The code already identifies for each disk if it is on top of a pole and has valid destinations. You only need to add the attributes and events to make these disks draggable.

You will need to modify the lines below, which are in the viewDisk function.

snippet in viewDisk where events and attributes are set

idx is the index of the disk on the pole. If it is 0 then this is the top disk (the only potentially moveable disk on any pole). Hanoi.canMove is a helper function that determines if any other pole can actually take the current disk (if both other poles only have smaller disks on top, it will return False ).

Change the empty attribute list to this:

[ attribute "draggable" "true" ]

This sets the attribute (imported from Html.Attributes) draggable to true. Now, whenever a disk is draggable (and green), your code has also made it a draggable item.

You can now run your code, to see that you can drag the green disk (a ghost image of the disk will appear).

Step 2. Keep track of drags

The starting model already has a flag to keep track of moving disks. To start using it, change your Msg type, so the model can be informed that the user started dragging a disk:

type Msg

= Move Hanoi.Disk

Hanoi.Disk is an imported type alias for Int , the ID of the disk being moved (also used to determine disk size, so lower ID == smaller disk).

Then, add a (custom) event to your view function to fire whenever the user starts dragging, right below the draggable attribute you just set:

[ attribute “draggable” “true”

, onDragStart <| Move disk

]

disk is the ID of the current disk being rendered in viewDisk . onDragStart is an imported custom event handler, invoked when the dragstart event fires.

When you define your own custom handler for the onDragStart event, it could look like this (using the on function from the Html.Events package):

The custom handlers used in this tutorial are defined in a separate module DragEvents.elm. You can look at that file to see how they are defined.

In Firefox, HTML5 drag and drop will only work if you directly set the data of whatever is being dragged. If you are using another browser, you can skip this part.