appcelerator/hyperloop — pull request #147 by infosia (2013-11-04 08:32:52+00:00)
Title: Add ability to set custom class options

# Add ability to set custom class options

Add ability to set options for custom class to generate code flexibly. It is meant to be used for "setProperty callback" which is invoked before property is set to the object. Hyperloop should generate setProperty callback for custom classes only when user needs it, because it harms performance generally.

## The Problem

Let's imagine we want to wrap HTML5 Canvas API by iOS Quartz 2D API. Note that Canvas API uses properties as well as function.

```javascript
// draw line (width=1)
context.rect(0, 0, 10, 10);

// draw line (width=5)
canvas.lineWidth = 5;
context.rect(0, 0, 10, 10);

// draw line (width=10)
canvas.lineWidth = 10;
context.rect(0, 0, 10, 10);
```

Hyperloop JavaScript wrapper can wrap functions easily like below, but it can *not* wrap properties because JavaScript doesn't have a efficient way to wrap properties.

```javascript
// wrap Canvas.rect() by Quartz 2D CGContext
Canvas.proptotype.rect = function(x,y,w,h) {
CGContextAddRect(this.context, CGRectMake(x, y, w, h));
}
//
// But how to wrap 'lineWidth' property? It ends up with function
//
Canvas.proptotype.lineWidth = function(w) {
CGContextLineWidth(this.context, w);
}
```

Thus resulting code should be look like below. It breaks the API.

```javascript
context.rect(0, 0, 10, 10);
canvas.lineWidth(5); // we wanted to use property for this
```

## Solution

Add abilities to set additional 'options' to @class annotation and enable 'setProperty callback' event for the object. It tells generator to set custom property for specific classes. It could be 4th argument of @class.

```javascript
@class('MyCanvasClass', NSObject, [], [], [
{
name: 'enablePropertySetCallback',
value: true
}
]
);
```

It enables propertySetCallback in this case. In the hyperloop generator template, we are able to detect if specific property is set and then generate appropriate code for it.

```
<% if (options.enablePropertySetCallback) { -%>
ClassDefinitionFor<%-className%>.setProperty = SetPropertyCallbackFor<%-className%>;
<% } -%>
```

Because now javascript object has ability to detect property changes, it is easy to wrap properties with native API like below.

```javascript
canvas.propertySetCallback = function(name, oldvalue, newvalue, obj) {
if (name == "lineWidth") {
CGContextLineWidth(this.context, newvalue);
}
}
```

Basic concept here should be pretty much similar to Titanium module's [KrollProxyListener.propertyChanged](http://builds.appcelerator.com.s3.amazonaws.com/module-apidoc/2.0.0/android/org/appcelerator/kroll/KrollProxyListener.html#propertyChanged%28java.lang.String,%20java.lang.Object,%20java.lang.Object,%20org.appcelerator.kroll.KrollProxy%29).

By using property callback, final resulting code should look like below. It realizes natural canvas API.

```javascript
canvas.lineWidth = 5;
context.rect(0, 0, 10, 10);
```

## Notes

SetProperty callback should be used carefully because of following limitation that comes from basic behavior of JavaScriptCore's JSObjectSetPropertyCallback.

- The propertySetCallback is fired *before* property is set to the object.
- Updating object property in the callback should cause infinite loop.