Fork me on GitHub
#carry
<
2016-08-16
>
kauko06:08:35

how do you handle multiple controllers btw @metametadata ?

kauko06:08:35

if a component dispatches signals and the intended controller may be any one of N.. what do you do? Does a "parent" controller need to know the "child" controllers?

metametadata09:08:18

Hey @kauko! Could you please give an example of "multiple controllers"? Because, stricly speaking, for each app there's only a single controller.

metametadata09:08:49

Maybe you mean how one can define this controller using more smaller ones (i.e. like redux does with reducers)?

kauko10:08:46

Just need some way to split the controller into multiple files

kauko10:08:02

otherwise any app of non-trivial size will have a 2k+ loc controller 😛

kauko10:08:21

(well, maybe not, but splitting is a must IMO)

metametadata10:08:06

Yeah, I agree. It's something I thought about but didn't prescribe in the framework yet.

metametadata10:08:48

There could be different solutions. On top of my head: - define controller as multimethod, this way you can scatter signal processing across many files easily; but there may be problems with pattern matching now?.. - "Flux way": ensemble a controller from smaller controllers; when a "parent" receives a signal it sends the signals to all the children; this is also how Redux works if I'm not mistaken; - the "chain of responsibility"-kinda approach: ensemble controller from the smaller controllers; when it receives a signal it first sends it to the first child, if it wasn't handled - sends it to the second child, etc. until someone is able to handle the signal

metametadata10:08:39

I recall I liked the last approach the most

metametadata10:08:30

because in this case you can easily throw an exception if no handler was found for a signal. Redux in such situations silently ignores the error.

metametadata10:08:58

And here's a snippet for my liked approach:

;; spec

  ; only one controller will handle a signal
   :control    (ui/either #{app-control
                            router/control
                            files-logic/control
                            settings-logic/control})
(ns frontend.settings.logic
  (:require [cljs.core.match :refer-macros [match]]))

(defn control
  [model signal dispatch-signal dispatch-action]
  (match signal
         [:on-select-theme id] (dispatch-action [:select-theme id])
         ; ...
         :else :bypass)) ; <-------------------------------------------- let other controller handle the unknown signal
(defn either
  "Returns a function of any args which calls functions one by one while :bypass is returned."
  [fns]
  (fn either-fn [& args]
    (loop [f (first fns)
           next-fns (rest fns)]
      (when (nil? f)
        (throw (ex-info (str "No function was able to handle passed args: " (pr-str args))
                        {})))

      (let [result (apply f args)]
        (if (= result :bypass)
          (recur (first next-fns) (rest next-fns))
          result)))))

kauko10:08:05

Yeah, I think that could work. It's important that the ability to give components custom controllers is retained, for testing for example. So avoid some register-controller functions that add a controller to some global thingy

kauko10:08:50

and when I work on my view component and add some new signals, I want to add it to one place, not several places

metametadata10:08:05

Sounds reasonable!

metametadata10:08:51

this either function can be used to split a reconciler as well