day8 / re-frame

A ClojureScript framework for building user interfaces, leveraging React
http://day8.github.io/re-frame/
MIT License
5.41k stars 717 forks source link

(un) registration #22

Closed christianromney closed 9 years ago

christianromney commented 9 years ago

This is very interesting work. One thing I was thinking about was dynamic registration / unregistration of event handlers. How do you feel about this? Would you take a pull request for unregistration?

mike-thompson-day8 commented 9 years ago

In your usercase, do you want to re-register (replace the handler for an existing registration), or genuinely remove a registration completely.

BTW, there are a couple of other variations on registration too. Like, for example, having multiple handlers registered for the one event, perhaps with some ordering amoung them (Flux has this notion of certain handlers waiting for other handlers to run, etc). I've resisted them all so far because I didn't have a usecase which is why I'm curious about yours.

christianromney commented 9 years ago

This is just me thinking aloud, but I'm curious what you think about this idea. It looks like Clojure, but consider it pseudo-code—I've not even run it let alone tested it. You won't offend me if you think it sucks... :)

(def ^:private event-chan (chan)) 
;; core.async pub/sub (publication part)
(def ^:private event-publisher (pub event-chan :event)) 

(defn register 
  "Subscribes to certain events"
  ([topic handler-fn]
   (let [sub-ch (chan)]
     ;; core.async pub/sub (subscription part)
     (sub event-publisher topic sub-ch)
     (go-loop []
       (let [event-data (:data (<! sub-ch))]
         (handler-fn app-db event-data)
         recur))
     sub-ch)
   ([topic middleware handler-fn]
    (let  [mware (if (vector? middleware)
                   (apply comp middleware)   ;; compose the vector of middleware
                   middleware)
           hander-fn (mware handler-fn)]
      (register topic hander-fn))))

(defn unregister
  "Unsubscribes from events"
    [topic ch]
    (unsub event-publisher topic ch))

(defn dispatch
  "reagent components use this function to send events.
  Usage example:
  (dispatch [:delete-item 42])"
  [[topic & data]]
  (when topic
    (put! event-chan {:event topic :data data})))
  nil)  

;;;; usage

(defn foobar
  "A pure event handler"
  [app-data event-data]
  ;; do something useful
  )

(def get-data-sub (register :get-data [pure] foobar)

(unregister :get-data get-data-sub)

;; if you never need unregister, just use as normal...
(register :get-data (fn [app-db event-data] ...)

For the sake of clarity, I've omitted the flush-dom metadata handling. What I like about this idea is that it uses core.async's native pub/sub capabilities which help us avoid reinventing the pub/sub mechanism and gives us multiple topic subscribers and unsubscription out of the box.

We also collapse the router loop and handle function and eliminate a nil check in dispatch since we're always putting a data structure on the channel. It's possible (likely?!) I've overlooked something, but I did want to share my thoughts.

Cheers!

christianromney commented 9 years ago

It's been fun watching the commits stream in over the last couple of days. :) In that time I've whipped up a working example of the idea sketched out above here: https://bitbucket.org/pointslope/restated/overview

I purposely changed some names so as not to really distract from the ideas. Cheers...

mike-thompson-day8 commented 9 years ago

Hi Christian, I've had a quick look, but I'll need to go back and read further. Initial thoughts:

When I was working on re-frame, I was aware that the dispatch/emit area would be impossible to get right for everyone. Small changes in requirements trigger quite different designs. I did wonder how I should make the dispatch/routing more pluggable, so that different approaches could be used within the framework.

So rather than adopting any alternative approaches which might offer feature A or B, I'd prefer a solution that allowed different approaches to be plugged in. But I'm not likely to cycle back around to this until I get v0.3.0 pushed, which might be another week or so.

Small point, but I much prefer the name emit to dispatch. There are too many dispatches in this world -- there's one in core.async and each time it shows up in a stacktrace, it confuses me. There is also dispatches in Flux which do something slightly different, etc. I really want to use emit now. Look what breakage you have wrought!!!

christianromney commented 9 years ago

Thanks for your considerate reply; you've given me much to think about. re-frame is very well thought through and your README and documentation are excellently written. I look forward to watching the evolution of your project, and I thank you for taking the time to review my ideas.

My best, Christian

mike-thompson-day8 commented 9 years ago

I've quickly put together this page of information which is related: https://github.com/Day8/re-frame/wiki/Alternative-dispatch%2C-routing-%26-handling

It certainly isn't a solution to the un-registration need you pose, but a way of thinking about re-frame as a base.

I'll probably add to that document over time.