转:[译]利用js构建osx应用
英文原文地址javascript
OSX Yosemite
引入了js
来建立Automation
,这使得javascript
能够访问native
OSX类库,我已经深刻研究这块而且编写了一些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
注意:你的操做系统要在
yosemite
开发版7+,下面的例子才能够正常运行github
了解它的最好方法就是实践,下面咱们将要实现一个简单的app(从你电脑上选取一个图片并显示它),效果如图web
制定一个app.js
,包括一个窗口,一个文本标签,一个输入框和一个按钮,对应的class
名称就是NSWindow
,NSTextField
,NSTextField
和NSButton
.objective-c
单击选择图片文件按钮,将会显示一个NSOpenPanel
,它将会显示一个文件选择窗口,咱们还须要配置这个窗口的文件过滤属性,只能选择.jpg
,.png
或者.gif
chrome
当选择一个图片以后,将会在窗口中显示出来,而且自适应图片大小,窗口设置了一个最小的宽高以避免图片被裁剪
打开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
,确认保存以前,有两个选项须要注意下,看下图:
文件格式选择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
命令,控制台信息以下图
你是否是看到在控制台中显示hello feenan!
了,若是没有的话,退出应用程序再次运行看看,有时候咱们忘记退出应用程序,代码并无再次运行.
$
让你可以访问Objective-C bridge
.任什么时候候你须要访问Objective-C
其中某个类或者常量,你均可能使用$.foo
或者ObjC.foo
,后面还有讲到关于使用$
的其它一些方法.
Console app
和NSLog
是必不可少的工具,你将会不停的使用它们来调试你的应用程序,想要了解更多的信息,能够点击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
而且打开一个窗口,并且咱们能够移动,最小化和关闭它.
若是你跟我同样没有用Objective-C
或者Cocoa
来建立一个app的话,这些能够看起来有点难以理解,对我来讲,这些方法名称的长度有点难以接受,虽然我喜欢描述性的方法名称,可是像Cocoa
这样的仍是太极端了.
看上面的代码其实就是javascript
,就跟你编写网站代码同样.
第一行中的styleMask
是干么的呢?它提供了一些对窗口属性设置的功能,有标题
,关闭按钮
,最小化按钮
,这些选项都是常量,经过|
符号来添加多个,|
符号是C
里的or
操做符,不用去理解它的原理,只要知道它是用来并列多个选项的就足够了.
这里有不少种样式信息,想了解详情的能够点击the docs,NSResizableWindowMask
将是接下来要使用的一个样式,能够添加到上面的代码中看看效果
这里还有一些有趣的语法须要你记住,$.NSWindow.alloc
调用NSWindow
里的alloc
方法,可是并无在后面插入()
,这种调用方式就跟js
里获取属性同样,可是js
里调用方法不是这样的,原来,在OSX JS
中,假如方法没有参数的时候,是不能在后面加入()
的,不然它会出现运行时错误.因此之后当你发现有些事件并无像你指望中发生时就要检查下console
里的输出内容了.
下一个要关注的事情是这个超长的方法:
initWithContentRectStyleMaskBackingDefer
让咱们来看看NSWindow
的doc上讲解这个方法的,你会发现有一点不一样:
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
表示调用NSWindow
的alloc
方法,换成js
的话,须要在二者以前添加一个.
,像NSWindow.alloc
这样
我认为剩下的代码足够用来描述建立一个窗口并显示它,我跳过了不少关于这方面的细节说明,这个须要不少时间用来阅读相关文档,不过你能够这些.当你作到显示出一个窗口来已经不错了,让咱们作更多的事情吧
窗口里还须要一个标签,一个输入框,一个按钮,我使用NSTextField
和NSButton
来建立它,输入下面的代码而后运行你的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);
若是应用跑起来没问题的话,将会看到下面的效果,你能够在输入框中输入内容,按钮点击没有任何效果,不过咱们后面会加些东西上去
看到上面的代码是否是对添加有些疑惑,到底怎么实现的呢?textFieldLabel
和textField
很是类似,它们都是NSTextField
的实例,在这里都是经过类似的方法实现的,当你看到initWithFrame
和NSMakeRect
时,它们是建立ui
元素的好方法,NSMakeRect
就跟它的名字同样,用来建立有必定大小的方形用给定的位置信息(x, y, width, height)
.这就至关于建立了Objective-c
中的结构体信息,在js
中咱们引用它为一个对象或者hash
,或者一个dict
,拥有本身的键值对.
建立完输入框这后,给它赋一些属性,Cocoa
并无单首创建标签的方法,因此这里咱们利用NSTextField
,禁用它的编辑属性而且设置的背景样式来模拟标签效果.
假如是正常的输入框的话,其实只须要设置一行代码就能够了
对于按钮咱们使用NSButton
类,跟输入框同样,建立它也须要先画一个矩形,不过这里有两个属性须要强调下:bezelStyle
和buttonType
.它们的值就是常量,主要用来控制按钮的渲染以及有什么样式信息,想了解更多的信息,能够点击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"; ...
appDelegate
和btnClickHandler
还没存在,因此要先建立它们,在下面的代码有提供,而后代码中还增长了注释用来告诉你把这些新的代码添加到何处
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
中的错误信息.
ObjC.registerSubclass
是干什么的呢?Subclassing
是建立一个子类的方法,它能够继承一个父类.也许这个名称叫的不专业,还请多多包含.registerSubclass
须要一个参数,它是一个对象,成员能够包含name
,superclass
,protocols
,properties
,methods
,我不敢保证这里列出了全部的成员,不过你能够看release notes.
一切看起来挺不错的,可是上面的代码作了什么呢?由于并无写superclass
属性,因此默认继承NSObject
,它是全部Objective-c
里的基类, 设置name
属性方便后面咱们用$
或者Objc
来引用它.
$.AppDelegate.alloc.init
会建立一个AppDelegate
类实例,须要再次注意的是,alloc
和init
后面并无插入()
,由于咱们并无传任何参数.
能够经过一个字符串内容来建立一个方法,好比上面的btnClickHandler
,而后给它提供一个对象参数,成员包括types
和implementation
,官方文档上并无说明types
数组应该包括什么,可是通过我不断尝试感受它的参数说明应该是这样的:
["return type", ["arg 1 type", "arg 2 type",...]]
btnClickHandler
没返回任务东西因此设置return type
为void
,它须要一个参数,就是发送的对象,因此这里设置参数名为id
,它能够表示任何对象.
想查看整个类型的列表信息,能够点击release notes
implementation
是一个普通的函数,在这里面能够写javascript
,同时能够访问$
符号以及外面定义的变量.
在子类中能够实现Cocoa protocols
,可是我发如今你脚本中使用protocols
数组,应用程序会中止并且没有任何错误,我写了一些example and explanation来讲明这些问题,有兴趣的能够看一看.
咱们准备打开一个面板,选择一个图片,而后显示它,更新btnClickHandler
的implementation
函数,代码以下
... 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
桥的另一种用法,能够经过这个方法在js
和Objective-c
之间进行类型转换,想转换Objective-c
类型为js
中对应的类型的话,使用$(ObjCThing).js
方法.
打开面板经过panel.runModal
方法,这个代码会立刻执行,你能够点击取消
和肯定
,而后咱们能够经过返回值来判断你点击是哪一个,当点击肯定返回的是$.NSOKButton
.
另外一个须要注意的是panel.URLs
,一般咱们访问js
中的数组元素是经过[]
,由于URLS
是NSArray
类型,因此不能经过[]
来访问,这里提供了objectAtIndex
方法来访问,它跟[]
的效果是同样的.
只要咱们获取到图片的URL
,那么咱们就能够建立一个NSImage
实例,这里有一个专门的方法
initByReferencingFile
跟建立其它ui
元素同样, 这里咱们建立一个NSImageView
实例来显示图片
咱们想窗口的大小可以自适应图片的大小,可是不能低于最小宽高,为了设置窗口的大小,这里调用setFrameDisplay
方法
咱们经过图像视图来包装图片,而后把它添加到window
视图中,由于窗口大小改变了,因此须要从新计算居中显示.
迄今为止,咱们已经在Script Editor
中建立了一个app并用命令opt + cmd + d
运行它,也能够像其它app同样,双击它的应用图标来运行.
你也能够修改app图标的路径,替换/Contents/Resources/applet.icns
就行,想访问应用的资源文件,能够右键app选择Show Package Contents
就行.
我对它的潜能感到很是激动,下面是我已经想到的一点观点.当Yosemite
正式发布以后,不少人能够坐下来开发原生app了,使用最普通的编程语言,并且不用下载或者安装别的东西,假如你想的话连xcode
也能够不用安装,彻底下降了进入app
开发的门槛,这简直太疯狂
了.
我知道有不少大型应用程序经过脚本
是办不到的,我也没有说脚本
是建立app的惟一方式,可是咱们可让一些人建立一些小的app为他本身或者别人.当一个团队成天在命令行下工做的不舒服时,咱们能够为它们建立一个gui
程序;当须要快速,可视化的建立或者修改配置文件时,也能够为它建立一个小的app.
固然也有其它编程语言能够办到,像python
和ruby
也能够访问到低层api,而后建立app.只是利用javascript
来建立app显的更不同凡响,这简直有点颠覆咱们的思想.这感受就像一些网站敲响了桌面上的门,apple
让这个门解锁了,我彻底被它吸引了.