Skip to content

Instantly share code, notes, and snippets.

@mhuebert
Last active February 1, 2019 14:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mhuebert/7be2abd9c188bd84c79c05596fd5dbbf to your computer and use it in GitHub Desktop.
Save mhuebert/7be2abd9c188bd84c79c05596fd5dbbf to your computer and use it in GitHub Desktop.
js-interop
{:deps {chia {:git/url "https://github.com/mhuebert/chia"
:sha "885e84e84b39836a9cb12bc3b90880dc25a11482"}}}
(ns XYZ
(:require [chia.util.js-interop :as j])
(def o
(-> #js {}
(j/assoc! :x 1) ;; gobj/set
(j/update :x inc) ;; gobj/get and gobj/set
(j/assoc-in! [:y :z] 0) ;; gobj/getValueByKeys and gobj/set
(j/update-in! [:y :z] inc))) ;; gobj/getValueByKeys and gobj/set
(j/get o :x) ;; gobj/get
(j/get o :x :not-found)
(j/get-in o [:y :z]) ;; gobj/getValueByKeys
(j/get-in o [:y :z] :not-found)
(j/select-keys o [:x :y]) ;; returns new object containing only the provided keys which are contained in `o`
(j/contains? o :x) ;; gobj/containsKey
(j/set! o :a 0) ;; set! uses `unchecked-set`, whereas `assoc!` uses gobj/set
(let [{:keys [x y]} (j/lookup o)]
;; j/lookup wraps `o` to support ILookup, & therefore destructuring
)
(j/push! a 0) ;; JS array push, returns array
(j/unshift! a 0) ;; JS array unshift (prepend), returns array
(j/call o :someFn arg1 arg2) ;; call a fn on `o` with `this` bound to `o`, like o.someFn(arg1, arg2)
;; this one maybe shouldn't stay, or should be renamed
(j/!get o :x) ;; unchecked-get, faster than gobj/get, compiles to javascript property array access
;; using a core function marked as INTERNAL
;; macros namespace
(ns chia.util.js-interop
(:refer-clojure :exclude [get get-in assoc! contains? set!])
(:require [clojure.core :as core]))
(defn wrap-key [k]
(cond
(string? k) k
(keyword? k) (name k)
(symbol? k) (if (= (:tag (meta k)) "String")
k
`(wrap-key ~k))
:else `(wrap-key ~k)))
(defn wrap-path [p]
(if (vector? p)
(mapv wrap-key p)
`(mapv wrap-key ~p)))
(defn- get*
([o k]
(get* o k nil))
([o k not-found]
`(~'goog.object/get ~o ~(wrap-key k) ~not-found)))
(defmacro get
[& args]
(apply get* args))
(defmacro !get [o k]
`(core/unchecked-get ~o ~(wrap-key k)))
(defn- get-in*
([o path]
(get-in* o path nil))
([o path not-found]
`(or ~(if (vector? path)
`(~'goog.object/getValueByKeys ~o ~@(mapv wrap-key path))
`(.apply ~'goog.object/getValueByKeys
nil
(to-array (cons ~o (map wrap-key ~path)))))
~not-found)))
(defmacro get-in
[& args]
(apply get-in* args))
(defn doto-pairs [o f pairs]
`(doto ~o
~@(loop [pairs (partition 2 pairs)
out []]
(if (empty? pairs)
out
(let [[k v] (first pairs)]
(recur (rest pairs)
(conj out (f (wrap-key k) v))))))))
(defmacro assoc! [o & pairs]
(doto-pairs o (fn [k v]
`(~'goog.object/set ~k ~v)) pairs))
(defmacro set! [o & pairs]
(doto-pairs o (fn [k v]
`(~'cljs.core/unchecked-set ~k ~v)) pairs))
(defmacro push! [a v]
`(doto ~a
(~'.push ~v)))
(defmacro unshift! [arr v]
`(doto ~arr
(~'.unshift ~v)))
(defmacro then [promise arglist & body]
`(~'.then ~'^js ~promise
(fn ~arglist ~@body)))
(defn contains? [o k]
`(~'goog.object/containsKey o ~(wrap-key k)))
(defmacro call [o k & args]
`(let [^js f# (get ~o ~k)]
(~'.call f# ~o ~@args)))
;; functions namespace
(ns chia.util.js-interop
(:refer-clojure :exclude [get unchecked-get get-in assoc! assoc-in! update! update-in! select-keys contains?])
(:require [goog.object :as gobj]
[cljs.core :as core])
(:require-macros [chia.util.js-interop :as j]))
(defn wrap-key [k]
(cond-> k
(keyword? k) (name)))
(defn get
([o k]
(j/get o k))
([o k not-found]
(j/get o k not-found)))
(defn get-in
([obj ks]
(.apply gobj/getValueByKeys nil (to-array (cons obj (map wrap-key ks)))))
([obj ks not-found]
(or (get-in obj ks) not-found)))
(defn assoc!
[obj & pairs]
(loop [[k v & more] pairs]
(gobj/set obj (wrap-key k) v)
(if (seq more)
(recur more)
obj)))
(defn assoc-in! [obj ks value]
(assert (> (count ks) 1))
(let [ks (mapv wrap-key ks)
inner-obj (get-in obj (drop-last ks))]
(gobj/set inner-obj (last ks) value)
obj))
(defn set! [obj k val]
(core/unchecked-set obj (wrap-key k) val)
obj)
(defn update! [obj k f & args]
(gobj/set obj (wrap-key k)
(apply f (cons (gobj/get obj (wrap-key k)) args)))
obj)
(defn update-in! [obj ks f & args]
(let [ks (mapv wrap-key ks)
val-at-path (.apply gobj/getValueByKeys nil (to-array (cons obj ks)))]
(assoc-in! obj ks (apply f (cons val-at-path args)))))
(deftype JSLookup [obj]
ILookup
(-lookup [_ k]
(gobj/get obj (wrap-key k)))
(-lookup [_ k not-found]
(gobj/get obj (wrap-key k) not-found))
IDeref
(-deref [o] obj))
(defn lookup
"Allows for single-level destructuring of JS keys
(let [{:keys [id]} (lookup some-obj)]
...)"
[obj]
(JSLookup. obj))
(defn select-keys [o ks]
(reduce (fn [m k]
(let [k (wrap-key k)]
(cond-> m
(gobj/containsKey o k)
(doto
(unchecked-set k
(gobj/get o k nil)))))) #js {} ks))
(defn push! [^js a v]
(doto a
(.push v)))
(defn contains? [o k]
(gobj/containsKey o (wrap-key k)))
(defn call [^js o k & args]
(.apply (j/get o k) o (to-array args)))
(defn !get [o k]
(core/unchecked-get o (wrap-key k)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment