Skip to content

Instantly share code, notes, and snippets.

@stephendeyoung
Last active August 16, 2016 07:54
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save stephendeyoung/7720578 to your computer and use it in GitHub Desktop.
Save stephendeyoung/7720578 to your computer and use it in GitHub Desktop.
ClojureScript function to show a Bootstrap dropdown.
(defn actions [array-of-funcs]
(doseq [func array-of-funcs] (func)))
(defn show-dropdown [e]
(this-as button
(let [$button (js/$ button)]
(.preventDefault e)
(remove-dropdown)
(.addClass (.parents $button ".dropdown") "open"))))
(defn show-dropdown [e]
(this-as button
(let [$button (js/$ button)]
(actions
(array (fn [] (.preventDefault e))
(fn [] (remove-dropdown))
(fn [] (.addClass (.parents $button ".dropdown") "open")))))))
@frankhale
Copy link

I definitely like your 'pure' implementation. This looks much cleaner. I'm working on a Clojurescript app now and I am facing the same issues as you but I had not given it much thought. I think I'm going to borrow your ideas here and implement it in my app.

@mullr
Copy link

mullr commented Dec 1, 2013

I don't see how this is any more pure than the original. There's a layer of indirection in there, but what does it buy you?

A functional approach to this problem may be: take a DOM value as input and generate a modified value as output. Unfortunately I don't know how to do DOM-as-a-value. I suppose you could string them together in the same way though:

(defn prevent-default! [[dom-node event]]
  (.preventDefault event)
  [dom-node event])

(defn remove-dropdown! [[dom-node event]]
  ;; whatever
  [dom-node event])

(defn add-dropdown-class! [[dom-node event] 
  (.addClass (.parents dom-node ".dropdown") "open")
  [dom-node event])

(defn show-dropdown! [e]
  (this-as button
    (-> [(js/$ button) e] prevent-default! remove-dropdown! add-dropdown-class!)))

I'm not sure I like this approach overall. It has the advantage of being explicit about when state is being mutated, and of using a somewhat more idiomatic way of stringing functions together. This is how I would do it if I did have DOM-as-a-value. But the individual handlers are somewhat bulky, and it's somewhat more code than the original version.

If I was reading someone else's code, I would be happiest to see your original 'impure' version, perhaps with a "!" at the end of the function name.

@frankhale
Copy link

Good point @mullr, I think I understood the original posters code being that he wanted to do all the mutations in a separate function keeping the other functions as pure as possible but then again this notion of pure is probably not inline with the functional notion of pure. While I like the idea, your post has given me a time to reflect and I'm not sure how best to solve this, but I do like delegating the mutation to a single function thereby keeping the other functions pretty clean. I haven't implemented this in my own app just yet, perhaps I'll wait to see if a more clean approach surfaces during my own development.

@stephendeyoung
Copy link
Author

Sorry for the delayed response. Thanks for your comments @frankhale and @mullr.

@mullr the reason I feel my pure implementation is pure is because there are no side effects in the show-dropdown function. The side effects (preventing the default action, adding a class etc) are handled by the doseq expression. I thought this might be a slightly cleaner way of doing things.

Unless I've misunderstood your implementation you have side effects in your prevent-default! and add-dropdown-class! functions because you have two statements: one to change state and then another to set the return value of the function.

However, as you've suggested I'm not sure what this approach really buys you. I was interested in how you could write more functional code in CLJS than JS but I've found that you still end up having a lot of side effects in CLJS (mainly because of the DOM API). I thought you could hide this ugliness in my actions function but, to be honest, you could implement something like this in JS anyway.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment