3

Although I can get turn a simple js object into a clojure object with something like;

(-> "{a: 2, b: 3}" js* js->clj)

I'm apparently not being able to do so with a particular object, goog.events.BrowserEvent, in a handler function like:

(defn handle-click [e]
  ...
  (-> e .-evt js->clj keys) ;; <-------------
  ...

The function does get applied, but the resulting object doesn't respond to sequence functions like countor first, although I can fetch items using aget. The error message I get, in chrome's console, is;

Uncaught Error: No protocol
method ISeqable.-seq defined for type object: [object Object]

Why is this happening? Shouldn't js->clj work with all objects?

How can I fix this?

Thanks!

2 Answers 2

4

The js->clj only changes something that is exactly a JavaScript object (it is implemented using instance? instead of isa?, and with good reasons), when you pass a descendant of js\Object js->clj returns the same object. aget (and aset) works because it compiles down to the object[field-name] syntax on JavaScript.

You can extend the ISeq protocol (or any other protocol) to the goog.events.BrowserEvent and all functions that works with ISeq will work with goog.events.BrowserEvent. There is a talk by Chris Houser where he showed how to extend a bunch of protocols to a goog Map. I recommend watching the whole talk, but the part that are relevant to your question begins at approximately 14 minutes.

Sign up to request clarification or add additional context in comments.

Comments

1

First, I found out functions in google closure to get the keys and values of an object:

 (defn goog-hash-map [object]
   (zipmap (goog.object/getKeys object) (goog.object/getValues object)))

Then, by studying the source of cljs.core, I realized all I had to do was to extend the IEncodeClojure interface with it:

 (extend-protocol IEncodeClojure
   goog.events.BrowserEvent
   (-js->clj
    ([x {:keys [keywordize-keys] :as options}]
       (let [keyfn (if keywordize-keys keyword str)]
         (zipmap (map keyfn (gobj/getKeys x)) (gobj/getValues x))))
    ([x] (-js->cljs x {:keywordize-keys false}))))

The original code doesn't work on this object, because its type must be exactly Object. I tried to change the comparison function to instance?, ie,

(instance? x js/Object) (into {} (for [k (js-keys x)]
                                      [(keyfn k) (thisfn (aget x k))]))

but that didn't work either, wielding the following error, which made me settle for the previous approach.

Uncaught TypeError: Expecting a function in instanceof check, 
but got function Object() { [native code] }`.

1 Comment

(instance? x js/Object) doesn't work because the first parameter to instance? should be a class. You should use (isa? x js/Object) instead.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.