利用JavaScript构建OSX应用

转:[译]利用js构建osx应用
英文原文地址javascript

OSX Yosemite引入了js来建立Automation,这使得javascript能够访问nativeOSX类库,我已经深刻研究这块而且编写了一些examples,今天这篇文章会讲解一些基础东西而且一步步的来建立一个小的example app.html

WWDC 2014上面有一个JavaScript for Automation主题,专门解释用javascript来代替applescript建立自动化应用程序,这是个很是激动的事情,使用applescript来构建自动化任务已经存在很长时间了,它的一些语法一直都不是很受欢迎.java

在这个主题上,主持人讲解了object-c bridge,这个是很是酷的东西,能够往javascript中导入任何底层的方法,例如,如用你想用标准的OS X控件来建立gui应用的话,你将要引入cocoa:python

ObjC.import('Cocoa');

Foundation框架就跟它名字同样,提供一些基础模块给osx app,它包含不少类以及接口,好比NSArray,NSURL,NSUserNotification等等,也许你对这些类不熟悉,可是根据字面意思大概就能知道它们的功能,这些类比较经常使用,因此能够直接在app中使用而不须要单独导入它.它是默认就会加载的jquery

我能够这么跟你说,只要是用objective-c或者swift建立的app,用javascript均可以作出来.git

build and example app

注意:你的操做系统要在yosemite开发版7+,下面的例子才能够正常运行github

了解它的最好方法就是实践,下面咱们将要实现一个简单的app(从你电脑上选取一个图片并显示它),效果如图web

clipboard.png

制定一个app.js,包括一个窗口,一个文本标签,一个输入框和一个按钮,对应的class名称就是NSWindow,NSTextField,NSTextFieldNSButton.objective-c

单击选择图片文件按钮,将会显示一个NSOpenPanel,它将会显示一个文件选择窗口,咱们还须要配置这个窗口的文件过滤属性,只能选择.jpg,.png或者.gifchrome

当选择一个图片以后,将会在窗口中显示出来,而且自适应图片大小,窗口设置了一个最小的宽高以避免图片被裁剪

建立一个project

打开Apple Script Editor应用程序,定位到Applications > Utilities,Script Editor并非最好用的编辑器,可是如今有必要用它,它提供了不少的特性用来建立JS OSX APP,而且能够用来编译和运行你的js osx app,它也能够添加些扩展文件像咱们app须要的info.plist文件.我猜也许还有别的编辑器能够作一样的事情,不过目前我尚未找到.

建立一个新的文档,定位到File->New或者使用命令cmd + n,首先要作的就是保存它为Application,定位到File->Save或者使用命令cmd + s,确认保存以前,有两个选项须要注意下,看下图:

clipboard.png

文件格式选择Application,选中Stay open after run handler

若是没有选中Stay open after run handler,打开应用以后会一闪而过,而后自动关闭,这些网上都没有什么教程,只是本身几个小时摸索出来的.

如今应该是作些有意义的时候了

添加下面两行代码到你的编辑器中,而后运行它,定位到Script->Run Application或者opt + cmd + r.

Objc.import('Cocoa');
$.NSLog('hello feenan!');

这时候运行应用什么都没有发生,惟一看到改变的就是全局菜单栏以及dock,由于应用名称以及file,edit并排在菜单栏上,应用图片显示在dock上,这些都表明着应用已经在运行.

那么hello feenan!跑哪去了呢?$符号是什么,是jquery?,咱们先把应用程序退出了,定位到File->Quit或者cmd + q,而后咱们来找找NSLog输出的内容.

打开Console app,定位到Applications > Utilities > Console,任何一个应用程序均可以记录日志到console app中,它跟chrome,firefox,safari中的控制台没多大区别,主要的区别在于你可使用它来调试应用程序代替website

console app中有不少日志信息,你能够在右上角的输入框中输入applet来过滤日志,输入applet到过滤框中以后,回到Script Editor中,再次运行应用程序,使用opt + cmd + r命令,控制台信息以下图

clipboard.png

你是否是看到在控制台中显示hello feenan!了,若是没有的话,退出应用程序再次运行看看,有时候咱们忘记退出应用程序,代码并无再次运行.

$符号是什么呢?

$让你可以访问Objective-C bridge.任什么时候候你须要访问Objective-C其中某个类或者常量,你均可能使用$.foo或者ObjC.foo,后面还有讲到关于使用$的其它一些方法.

Console appNSLog是必不可少的工具,你将会不停的使用它们来调试你的应用程序,想要了解更多的信息,能够点击NSLog example

建立一个窗口

让咱们建立一个能够显示而且有交互的窗口,代码看起来像下面这样

ObjC.import("Cocoa");

var styleMask = $.NSTitledWindowMask | $.NSClosableWindowMask | $.NSMiniaturizableWindowMask;
var windowHeight = 85;
var windowWidth = 600;
var ctrlsHeight = 80;
var minWidth = 400;
var minHeight = 340;
var window = $.NSWindow.alloc.initWithContentRectStyleMaskBackingDefer(
  $.NSMakeRect(0, 0, windowWidth, windowHeight),
  styleMask,
  $.NSBackingStoreBuffered,
  false
);

window.center;
window.title = "Choose and Display Image";
window.makeKeyAndOrderFront(window);

当这些都在合适的位置以后,咱们运行它经过opt + cmd + r,而后咱们能够说,只须要这么点代码就能够启动一个app而且打开一个窗口,并且咱们能够移动,最小化和关闭它.

clipboard.png

若是你跟我同样没有用Objective-C或者Cocoa来建立一个app的话,这些能够看起来有点难以理解,对我来讲,这些方法名称的长度有点难以接受,虽然我喜欢描述性的方法名称,可是像Cocoa这样的仍是太极端了.

看上面的代码其实就是javascript,就跟你编写网站代码同样.

第一行中的styleMask是干么的呢?它提供了一些对窗口属性设置的功能,有标题,关闭按钮,最小化按钮,这些选项都是常量,经过|符号来添加多个,|符号是C里的or操做符,不用去理解它的原理,只要知道它是用来并列多个选项的就足够了.

这里有不少种样式信息,想了解详情的能够点击the docs,NSResizableWindowMask将是接下来要使用的一个样式,能够添加到上面的代码中看看效果

这里还有一些有趣的语法须要你记住,$.NSWindow.alloc调用NSWindow里的alloc方法,可是并无在后面插入(),这种调用方式就跟js里获取属性同样,可是js里调用方法不是这样的,原来,在OSX JS中,假如方法没有参数的时候,是不能在后面加入()的,不然它会出现运行时错误.因此之后当你发现有些事件并无像你指望中发生时就要检查下console里的输出内容了.

下一个要关注的事情是这个超长的方法:

initWithContentRectStyleMaskBackingDefer

让咱们来看看NSWindowdoc上讲解这个方法的,你会发现有一点不一样:

initWithContentRect:styleMask:backing:defer:

上面的描述至关于在Objective-c中建立下面的代码

NSWindow* window [[NSWindow alloc]
  initWithContentRect: NSMakeRect(0, 0, windowWidth, windowHeight)
  styleMask: styleMask,
  backing: NSBackingStoreBuffered
  defer: NO];

注意下上面方法签名中的:,当你想把一个Objective-c中的方法转换成js的方法时,首先须要把:除掉而后把跟在后面的第一个字母大写.当看到方括号[]里有两项是,表明调用一个类或者对象的方法,NSWindow alloc表示调用NSWindowalloc方法,换成js的话,须要在二者以前添加一个.,像NSWindow.alloc这样

我认为剩下的代码足够用来描述建立一个窗口并显示它,我跳过了不少关于这方面的细节说明,这个须要不少时间用来阅读相关文档,不过你能够这些.当你作到显示出一个窗口来已经不错了,让咱们作更多的事情吧

添加控件

窗口里还须要一个标签,一个输入框,一个按钮,我使用NSTextFieldNSButton来建立它,输入下面的代码而后运行你的app

ObjC.import("Cocoa");

var styleMask = $.NSTitledWindowMask | $.NSClosableWindowMask | $.NSMiniaturizableWindowMask;
var windowHeight = 85;
var windowWidth = 600;
var ctrlsHeight = 80;
var minWidth = 400;
var minHeight = 340;
var window = $.NSWindow.alloc.initWithContentRectStyleMaskBackingDefer(
  $.NSMakeRect(0, 0, windowWidth, windowHeight),
  styleMask,
  $.NSBackingStoreBuffered,
  false
);

var textFieldLabel = $.NSTextField.alloc.initWithFrame($.NSMakeRect(25, (windowHeight - 40), 200, 24));
textFieldLabel.stringValue = "Image: (jpg, png, or gif)";
textFieldLabel.drawsBackground = false;
textFieldLabel.editable = false;
textFieldLabel.bezeled = false;
textFieldLabel.selectable = true;

var textField = $.NSTextField.alloc.initWithFrame($.NSMakeRect(25, (windowHeight - 60), 205, 24));
textField.editable = false;

var btn = $.NSButton.alloc.initWithFrame($.NSMakeRect(230, (windowHeight - 62), 150, 25));
btn.title = "Choose an Image...";
btn.bezelStyle = $.NSRoundedBezelStyle;
btn.buttonType = $.NSMomentaryLightButton;

window.contentView.addSubview(textFieldLabel);
window.contentView.addSubview(textField);
window.contentView.addSubview(btn);

window.center;
window.title = "Choose and Display Image";
window.makeKeyAndOrderFront(window);

若是应用跑起来没问题的话,将会看到下面的效果,你能够在输入框中输入内容,按钮点击没有任何效果,不过咱们后面会加些东西上去

看到上面的代码是否是对添加有些疑惑,到底怎么实现的呢?textFieldLabeltextField很是类似,它们都是NSTextField的实例,在这里都是经过类似的方法实现的,当你看到initWithFrameNSMakeRect时,它们是建立ui元素的好方法,NSMakeRect就跟它的名字同样,用来建立有必定大小的方形用给定的位置信息(x, y, width, height).这就至关于建立了Objective-c中的结构体信息,在js中咱们引用它为一个对象或者hash,或者一个dict,拥有本身的键值对.

建立完输入框这后,给它赋一些属性,Cocoa并无单首创建标签的方法,因此这里咱们利用NSTextField,禁用它的编辑属性而且设置的背景样式来模拟标签效果.

假如是正常的输入框的话,其实只须要设置一行代码就能够了

对于按钮咱们使用NSButton类,跟输入框同样,建立它也须要先画一个矩形,不过这里有两个属性须要强调下:bezelStylebuttonType.它们的值就是常量,主要用来控制按钮的渲染以及有什么样式信息,想了解更多的信息,能够点击docs,我也有一些关于不一样样式的按钮例子,example app

最后的事情就是经过addSubview把这些控件添加到咱们的window中去,刚开始我调用window.addSubview(theView),可是并没添加起来,最后发现只能添加其它用NSView建立的实例,我不知道为何会这样,可是想要添加NSWindow建立的实例的话,只能调用window.contentView.addSubview(theView).官方文档上是这样描述的,NSView对象是窗口里最高的访问层次.

给按钮添加代码

当点击按钮的时候,我想显示一个面板出来,上面列出本地的文件信息,作这些以前,让咱们先添加一个日志信息热热身.

javascript中添加事件通常是给对象添加监听,可是在Objective-C中没有这样的概念,在它这里叫消息通信,你得发送一个包含方法名的消息给目标对象,目标对象收到这个包含方法名的消息以后才能决定作什么,也许我说的不是很准确,可是大概就是这个意思

首先咱们要作的就是添加一个target和一个action,target是发送给action的一个对象,也许如今还没什么意义,可是后面我会增长更多的代码,先增长下面的一部分代码,用来更新按钮属性的:

...
btn.target = appDelegate;
btn.action = "btnClickHandler";
...

appDelegatebtnClickHandler还没存在,因此要先建立它们,在下面的代码有提供,而后代码中还增长了注释用来告诉你把这些新的代码添加到何处

ObjC.import("Cocoa");

// New stuff
ObjC.registerSubclass({
  name: "AppDelegate",
  methods: {
    "btnClickHandler": {
      types: ["void", ["id"]],
      implementation: function (sender) {
        $.NSLog("Clicked!");
      }
    }
  }
});

var appDelegate = $.AppDelegate.alloc.init;
// end of new stuff

// Below here is in place already
var textFieldLabel = $.NSTextField.alloc.initWithFrame($.NSMakeRect(25, (windowHeight - 40), 200, 24));
textFieldLabel.stringValue = "Image: (jpg, png, or gif)";
...

运行app,而后在console中查看是否有显示Clicked!,若是显示了,说明代码没问题,不然检查下代码跟文中是否有区别,而后仔细看下console中的错误信息.

Subclassing(子类)

ObjC.registerSubclass是干什么的呢?Subclassing是建立一个子类的方法,它能够继承一个父类.也许这个名称叫的不专业,还请多多包含.registerSubclass须要一个参数,它是一个对象,成员能够包含name,superclass,protocols,properties,methods,我不敢保证这里列出了全部的成员,不过你能够看release notes.

一切看起来挺不错的,可是上面的代码作了什么呢?由于并无写superclass属性,因此默认继承NSObject,它是全部Objective-c里的基类, 设置name属性方便后面咱们用$或者Objc来引用它.

$.AppDelegate.alloc.init会建立一个AppDelegate类实例,须要再次注意的是,allocinit后面并无插入(),由于咱们并无传任何参数.

Subclass methods(子类方法)

能够经过一个字符串内容来建立一个方法,好比上面的btnClickHandler,而后给它提供一个对象参数,成员包括typesimplementation,官方文档上并无说明types数组应该包括什么,可是通过我不断尝试感受它的参数说明应该是这样的:

["return type", ["arg 1 type", "arg 2 type",...]]

btnClickHandler没返回任务东西因此设置return typevoid,它须要一个参数,就是发送的对象,因此这里设置参数名为id,它能够表示任何对象.

想查看整个类型的列表信息,能够点击release notes

implementation是一个普通的函数,在这里面能够写javascript,同时能够访问$符号以及外面定义的变量.

使用protocols的问题

在子类中能够实现Cocoa protocols,可是我发如今你脚本中使用protocols数组,应用程序会中止并且没有任何错误,我写了一些example and explanation来讲明这些问题,有兴趣的能够看一看.

Choosing and displaying images(选择并显示图片)

咱们准备打开一个面板,选择一个图片,而后显示它,更新btnClickHandlerimplementation函数,代码以下

...
implementation: function (sender) {
  var panel = $.NSOpenPanel.openPanel;
  panel.title = "Choose an Image";

  var allowedTypes = ["jpg", "png", "gif"];
  // NOTE: We bridge the JS array to an NSArray here.
  panel.allowedFileTypes = $(allowedTypes);

  if (panel.runModal == $.NSOKButton) {
    // NOTE: panel.URLs is an NSArray not a JS array
    var imagePath = panel.URLs.objectAtIndex(0).path;
    textField.stringValue = imagePath;

    var img = $.NSImage.alloc.initByReferencingFile(imagePath);
    var imgView = $.NSImageView.alloc.initWithFrame(
    $.NSMakeRect(0, windowHeight, img.size.width, img.size.height));

    window.setFrameDisplay(
      $.NSMakeRect(
        0, 0,
        (img.size.width > minWidth) ? img.size.width : minWidth,
        ((img.size.height > minHeight) ? img.size.height : minHeight) + ctrlsHeight
      ),
      true
    );

    imgView.setImage(img);
    window.contentView.addSubview(imgView);
    window.center;
  }
}

首先咱们建立NSOpenPanel一个实例,若是你历来没有打开过文件或者保存操做,那么默认显示的面板是活动文件列表.

我只想打开图片文件,因此须要过滤文件列表,设置allowedFileTypes属性,它是一个NSArray类型,咱们建立一个js数组allowedTypes来给它赋值,可是咱们须要转换成NSArray类型,经过$(allowedTypes),这是bridge桥的另一种用法,能够经过这个方法在jsObjective-c之间进行类型转换,想转换Objective-c类型为js中对应的类型的话,使用$(ObjCThing).js方法.

打开面板经过panel.runModal方法,这个代码会立刻执行,你能够点击取消肯定,而后咱们能够经过返回值来判断你点击是哪一个,当点击肯定返回的是$.NSOKButton.

另外一个须要注意的是panel.URLs,一般咱们访问js中的数组元素是经过[],由于URLSNSArray类型,因此不能经过[]来访问,这里提供了objectAtIndex方法来访问,它跟[]的效果是同样的.

只要咱们获取到图片的URL,那么咱们就能够建立一个NSImage实例,这里有一个专门的方法

initByReferencingFile

跟建立其它ui元素同样, 这里咱们建立一个NSImageView实例来显示图片

咱们想窗口的大小可以自适应图片的大小,可是不能低于最小宽高,为了设置窗口的大小,这里调用setFrameDisplay方法

咱们经过图像视图来包装图片,而后把它添加到window视图中,由于窗口大小改变了,因此须要从新计算居中显示.

Tidbits(花絮)

迄今为止,咱们已经在Script Editor中建立了一个app并用命令opt + cmd + d运行它,也能够像其它app同样,双击它的应用图标来运行.

clipboard.png

你也能够修改app图标的路径,替换/Contents/Resources/applet.icns就行,想访问应用的资源文件,能够右键app选择Show Package Contents就行.

WHY I’M EXCITED(为何我这么激动?)

我对它的潜能感到很是激动,下面是我已经想到的一点观点.当Yosemite正式发布以后,不少人能够坐下来开发原生app了,使用最普通的编程语言,并且不用下载或者安装别的东西,假如你想的话连xcode也能够不用安装,彻底下降了进入app开发的门槛,这简直太疯狂了.

我知道有不少大型应用程序经过脚本是办不到的,我也没有说脚本是建立app的惟一方式,可是咱们可让一些人建立一些小的app为他本身或者别人.当一个团队成天在命令行下工做的不舒服时,咱们能够为它们建立一个gui程序;当须要快速,可视化的建立或者修改配置文件时,也能够为它建立一个小的app.

固然也有其它编程语言能够办到,像pythonruby也能够访问到低层api,而后建立app.只是利用javascript来建立app显的更不同凡响,这简直有点颠覆咱们的思想.这感受就像一些网站敲响了桌面上的门,apple让这个门解锁了,我彻底被它吸引了.

相关文章
相关标签/搜索