binaryage / cljs-oops

ClojureScript macros for convenient native Javascript object access.
Other
350 stars 13 forks source link

How to use cljs-oops to access a constructor? #10

Closed Engelberg closed 7 years ago

Engelberg commented 7 years ago

The following constructor call works great with compilation level "none":

(defonce game (js/Phaser.Game. 1152 648 js/Phaser.AUTO "app" #js{"create" create, "preload" preload}))

I'm trying to convert this, using cljs-oops, so it will work under advanced compilation, for example:

(defonce game (new (oget js/Phaser "Game") 1152 648 (oget js/Phaser "AUTO") "app"
                   #js{"create" create,
                       "preload" preload}))

But this does not properly compile into a constructor call. The output that is produced is below. Notice how the inputs are getting moved outside of the constructor call. How do I do this properly?

if ("undefined" === typeof EK) {
  var EK = (new function() {
    return Phaser.Game;
  })(1152, 648, Phaser.AUTO, "app", {create:OK, preload:FK});
}
darwin commented 7 years ago

I would write:

(def game-ctor (gget "Phaser.Game")) ; this is equivalent of (oget js/Phaser "Game")
(def game (game-ctor. <args>))

This should produce valid code under both compilation modes.

Please note that you cannot write:

(def game ((gget "Phaser.Game"). <args>))

due to cljs compiler limitation.

Engelberg commented 7 years ago

Thanks. I didn't see gget in the README, so I didn't realized it existed and have been struggling with how to interact with things you'd access via js/.

Relatedly, I've been struggling with making all the calls necessary to inherit from a foreign class and override one of the methods.

I ended up with this, using oget:

  (let [particle-class (fn [game x y key frame]
                         (this-as this
                           (.call (oget js/Phaser "Particle") this game x y key frame)))]
    (oset! particle-class "prototype" (js/Object.create (oget js/Phaser "Particle.prototype")))
    (oset! particle-class "prototype.update"
           (fn [] (this-as this
                    ... custom logic goes here ...
                    (ocall js/Phaser "Particle.prototype.update" this))))

I figured this out through a lot of trial and error. I don't completely understand why I seemed to need (.call (oget js/Phaser "Particle") args...) to call the superclass constructor, but I was able to do (ocall js/Phaser "Particle.prototype.update" this) to call the superclass method.

Which of these two calls would correspond to gcall?

Do you know of a cleaner way to do this?

Thanks.

darwin commented 7 years ago

You are correct, I didn't document all features in the readme. Maybe look at the source code, g-macros are no magic.

You code works, maybe I would just structure it a bit differently:

(def particle-methods
  #js {"update"          (fn [] ...)
       "someOtherMethod" (fn [] ...)})

(def particle-prototype (js/Object.create (gget "Phaser.Particle.prototype") particle-methods))

(defn particle-class [my-param game x y key frame]
  (this-as this
    (.call (gget "Phaser.Particle") this game x y key frame)
    (oset! this "myParam" my-param)))

(oset! particle-class "prototype" particle-prototype)

(def my-particle-instance (particle-class. <args>))