JavaScript Cocoa - JSCocoa

KosmicTask Help

JavaScript Cocoa - JSCocoa

JavaScript Cocoa tasks access the Cocoa framework via the JSCocoa bridge.

JSCocoa provides a bridge between the JavaScript and Objective-C programming languages.

Using JSCocoa to Overcome Native JavaScript Limitations

Native JavaScript prohibits access to the operating system.

However, using JSCocoa it is possible to escape the sandbox and access the operating system directly, for example, to permit file access or launch new processes using NSTask.

function kosmicTask() 
{

    // form our image file result path
    // this file will be automatically deleted when the task ends.
    var path = KosmicTaskController.resultFileWithName('capture.png');

    // capture screen shot to file
    var task = NSTask.launchedTaskWithLaunchPath_arguments('/usr/sbin/screenCapture', ['-t', 'png', path]);
    task.waitUntilExit;

    // form result dictionary 
    var result = {kosmicFile: path};

    return result

}

Method Syntax

JSCocoa supports two ways to call methods on Cocoa classes and objects. The first uses JavaScript property syntax. Cocoa method signatures are converted to function names by replacing : (colon) with _ (underscore). The trailing underscore on function names may be omitted.

// define url
var theUrl = NSURL.URLWithString_(theAddress);

// define the request - note trailing underscore omitted
var theRequest = NSMutableURLRequest.requestWithURL_cachePolicy_timeoutInterval(theUrl, NSURLRequestReloadIgnoringLocalCacheData, 60);
theRequest.setHTTPMethod_("GET");

The second way to call Cocoa methods is by using Objective-J syntax. Code syntax is now very similar to native Objective-C, the language normally used to create native applications on OS X.

// get response data
var theResponse = null;
var theResponseData = [NSURLConnection sendSynchronousRequest:theRequest returningResponse:nil error:nil];

// convert data to text
theText = [[NSString alloc] initWithData:theResponseData encoding:NSUTF8StringEncoding]; 

From an Objective-C developer's perspective this second approach is clearly superior. It also makes transcribing Objective-C code from existing source and example code much easier.

String Format Specifier Usage

Many Cocoa classes support variable length argument lists that may include both object references and primitive data types such as numbers and char arrays. JSCocoa converts all such arguments to JavaScript values which may then be referenced as objects. For instance, this means that when using format specifiers with NSString all arguments, regardless of their type, must use the %@ format specifier.

// These FAIL as the %i (and all other non object) format specifier is not supported.
// They will return a large number representing the address of 
// an object that contains the original value.
var theString = [NSString stringWithFormat:"%i", 100];
theString = [NSString stringWithFormat:"%i", [thePostData length]];

// These SUCCEED thanks to the use of the %@ object specifier.
var theString = [NSString stringWithFormat:"%@", 100];
theString = [NSString stringWithFormat:"%@", [thePostData length]];

The rule is simple. Just use the %@ format specifier and no others.

Calling the Task Run Function

KosmicTask JavaScript powered tasks are initiated by calling the Run Function defined in the task Settings. In the following example the task Run Function is set to kosmicTask.

function kosmicTask() 
{
    // return our string result
    return 'Hello, kosmos!';
}

Result Objects

Tasks written in JavaScript Cocoa can return native JavaScript objects or Cocoa objects as task results. The JSCocoa bridge ensures that all objects are coerced as required.

function kosmicTask() 
{
    // return NSArray
    return NSArray.arrayWithArray(["item 1", "item 2"])
}

Result File Handling

KosmicTask supports the returning of file contents within task results.

KosmicTask automatically looks for a kosmicFile record containing file paths within a dictionary type result object. If found, KosmicTask will return the contents of the file or files to the client.

For JavaScript Cocoa powered tasks files are returned as results using the following syntax:

var result = {kosmicFile: path};

A common usage scenario is that a task creates a temporary file (or files) whose contents are then returned to the client. KosmicTask therefore supports automatic temporary file creation and deletion. Temporary files created through KosmicTask are automatically deleted once the parent task has completed.

JavaScript Cocoa powered tasks can create temporary files by calling the KosmicTaskController resultFileWithName function.

function kosmicTask() 
{

    // form our image file result path
    // this file will be automatically deleted when the task ends.
    var path = KosmicTaskController.resultFileWithName('capture.png');

    // capture screen shot to file
    var task = NSTask.launchedTaskWithLaunchPath_arguments('/usr/sbin/screenCapture', ['-t', 'png', path]);
    task.waitUntilExit;

    // form result dictionary 
    var result = {kosmicFile: path};

    return result

}

Logging and Debugging

Diagnostic and logging information can be written to a task's error stream using the log() function. JavaScript does not feature a native logging capability and the function described here is provided by JSCocoa.

// send log value to stderr
log("Goodbye, kosmos");

NSObject Subclassing

JavaScript Cocoa objects can be subclassed directly from Cocoa's NSObject:

// define the Person class
class Person < NSObject
{
    // init method
    - (void)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName
    {
        this.firstName = firstName
        this.lastName = lastName     
        return this          
    }

    // description method
    - (NSString *)description
    {
        return this.firstName + " " + this.lastName
    }
}

This is a unique syntax used to define Cocoa objects and closely mirrors objective-C. A similiar syntax is used in Objective-J and JSTalk. Prior to task execution this syntax is converted to valid JavaScript.

Runloop Management

When an JavaScript Cocoa task is run the script runner is transformed into a fully fledged application complete with a run loop (an instance of NSRunLoop). This enables the task to utilise all the features of the Cocoa framework, many of which depend upon the existence of a run loop in order to perform correctly. As a result of its application based nature a task written in JSCocoa will probably take longer to launch and consume more system resources than a regular JavaScript task.

By default however the JSCocoa task will exit its run loop whenever the task entry point exits unless it is requested to do otherwise. This is the case in the example above.

In order for the task to continue executing it is necessary to tell the task controller object (KosmicTaskController ) to keep the task running after the script entry point function has returned. KosmicTaskController is a predefined class that can be accessed by name within the task script .

To keep the task running call the KosmicTaskController keepTaskRunning static method. The task will then continue to process input events on its runloop until the KosmicTaskController stopTask static method is called. The task will then end and return its result to the client.

// define class
class KosmicTask < NSObject
{
    // start method
    - (void)start
    {
        var selector = @selector(timerFired:)
        this.timer = NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(1, this, selector, nil, true)
        this.count = 0

        // keep the task running after the entry point function exits
        KosmicTaskController.keepTaskRunning      
    }

    // callback method
    - (void)timerFired:(NSTimer *)timer
    {
        this.count++
        if (this.count == 5) {
            timer.invalidate
            KosmicTaskController.stopTask("timer expired " + this.count + " times")
        }
    }
}

// task entry point
function kosmicTask() 
{
    // allocate a task object
    var task = KosmicTask.instance

    // start the task
    task.start
}

System Framework Access

JavaScript Cocoa tasks can access system framework classes using the loadFramework function.

// load the AddressBook framework
loadFramework("AddressBook")

function kosmicTask() 
{
    // access the me object in the shared address book
    var person = ABAddressBook.sharedAddressBook.me

    // get the first name property
    result = person.valueForProperty(kABFirstNameProperty)

    return result
}

Using Pointers

Numerous Cocoa classes take pointers to objects as arguments. JSCocoa uses an outArgument to pass pointers into methods. This syntax supports both basic types, structs and objects.

var scanner = NSScanner.scannerWithString('4.56 123')
var extractedFloat = new outArgument
scanner.scanFloat(extractedFloat)
log(extractedFloat)

If a Cocoa object is returned within an outArgument then its value can be obtained using .outValue.

function kosmicTask() 
{
    // define a scanner
    var theScanner = [NSScanner scannerWithString:"Hello brave new kosmos\n"]

    // scan over Hello
    [theScanner scanString:"Hello" intoString:nil]

    // define out argument
    var outString = new outArgument

    // scan up to end of line and save text in outString
    [theScanner scanUpToString:"\n" intoString:outString]

    // the NSString result is available via the outValue property
    return outString.outValue
}

The JSCocoaController Object

JSCocoa defines a Cocoa controller object that gives access to the JSCocoa execution context and various utilties. The controller object can be accessed explicitly or via the global __jsc__ variable:

function kosmicTask() 
{
    // get the JSCocoa controller.
    // this is also always available as __jsc__ 
    var jsc = JSCocoaController.sharedController

    // define an NSArray
    var nsArray = [NSArray arrayWithObjects:"Hello", "brave", "new", "kosmos"]

    // convert to JS array using local jsc var
    log("JS array using jsc = " + jsc.toJS(nsArray))

    // convert to JS array using global __jsc__ var
    log("JS array using __jsc__ = " + __jsc__.toJS(nsArray))

}

JSCocoa is very flexible about type handling and it is not usual to have to convert objects explictly to JavaScript (but see below).

Serializing Cocoa Objects to JSON

JavaScript serializes native objects using JSON.stringify(object). JSCocoa does not currently support calling stringify on objects that are Cocoa objects or contain Cocoa objects. The solution to this problem is to convert the Cocoa object to JavaScript using .toJS().

function kosmicTask() 
{
    // get the JSCocoa controller.
    // this is also always available as __jsc__ 
    var jsc = JSCocoaController.sharedController

    // define an NSArray instance
    var theNSArray = [NSArray arrayWithObjects:@"Hello", @"brave", @"new", @"kosmos"]
    //return JSON.stringify(theNSArray) // this does not give us a valid JSON representation

    // convert to JS array
    var theJSArray = jsc.toJS(theNSArray)

    // stringify the JS array
    return JSON.stringify(theJSArray)
}

A similar approach using NSDictionary currently fails however and returns a JS object with no defined properties.

function kosmicTask() 
{
    // get the JSCocoa controller.
    // this is also always available as __jsc__ 
    var jsc = JSCocoaController.sharedController

    // define an NSDictionary instance
    var theNSDict = [NSDictionary dictionaryWithObjectsAndKeys:@"Hello", @"brave", @"new", @"kosmos"]
    //return JSON.stringify(theNSDict) // this does not give us a valid JSON representation

    // convert to JS dict
    var theJSDict = jsc.toJS(theNSDict)

    // stringify the JS dict
    // note that at present this generates an empty object as toJS returns a
    // JS array as opposed to a JS object
    return JSON.stringify(theJSDict)
}

.toJs() returns a JS array with the dictionary keys as named properties. JSON.stringify is defined to only return index elements during the serialization of an array object and omits any named parameters - hence the problem. A JS object, such as var myObj = {} is required to correctly serialize named parameters. So at present NSDictionary objects cannot be used to build objects that will be later stringified.

Learn More

One useful way to learn about JSCocoa is to browse the project test suite. These short scripts test most of JSCocoa's functionality and are a great way of learning about the language's functionality and syntax.

Syntax Checking

Syntax checking is provided by JSLint which is included as part of JSCocoa.

Note that JSLint will rewrite the syntax used to implement Cocoa class creation. The rewritten code may be referenced in any build error reports.