Skip to content

Instantly share code, notes, and snippets.

@mike-thompson-day8
Last active February 7, 2018 00:43
Show Gist options
  • Save mike-thompson-day8/09b173d52575383841bd1fb5dbbe7ae1 to your computer and use it in GitHub Desktop.
Save mike-thompson-day8/09b173d52575383841bd1fb5dbbe7ae1 to your computer and use it in GitHub Desktop.
DnD example for re-com
;; What you are looking at: I just picked up this code from one of our apps, and dumped it here unedited.
;; It shows how to do DnD with reagent/re-com, using the terrible HTML5 API.
;; I haven't done anything to shrink this code down. It is a straight cut and paste from live code
;; so it is complicated by many real world, application issues, sorry. It is also very early
;; code for us, on coming to cljs, so it probably contains all sorts of ugly and embarasing attrocities.
;; But it does work.
;; So, as background, when trying to understand what the code is doing, imagine a panel containing
;; a "table" of rows. Each row displays "a single daypart" item (it doesn't matter what a daypart is)
;; and there's a button on the left of each row which will popup an editor panel for that daypart
;; (row), PLUS the rows can be dragged up and down, so as to confer an ordering.
;; View for displaying a daypart row (in a Table)
(defn daypart-row
[]
(let [editor-showing? (reagent/atom false)
show-editor #(reset! editor-showing? true)
mouse-over-row-fn (fn [mouse-over-id row-id dragging-id]
(and
(not @dragging-id)
(= @mouse-over-id row-id)))]
(fn
[daypart mouse-over-row-id dragging-id dragging-over-id]
(let [this-id (:id daypart)
pre-drag-id (:pre-drag-id daypart)]
;; DO NOT put in a key.
;; If you add one, we don't get on-drag-end events for this combination:
;; 1. we drop outside of the table
;; 2. having first dragged across the table.
;;
[h-box
:class "rc-div-table-row"
:align :center
:style {:font-size "small"
:cursor #(@editor-showing? "auto" "move")
:padding "5px"
:line-height "1.428"
:vertical-align "middle"
:border-top "1px solid #ddd"
:background-color (if (= @mouse-over-row-id this-id) "#f5f5f5") ;; bootsrap hover color
;; when we are dragging this item, make it disappear,
;; so the dragged image doesn't have a noisy background
:opacity (if (= @dragging-id this-id) 0)
}
:attr {:on-mouse-over (handler-fn (reset! mouse-over-row-id this-id) {})
:on-mouse-out (handler-fn (reset! mouse-over-row-id nil) {})
;; Drag and Drop and drop
;; Below we use the HTML5 DnD API, which is a flawed and tricky beast:
;; - subtle interactions between event handlers.
;; - subtle effects related to calling 'preventDefault' in handlers or not doing it.
;; Note that for reasons unknown this component can't have a key. See comment further above.
;;
;; This helped: https://www.refheap.com/73581
;;
:draggable (not @editor-showing?) ;; if this is present/true, then browser will allow dragging - don't do it when editor open
:on-drag-start (handler-fn
(reset! dragging-id this-id)
(reset! dragging-over-id this-id)
#(.setData (.-dataTransfer event) "text/plain" "blah")) ;; needed for firefox
:on-drag-over #(.preventDefault %) ;; without this, won't accept a drag
:on-drag-enter (handler-fn
(reset! dragging-over-id pre-drag-id)
(.preventDefault event))
:on-drop (handler-fn
(emit [:dayparts/drag-move @dragging-id @dragging-over-id])
(reset! dragging-over-id nil)
(reset! dragging-id nil)
(reset! mouse-over-row-id nil))
:on-drag-end (handler-fn ;; if drop happens somewhere impossible, then this event is the only way we have to know that the process has stopped.
(reset! dragging-over-id nil)
(reset! dragging-id nil)
(reset! mouse-over-row-id nil))}
:children [[box
:width width-edit-column
:padding "5px"
:justify :center
:child [popover-anchor-wrapper
:position :left-below
:showing? editor-showing?
:anchor [row-button
:md-icon-name ux-common/md-edit-icon-name
:tooltip (if @dragging-id nil "edit")
:mouse-over-row? (mouse-over-row-fn mouse-over-row-id this-id dragging-id)
:on-click show-editor]
:popover [edit-view/popover-wrapper daypart editor-showing? :left-below]]
]
[label :style {:width width-name-column :text-align "center"} :label (:name daypart) :on-click show-editor]
[label :style {:width width-from-column :text-align "center"} :label (mins->time-str (:from daypart)) :on-click show-editor]
[label :style {:width width-to-column :text-align "center"} :label (mins->time-str (:to daypart)) :on-click show-editor]
[label :style {:width width-days-column :text-align "center"} :label (daypart/bool-mask->umask (:mask daypart)) :on-click show-editor]
[box
:width width-sort-column
:padding "5px"
:justify :center
:child [row-button
:md-icon-name ux-common/md-menu-icon-name ; md-import-export
:tooltip (if @dragging-id nil "drag")
:mouse-over-row? (mouse-over-row-fn mouse-over-row-id this-id dragging-id)
:style {:cursor "move"}]]
[h-box
:gap "15px"
:justify :between
:width width-actions-column
:children [[row-button
:md-icon-name ux-common/md-copy-icon-name
:tooltip (if @dragging-id nil "clone")
:mouse-over-row? (mouse-over-row-fn mouse-over-row-id this-id dragging-id)
:on-click #(emit [:dayparts/duplicate-daypart daypart])]
[row-button
:md-icon-name ux-common/md-delete-icon-name
:tooltip (if @dragging-id nil "delete")
:mouse-over-row? (mouse-over-row-fn mouse-over-row-id this-id dragging-id)
:on-click #(emit [:dayparts/delete-daypart this-id])]]]]]))))
;; OVERALL TABLE
(defn dayparts-table
[]
(let [dragging-id (reagent/atom nil) ;; if user is dragging a daypart, then will be the id of the one being dragged, otherwise nil.
dragging-over-id (reagent/atom nil) ;; if not nil, then a daypart has been dragged over the row associated with this daypart
mouse-over-row-id (reagent/atom nil)] ;; the mouse is over this row
(fn
[dayparts]
[v-box
:children [[gap :size "12px"]
[v-box
:class "rc-div-table"
:style {:border "solid 1px #ddd"}
:attr {:on-mouse-out #(reset! mouse-over-row-id nil)}
:children [[h-box
:class "rc-div-table-header"
:style {:font-size "small" :text-align "center"}
:children [[label :style {:width width-edit-column} :label "" :width width-edit-column] ;; Doesn't work unless width is repeated
[label :style {:width width-name-column} :label "Name"]
[label :style {:width width-from-column} :label "From"]
[label :style {:width width-to-column} :label "To"]
[label :style {:width width-days-column} :label "Days"]
[label :style {:width width-sort-column} :label "Sort" :width width-sort-column] ;; Doesn't work unless width is repeated
[label :style {:width width-actions-column} :label "Actions"]
[gap :size "2px"]]]
[v-box
:children (for [daypart (drag-reorder @dayparts @dragging-id @dragging-over-id)]
[daypart-row daypart mouse-over-row-id dragging-id dragging-over-id])]]]
[gap :size "8px"]
]])))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment