Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>Nice Question! =D</p> <p>(Note: i've only tested this in Firefox, as it seems is the only browser that supports Harmony proxies.)</p> <p>This seems to work for <strong>missing properties</strong>:</p> <pre><code>class DynamicObject propertyMissingHandler = get: (target, name) -&gt; if name of target target[name] else target.propertyMissing name constructor: -&gt; return new Proxy @, propertyMissingHandler # By default return undefined like a normal JS object. propertyMissing: -&gt; undefined class Repeater extends DynamicObject exited: no propertyMissing: (name) -&gt; if @exited then "#{name.toUpperCase()}!" else name r = new Repeater console.log r.hi # -&gt; hi console.log r.exited # -&gt; false. Doesn't print "exited" ;) r.exited = yes console.log r.omg # -&gt; OMG! </code></pre> <p>Now, it works, but it has a small <strong>big caveat</strong>: it relies on an "other typed" constructor. That is, the constructor of DynamicObject <em>returns</em> something else than the DynamicObject instance (it returns the proxy that wraps the instance). Other typed constructors have subtle and not-so-subtle problems and <a href="https://github.com/jashkenas/coffee-script/issues/2359" rel="nofollow">they are not a very loved feature in the CoffeeScript community</a>. </p> <p>For example, the above works (in CoffeeScript 1.4), but only because the generated constructor for Repeater returns the result of calling the super constructor (and therefore returns the proxy object). If Repeater would have a different constructor, it wouldn't work:</p> <pre><code>class Repeater extends DynamicObject # Innocent looking constructor. constructor: (exited = no) -&gt; @exited = exited propertyMissing: (name) -&gt; if @exited then "#{name.toUpperCase()}!" else name console.log (new Repeater yes).hello # -&gt; undefined :( </code></pre> <p>You have to explicitly return the result of calling the super constructor for it to work:</p> <pre><code> constructor: (exited = no) -&gt; @exited = exited return super </code></pre> <p>So, as other typed constructors are kinda confusing/broken, i'd suggest avoiding them and using a class method to instantiate these objects instead of <code>new</code>:</p> <pre><code>class DynamicObject propertyMissingHandler = get: (target, name) -&gt; if name of target target[name] else target.propertyMissing name # Use create instead of 'new'. @create = (args...) -&gt; instance = new @ args... new Proxy instance, propertyMissingHandler # By default return undefined like a normal JS object. propertyMissing: -&gt; undefined class Repeater extends DynamicObject constructor: (exited = no) -&gt; @exited = exited # No need to worry about 'return' propertyMissing: (name) -&gt; if @exited then "#{name.toUpperCase()}!" else name console.log (Repeater.create yes).hello # -&gt; HELLO! </code></pre> <hr> <p>Now, for <strong>missing methods</strong>, in order to have the same interface as requested in the question, we can do something similar in the proxy handler, but instead of directly calling a special method (propertyMissing) on the target when it has no property with that name, it returns a function, that in turn calls the special method (methodMissing):</p> <pre><code>class DynamicObject2 methodMissingHandler = get: (target, name) -&gt; return target[name] if name of target (args...) -&gt; target.methodMissing name, args # Use this instead of 'new'. @create = (args...) -&gt; instance = new @ args... new Proxy instance, methodMissingHandler # By default behave somewhat similar to normal missing method calls. methodMissing: (name) -&gt; throw new TypeError "#{name} is not a function" class CommandLine extends DynamicObject2 cd: (path) -&gt; # Usually 'cd' is not a program on its own. console.log "Changing path to #{path}" # TODO implement me methodMissing: (name, args) -&gt; command = "#{name} #{args.join ' '}" console.log "Executing command '#{command}'" cl = CommandLine.create() cl.cd '/home/bob/coffee-example' # -&gt; Changing path to /home/bob/coffee-example cl.coffee '-wc', 'example.coffee' # -&gt; Executing command 'coffee -wc example.coffee' cl.rm '-rf', '*.js' # -&gt; Executing command 'rm -rf *.js' </code></pre> <hr> <p>Unfortunately, i couldn't find a way to distinguish property accesses from method calls in the proxy handler so that DynamicObject could be more intelligent and call propertyMissing or methodMissing accordingly (it makes sense though, as a method call is simply a property access followed by a function call).</p> <p>If i had to choose and make DynamicObject as flexible as possible, i'd go with the propertyMissing implementation, as subclasses can choose how they want to implement propertyMissing and treat that missing property as a method or not. The CommandLine example from above implemented in terms of propertyMissing would be:</p> <pre><code>class CommandLine extends DynamicObject cd: (path) -&gt; # Usually 'cd' is not a program on its own. console.log "Changing path to #{path}" # TODO implement me propertyMissing: (name) -&gt; (args...) -&gt; command = "#{name} #{args.join ' '}" console.log "Executing command '#{command}'" </code></pre> <p>And with that, we can now mix Repeaters and CommandLines that inherit from the same base class (how useful! =P):</p> <pre><code>cl = CommandLine.create() r = Repeater.create yes cl.echo r['hello proxies'] # -&gt; Executing command 'echo HELLO PROXIES!' </code></pre>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload