Skip to content

Instantly share code, notes, and snippets.

@japgolly
Created August 11, 2014 07:56
Show Gist options
  • Save japgolly/4bcfdcac208bbb7d925e to your computer and use it in GitHub Desktop.
Save japgolly/4bcfdcac208bbb7d925e to your computer and use it in GitHub Desktop.
Drag-and-Drop using scalajs-react
import org.scalajs.dom
import org.scalajs.dom.console
import scala.scalajs.js
import scalaz.{Equal, State, StateT}
import scalaz.std.option.optionEqual
import scalaz.std.tuple.tuple2Equal
import scalaz.syntax.bind._
import scalaz.effect.IO
import japgolly.scalajs.react._
import japgolly.scalajs.react.vdom.ReactVDom._
import japgolly.scalajs.react.vdom.ReactVDom.all._
import japgolly.scalajs.react.ScalazReact._
object DND {
def move[A](from: A, to: A)(l: List[A])(implicit E: Equal[A]): List[A] = {
console.log(s"DND Move: $from ⇒ $to") // TODO del
l.find(E.equal(from, _)) match {
case None => l
case Some(f) =>
var removedYet = false
l.flatMap(i => {
var x = if (E.equal(from, i)) {removedYet=true; Nil} else i :: Nil
if (E.equal(to, i)) x = if (removedYet) x :+ f else f :: x
x
})
}
}
object Parent {
type PState[A] = Option[(A, Option[A])] // src & target
def initialState[A]: PState[A] = None
implicit def changeFilter[A: Equal] = ChangeFilter.equal[PState[A]]
private def setStateDrop[A](s: Option[A]): State[PState[A], Unit] = State.modify(_.map(x => (x._1, s)))
def dragEnd[A] = State.put[PState[A]](None)
def dragStart[A](a: A) = State.put[PState[A]](Some(a, None))
def dragOver[A](a: A) = setStateDrop(Some(a))
def dragLeave[A] = setStateDrop[A](None)
def cProps[A: Equal](T: ComponentStateFocus[PState[A]], a: A, move: (A,A) => IO[Unit]) =
Child.CProps[A](
T.state match {
case Some((_, Some(d))) => implicitly[Equal[A]].equal(a, d)
case _ => false
},
T _runStateFS dragStart,
T _runStateFS dragOver,
T runStateFS dragLeave,
T runStateFS dragEnd,
T.state match {
case Some((from, Some(to))) => move(from, to)
case _ => IO(())
}
)
}
object Child {
case class CProps[A](dragover: Boolean,
onDragStart: A => IO[Unit],
onDragOver: A => IO[Unit],
onDragLeave: IO[Unit],
onDragEnd: IO[Unit],
onMove: IO[Unit])
type CState = Boolean
type StateIO[A] = StateT[IO, CState, A]
def initialState: CState = false
def dragStart[A](a: A, p: CProps[A]): SyntheticDragEvent[dom.Node] => StateIO[Unit] =
e => StateT(_ => p.onDragStart(a) >> IO {
//console.log(s"dragStart: $p")
e.dataTransfer.setData("text", "managed")
(true, ())
})
def dragEnd[A](p: CProps[A]): StateIO[Unit] =
StateT(_ => p.onDragEnd >> IO(false, ()))
def dragOver[A](a: A, p: CProps[A], s: => CState): SyntheticDragEvent[dom.Node] => IO[Unit] =
e => IO {
//console.log(s"dragOver: dragging = $s / dragover = ${p.dragover}")
if (!s) {
e.preventDefault()
e.dataTransfer.asInstanceOf[js.Dynamic].updateDynamic("dropEffect")("move")
p.onDragOver(a).unsafePerformIO()
}
}
def drop[A](p: CProps[A]): SyntheticDragEvent[dom.Node] => IO[Unit] =
_.preventDefaultIO >> p.onMove
def renderDragHandle[S, A](p: CProps[A], a: A, T: ComponentStateFocus[CState]) =
span(
className := "draghandle"
,draggable := "true"
,onDragStart ~~> T._runStateS(dragStart(a, p))
,onDragEnd ~~> T.runStateS(dragEnd(p))
// onMouseDown={typeof window.isIE9 != 'undefined' && this.handleIE9DragHack}
)("\u2630")
def renderRow[A](p: CProps[A], a: A, T: ComponentStateFocus[CState]) =
div(
classSet("dragging" -> T.state, "dragover" -> p.dragover)
,onDragEnter ~~> preventDefaultIO
,onDragOver ~~> dragOver(a, p, T.state)
,onDragLeave ~~> p.onDragLeave
,onDrop ~~> drop(p)
)
def dndItemComponent[A](r: (A, Tag) => Modifier ) = ReactComponentB[(A, DND.Child.CProps[A])]("DndItem")
.initialState(DND.Child.initialState)
.render(T => {
val (i,p) = T.props
DND.Child.renderRow(p, i, T)(
r(i, DND.Child.renderDragHandle(p, i, T))
)
}).create
}
}
import org.scalajs.dom.console
import scalaz.syntax.bind._
import scalaz.effect.IO
import japgolly.scalajs.react._
import japgolly.scalajs.react.vdom.ReactVDom._
import japgolly.scalajs.react.vdom.ReactVDom.all._
import japgolly.scalajs.react.ScalazReact._
object ReactExamples {
object DragAndDrop {
case class Item(id: Int, name: String)
implicit val itemEq = scalaz.Equal.equalRef[Item]
val RowComp = DND.Child.dndItemComponent[Item](
(i, hnd) => hnd :: raw(s"${i.id} | ${i.name}") :: Nil)
case class ParentState(items: List[Item], dnd: DND.Parent.PState[Item], i: Int)
val Component = ReactComponentB[List[Item]]("DragAndDrop")
.getInitialState(p => ParentState(p, DND.Parent.initialState, 0))
.render(T => {
console.log(s"DND.State = ${T.state}")
val itemsState = T.focusState(_.items)((a, b) => a.copy(items = b))
val dndState = T.focusState(_.dnd)((a, b) => a.copy(dnd = b))
def move(from: Item, to: Item) =
IO{ console.log(s"...Before = ${T.state}") } >>
IO{ itemsState.modState(DND.move(from, to)) } >>
IO{ console.log(s"....After = ${T.state}") }
def renderItem(i: Item) =
li(key := i.id)(RowComp((i, DND.Parent.cProps(dndState, i, move ))))
div(
h1("Drag and Drop"),
ol(T.state.items.map(renderItem).toJsArray)
)
}).create
def demo =
DragAndDrop.Component(List(
DragAndDrop.Item(10, "Ten")
,DragAndDrop.Item(20, "Two Zero")
,DragAndDrop.Item(30, "Firty")
,DragAndDrop.Item(40, "Thorty")
,DragAndDrop.Item(50, "Fipty")
))
}
}
/*
Don't think this is needed
li.placeholder {background: rgb(255,240,120);}
li.placeholder:before {
content: "Drop here";
color: rgb(225,210,90);
}
*/
.dragging {
opacity: 0.5;
background: #fcc;
}
/* .dropzone.dragover */
.dragover {
outline: 1px dashed #5b5c56;
background: #cfc;
}
.draghandle {margin-right: 1ex}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment