From 3c7a47abcd400406752c1f0c7391b6610008cfab Mon Sep 17 00:00:00 2001 From: Lucio D'Alessandro Date: Sun, 28 Mar 2021 16:29:41 +0200 Subject: [PATCH] Add drag and drop support for fieldarray --- CHANGELOG.md | 4 +++ README.md | 8 ++++- examples/fieldarray.cljs | 39 +++++++++++++++++---- project.clj | 2 +- src/fork/core.cljs | 74 ++++++++++++++++++++++++++++++++++++++-- 5 files changed, 117 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7148ea6..858abfc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog All notable changes to this project will be documented in this file. +## [2.4.0] +### Added +- Add support for fieldarray drag and drop (only top level fieldarrays) + ## [2.3.0] ### Added - Add support for nested field arrays diff --git a/README.md b/README.md index f942227..f1b14ce 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ As at this state you must be dying of curiosity, I will dive right into the impl #### In Deps ```clojure -fork {:mvn/version "2.3.0"} +fork {:mvn/version "2.4.0"} ``` or @@ -556,6 +556,12 @@ What follows is a list of the fieldarray available handlers: :fieldarray/set-handle-blur ;; same as the one in main props (see below) :fieldarray/handle-change ;; (handle-change evt idx) :fieldarray/handle-blur ;; (handle-blur evt idx)} +:fieldarray/current-target-idx ;; returns idx of droppable item location (current-target-idx :field-array-key) +:fieldarray/current-dragged-idx ;; returns idx of dragged item (current-dragged-idx :field-array-key) +:fieldarray/next-droppable-target? ;; true if hovered item is > than dragged (next-droppable-target? :field-array-key idx) +:fieldarray/prev-droppable-target? ;; true if hovered item is < than dragged (prev-droppable-target? :field-array-key idx) +:fieldarray/drag-and-drop-handlers ;; all drag and drop handlers +;; i.e. [:div (merge {:class ...} (drag-and-drop-handlers :field-array-key idx))] ``` ### Does Fork do anything else for me? diff --git a/examples/fieldarray.cljs b/examples/fieldarray.cljs index 865e9a7..5b0b606 100644 --- a/examples/fieldarray.cljs +++ b/examples/fieldarray.cljs @@ -4,6 +4,8 @@ [fork.re-frame :as fork] [cljs.pprint :as pprint])) +;; Showing nested field arrays and drag and drop on top level field arrays + (defn field-array-2 [{:keys [normalize-name]} {:fieldarray/keys [fields @@ -45,16 +47,35 @@ remove touched handle-change - handle-blur]}] + handle-blur + current-dragged-idx + next-droppable-target + prev-droppable-target + drag-and-drop-handlers]}] [:div (doall (->> fields (map-indexed (fn [idx field] ^{:key (str name idx)} - [:div {:style {:padding "1em" - :margin "1em" - :background "blue"}} + [:div + (merge + (drag-and-drop-handlers :field-array-1 idx) + {:style (cond-> {:padding "1em" + :margin "1em" + :background "blue"} + + (= idx (current-dragged-idx :field-array-1)) + (assoc :opacity 0.5) + + (next-droppable-target :field-array-1 idx) + (assoc :border-bottom "10px solid black") + + (prev-droppable-target :field-array-1 idx) + (assoc :border-top "10px solid black"))}) + [:div "DRAG ME!"] + [:br] + [:br] [:input {:name (normalize-name :field-array-1/input) :value (get field :field-array-1/input) @@ -84,9 +105,15 @@ [fork/form {:state state :keywordize-keys true :initial-values {:field-array-1 - [{:field-array-1/input "hello" + [{:field-array-1/input "hello1" + :field-array-2 + [{:field-array-2/input "hello nested1"}]} + {:field-array-1/input "hello2" + :field-array-2 + [{:field-array-2/input "hello nested2"}]} + {:field-array-1/input "hello3" :field-array-2 - [{:field-array-2/input "hello nested"}]}]}} + [{:field-array-2/input "hello nested3"}]}]}} (fn [props] [:form.manage-pages [fork/field-array {:props props diff --git a/project.clj b/project.clj index 8e94160..9dbc89a 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject fork "2.3.0" +(defproject fork "2.4.0" :description "Reagent & Re-Frame form library" :url "https://github.com/luciodale/fork" :license {:name "MIT"} diff --git a/src/fork/core.cljs b/src/fork/core.cljs index 60cdf99..9d0cafc 100644 --- a/src/fork/core.cljs +++ b/src/fork/core.cljs @@ -7,6 +7,10 @@ [coll pos] (vec (concat (subvec coll 0 pos) (subvec coll (inc pos))))) +(defn vec-insert-at + [coll pos element] + (vec (concat (subvec coll 0 pos) [element] (subvec coll pos)))) + (defn touched [state k] (or (:attempted-submissions @state) @@ -261,6 +265,40 @@ (or (:attempted-submissions @state) (get (:touched @state) (conj vec-field-array-key idx input-key)))) +(defn handle-drag-start + [state k idx] + (swap! state (fn [old-state] + (-> old-state + (dissoc :drag-and-drop) + (assoc-in [:drag-and-drop k :idx-of-item-being-dragged] idx))))) + +(defn handle-drag-end + [state] + (swap! state dissoc :drag-and-drop)) + +(defn handle-drag-over [e] (.preventDefault e)) + +(defn handle-drag-enter + [state k idx] + (swap! state assoc-in [:drag-and-drop k :idx-of-element-droppable-location] idx)) + +(defn handle-drop + [state k vec-field-array-key] + (let [dragged-idx (get-in @state [:drag-and-drop k :idx-of-item-being-dragged]) + dropped-idx (get-in @state [:drag-and-drop k :idx-of-element-droppable-location])] + (swap! state update-in (cons :values vec-field-array-key) + #(-> % + (vec-remove dragged-idx) + (vec-insert-at dropped-idx (get % dragged-idx)))))) + +(defn current-target-idx + [state k] + (some-> @state :drag-and-drop k :idx-of-element-droppable-location)) + +(defn current-dragged-idx + [state k] + (some-> @state :drag-and-drop k :idx-of-item-being-dragged)) + (defn field-array [props _] (let [state (get-in props [:props :state]) @@ -282,7 +320,34 @@ (fn [m] (fieldarray-insert state vec-field-array-key m)) :touched (fn [idx input-key] (fieldarray-touched - state vec-field-array-key idx input-key))}] + state vec-field-array-key idx input-key)) + :current-target-idx + (fn [k] (current-target-idx state k)) + :current-dragged-idx + (fn [k] (current-dragged-idx state k)) + :next-droppable-target? + (fn [k idx] + (and (= idx (current-target-idx state k)) + (> idx (current-dragged-idx state k)))) + :prev-droppable-target? + (fn [k idx] + (and (= idx (current-target-idx state k)) + (< idx (current-dragged-idx state k)))) + :drag-and-drop-handlers + (fn [k idx] + (when (or (nil? (:drag-and-drop @state)) + (current-dragged-idx state k)) + {:draggable true + :on-drag-start + (fn [_] (handle-drag-start state k idx)) + :on-drag-end + (fn [_] (handle-drag-end state)) + :on-drag-over + (fn [evt] (handle-drag-over evt)) + :on-drag-enter + (fn [_] (handle-drag-enter state k idx)) + :on-drop + (fn [_] (handle-drop state k vec-field-array-key))}))}] (fn [{:keys [props] :as args} component] (let [fields (get-in (:values props) vec-field-array-key)] [component props @@ -295,4 +360,9 @@ :fieldarray/set-handle-change (:set-handle-change handlers) :fieldarray/set-handle-blur (:set-handle-blur handlers) :fieldarray/handle-change (:handle-change handlers) - :fieldarray/handle-blur (:handle-blur handlers)}])))) + :fieldarray/handle-blur (:handle-blur handlers) + :fieldarray/current-target-idx (:current-target-idx handlers) + :fieldarray/current-dragged-idx (:current-dragged-idx handlers) + :fieldarray/next-droppable-target? (:next-droppable-target? handlers) + :fieldarray/prev-droppable-target? (:prev-droppable-target? handlers) + :fieldarray/drag-and-drop-handlers (:drag-and-drop-handlers handlers)}]))))