├── LICENSE ├── MochaJSDelegate.js ├── MochaJSDelegate.require.js └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Matt Curtis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MochaJSDelegate.js: -------------------------------------------------------------------------------- 1 | // 2 | // MochaJSDelegate.js 3 | // MochaJSDelegate 4 | // 5 | // Created by Matt Curtis 6 | // Copyright (c) 2015. All rights reserved. 7 | // 8 | 9 | var MochaJSDelegate = function(selectorHandlerDict){ 10 | var uniqueClassName = "MochaJSDelegate_DynamicClass_" + NSUUID.UUID().UUIDString(); 11 | 12 | var delegateClassDesc = MOClassDescription.allocateDescriptionForClassWithName_superclass_(uniqueClassName, NSObject); 13 | 14 | delegateClassDesc.registerClass(); 15 | 16 | // Handler storage 17 | 18 | var handlers = {}; 19 | 20 | // Define interface 21 | 22 | this.setHandlerForSelector = function(selectorString, func){ 23 | var handlerHasBeenSet = (selectorString in handlers); 24 | var selector = NSSelectorFromString(selectorString); 25 | 26 | handlers[selectorString] = func; 27 | 28 | if(!handlerHasBeenSet){ 29 | /* 30 | For some reason, Mocha acts weird about arguments: 31 | https://github.com/logancollins/Mocha/issues/28 32 | 33 | We have to basically create a dynamic handler with a likewise dynamic number of predefined arguments. 34 | */ 35 | 36 | var dynamicHandler = function(){ 37 | var functionToCall = handlers[selectorString]; 38 | 39 | if(!functionToCall) return; 40 | 41 | return functionToCall.apply(delegateClassDesc, arguments); 42 | }; 43 | 44 | var args = [], regex = /:/g; 45 | while(match = regex.exec(selectorString)) args.push("arg"+args.length); 46 | 47 | dynamicFunction = eval("(function("+args.join(",")+"){ return dynamicHandler.apply(this, arguments); })"); 48 | 49 | delegateClassDesc.addInstanceMethodWithSelector_function_(selector, dynamicFunction); 50 | } 51 | }; 52 | 53 | this.removeHandlerForSelector = function(selectorString){ 54 | delete handlers[selectorString]; 55 | }; 56 | 57 | this.getHandlerForSelector = function(selectorString){ 58 | return handlers[selectorString]; 59 | }; 60 | 61 | this.getAllHandlers = function(){ 62 | return handlers; 63 | }; 64 | 65 | this.getClass = function(){ 66 | return NSClassFromString(uniqueClassName); 67 | }; 68 | 69 | this.getClassInstance = function(){ 70 | return NSClassFromString(uniqueClassName).new(); 71 | }; 72 | 73 | // Conveience 74 | 75 | if(typeof selectorHandlerDict == "object"){ 76 | for(var selectorString in selectorHandlerDict){ 77 | this.setHandlerForSelector(selectorString, selectorHandlerDict[selectorString]); 78 | } 79 | } 80 | }; 81 | -------------------------------------------------------------------------------- /MochaJSDelegate.require.js: -------------------------------------------------------------------------------- 1 | // 2 | // MochaJSDelegate.js 3 | // MochaJSDelegate 4 | // 5 | // Created by Matt Curtis 6 | // Copyright (c) 2015. All rights reserved. 7 | // 8 | 9 | var MochaJSDelegate = function(selectorHandlerDict){ 10 | var uniqueClassName = "MochaJSDelegate_DynamicClass_" + NSUUID.UUID().UUIDString(); 11 | 12 | var delegateClassDesc = MOClassDescription.allocateDescriptionForClassWithName_superclass_(uniqueClassName, NSObject); 13 | 14 | delegateClassDesc.registerClass(); 15 | 16 | // Handler storage 17 | 18 | var handlers = {}; 19 | 20 | // Define interface 21 | 22 | this.setHandlerForSelector = function(selectorString, func){ 23 | var handlerHasBeenSet = (selectorString in handlers); 24 | var selector = NSSelectorFromString(selectorString); 25 | 26 | handlers[selectorString] = func; 27 | 28 | if(!handlerHasBeenSet){ 29 | /* 30 | For some reason, Mocha acts weird about arguments: 31 | https://github.com/logancollins/Mocha/issues/28 32 | 33 | We have to basically create a dynamic handler with a likewise dynamic number of predefined arguments. 34 | */ 35 | 36 | var dynamicHandler = function(){ 37 | var functionToCall = handlers[selectorString]; 38 | 39 | if(!functionToCall) return; 40 | 41 | return functionToCall.apply(delegateClassDesc, arguments); 42 | }; 43 | 44 | var args = [], regex = /:/g; 45 | while(match = regex.exec(selectorString)) args.push("arg"+args.length); 46 | 47 | dynamicFunction = eval("(function("+args.join(",")+"){ return dynamicHandler.apply(this, arguments); })"); 48 | 49 | delegateClassDesc.addInstanceMethodWithSelector_function_(selector, dynamicFunction); 50 | } 51 | }; 52 | 53 | this.removeHandlerForSelector = function(selectorString){ 54 | delete handlers[selectorString]; 55 | }; 56 | 57 | this.getHandlerForSelector = function(selectorString){ 58 | return handlers[selectorString]; 59 | }; 60 | 61 | this.getAllHandlers = function(){ 62 | return handlers; 63 | }; 64 | 65 | this.getClass = function(){ 66 | return NSClassFromString(uniqueClassName); 67 | }; 68 | 69 | this.getClassInstance = function(){ 70 | return NSClassFromString(uniqueClassName).new(); 71 | }; 72 | 73 | // Conveience 74 | 75 | if(typeof selectorHandlerDict == "object"){ 76 | for(var selectorString in selectorHandlerDict){ 77 | this.setHandlerForSelector(selectorString, selectorHandlerDict[selectorString]); 78 | } 79 | } 80 | }; 81 | 82 | module.exports = MochaJSDelegate; 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MochaJSDelegate 2 | =============== 3 | 4 | What is it? 5 | ----------- 6 | 7 | `MochaJSDelegate` is a way for scripts written in CocoaScript ([Mocha](https://github.com/logancollins/Mocha)) to create delegates for use with native AppKit/UIKit classes. This was originally written for use in Sketch 3+. 8 | 9 | How do I use it? 10 | ---------------- 11 | 12 | The following example will create a `WebView` and set its frame delegate: 13 | 14 | ````javascript 15 | @import 'MochaJSDelegate.js' 16 | 17 | /* 18 | This is so our script's JSContext sticks around, 19 | instead of being destroyed as soon as the current execution block is finished. 20 | */ 21 | 22 | COScript.currentCOScript().setShouldKeepAround_(true); 23 | 24 | // Create a WebView 25 | 26 | var webView = WebView.new(); 27 | 28 | // Create a delegate 29 | 30 | var delegate = new MochaJSDelegate({ 31 | "webView:didFinishLoadForFrame:": (function(webView, webFrame){ 32 | var app = [NSApplication sharedApplication]; 33 | [app displayDialog:"WebView Loaded!" withTitle:"Success!"]; 34 | 35 | COScript.currentCOScript().setShouldKeepAround_(false); 36 | }) 37 | }); 38 | 39 | // (You can also do this:) 40 | 41 | delegate.setHandlerForSelector("webView:didFinishLoadForFrame:", function(webView, webFrame){ 42 | var app = [NSApplication sharedApplication]; 43 | [app displayDialog:"WebView Loaded!" withTitle:"Success!"]; 44 | }); 45 | 46 | // Set WebView's frame load delegate 47 | 48 | webView.setFrameLoadDelegate_(delegate.getClassInstance()); 49 | webView.setMainFrameURL_("http://google.com/"); 50 | ```` 51 | 52 | There are a few other convience methods `MochaJSDelegate` makes available, for more information check out the source. 53 | 54 | How does it work? 55 | ---------------- 56 | 57 | `MochaJSDelegate` leverages [Mocha's](https://github.com/logancollins/Mocha) methods for manipulating the Objective-C runtime. Each delegate creates an `NSObject` subclass, creates method implementations corresponding to your selectors, and ensures that your function is invoked when they are called. (Mocha makes this really easy, once you wade through the source and figure it all out.) 58 | 59 | Notes/Caveats: 60 | ---------- 61 | 62 | - Every time you create a `MochaJSDelegate`, a new `NSObject` subclass is created. I've taken pains to ensure none of them is likely to conflict with any others floating around the runtime, so the only issue one might have is a bunch of classes being created in the runtime. 63 | - This won't work for classes that require their delegates to implement a specific Protocol (and perform a strict check). It's technically possible for the subclass to inherit and implement the necessary Protocol, but that isn't possible at this time for certain [reasons](https://github.com/logancollins/Mocha/issues/25). 64 | - I'm very new to Sketch/CocoaScript/Mocha, so I might be doing something stupid here (correction: *am likely doing something stupid) 65 | --------------------------------------------------------------------------------