Created
August 11, 2014 07:56
-
-
Save japgolly/4bcfdcac208bbb7d925e to your computer and use it in GitHub Desktop.
Drag-and-Drop using scalajs-react
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | |
)) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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