【Javascript】【CEP教程-6】面板与宿主之间的交互
[!(https://s4.ax1x.com/2022/02/28/bKdVPS.md.png)](https://imgtu.com/i/bKdVPS)前面的文章我们讲到了插件面板是运行在CEF这个浏览器运行时下,而需要操作Photoshop的时候,要用到运行在宿主环境下的ExtendScript(以下简称JSX),那这两个独立的运行时之间是如何进行交互通信,且有哪些通信方式的呢,这篇文章我们来扒一扒。
# 面板调用宿主(JS -> JSX)
当我们在面板上点击一个按钮,然后让Photshop执行一个行为,这个时候,就需要JS调用JSX来完成,这个操作通过**CSInterface**提供的方法来完成,该对象提供了一个叫**evalScript**的方法,让我们可以在JS环境下执行一段JSX的代码,这里记得是执行一段代码,它好比原生JS的eval方法,将一串字符串代码执行,如下代码执行,就会在PS上出现一个alert弹窗:
```
var cs = new CSInterface();
cs.evalScript(`alert("Hi there")`);
```
这个函数提供了一个通往JSX世界的入口,但是光执行一段字符串代码显然在实际开发中是不够用的,因为我们的JSX代码通常都会很多很多,不可能全部都塞成一段字符串,于是我们一般将工程里的所有JSX文件都预先加载进来,然后暴露出函数,再通过**evalScript**函数去执行函数调用的过程。比如我在JSX文件里头有一个函数,该函数获取当前图层的名称:
```
// main.jsx
// 获取当前选中图层的名称
function getActiveLayerName() {
var doc = app.activeDocument;
return doc.activeLayer.name;
}
```
main.jsx文件已经有了,它放在我们的插件的jsx目录下
[!(https://s4.ax1x.com/2022/02/28/bKdZ8g.png)](https://imgtu.com/i/bKdZ8g)
那我们如何将这个jsx文件加载进来呢,这里就涉及到前面文章[【CEP教程-3】 CEP插件面板结构介绍](https://www.52pojie.cn/thread-1571274-1-1.html)提到的CSXS/manifest.xml文件中的配置
```
<Resources>
<MainPath>./index.html</MainPath>
<ScriptPath>./jsx/main.jsx</ScriptPath>
<CEFCommandLine>
<Parameter>--enable-nodejs</Parameter>
</CEFCommandLine>
</Resources>
```
里面有一个**ScriptPath**的配置,它就是插件在启动的时候载入的入口JSX文件,按照我们上面的配置,它就能加载**main.jsx**文件了,这就好比我们在做网页开发的时候用script标签引用js文件一样。
```
<script src="./jsx/main.jsx"></script>
```
加载了main.jsx文件之后,我们就可以随时随地调用**getActiveLayerName**函数了
```
cs.evalScript(`getActiveLayerName()`);
```
我们注意到getActiveLayerName函数return返回了当前的图层名称,这个JSX的数据返回,我们可以在evalScript函数的回调方法中获取,该回调函数会将JSX返回结果输出
```
cs.evalScript(`getActiveLayerName()`, function(result) {
console.log(result);
});
```
我们就可以在控制台中看到JSX返回的图层名称
[!(https://s4.ax1x.com/2022/02/28/bKduKs.md.gif)](https://imgtu.com/i/bKduKs)
整体evalScript函数的使用非常简单,一看就会,有几个地方需要注意:
## 1. 参数传递
很多时候,我们需要在JS调用JSX的时候,传递一些参数进去,由于evalScript执行的是字符串,我们需要特别关注参数的拼接
```
// 传递基本类型
cs.evalScript(`foo("abc", 100)`);
```
如果传递的是对象类型参数,调用的时候需要转换成字符串,但是在JSX接收的地方并不需要做parse,系统会自动给你转化成对象
```
// JS
var params = {a: 100, b: "hi"};
cs.evalScript(`foo(${JSON.stringify(params)})`);
// JSX
function foo(params) {
// 不需要做JSON.parse(params);
alert(params.a); // 100
}
```
## 2. 错误
当我们的JSX代码执行错误的时候,evalScript返回的result就会显示 **EvalScript error**. ,如果我们不做处理就会导致面板的交互出现非预期的现象,于是我们可以对返回结果做一下判断
```
cs.evalScript(`fool()`, function(result) {
// EvalScript_ErrMessage 是一个定义在CSInterface.js中的一个常量,值就是EvalScript error.
if (result === EvalScript_ErrMessage) {
console.error(result);
} else {
// do your code stuff
}
});
```
总结: 上面我们介绍了插件面板主动调用宿主完成操作并返回结果的过程,这些过程都是面板主动发起的,那如果某些场景希望宿主主动发起一些动作让面板来处理呢,这里就涉及到插件开发过程中的**事件**了。
# 宿主调用面板 JSX->JS
说到事件,Photoshop && 插件系统提供了许多种事件场景和类型,在上一篇文章[插件面板的样式](https://www.52pojie.cn/thread-1589930-1-1.html)中,我们就第一次说到了事件:为了能够监听Ps的主题发生变化,我们监听**com.adobe.csxs.events.ThemeColorChanged**事件
## 1. CSXSEvent
```
csInterface.addEventListener('com.adobe.csxs.events.ThemeColorChanged', () => {
syncTheme();
});
```
这些CSXSEvent是Photoshop自身派发的事件,它们都是CSXSEvent的一部分,它也给我们提供了自定义事件的能力,这个事件可以在JSX层进行派发,然后在JS层进行监听,这样就可以实现JSX->JS这样一条通道。我们来看看实现过程,CSXSEvent是在一个叫**PlugPlug**插件模块提供的,我们需要先进行加载,然后通过CSXSEvent对象来实现事件派发的过程
```
// JSX
// 加载plugplug模块
try {
var xLib = new ExternalObject("lib:\PlugPlugExternalObject");
} catch (e) {
// do nothing
}
// 事件派发函数
function dispatch(message) {
var eventObj = new CSXSEvent();
eventObj.type = "my_custom_event_type";
eventObj.data = ' ' + message + '';
eventObj.dispatch()
}
// 给JS层发送事件
dispatch('message from jsx');
```
紧接着,和监听Ps主题变化的事件一样,我们在JS层监听事件名称**my_custom_event_type**,然后就可以在控制台看到输出的结果了
```
// JS
csInterface.addEventListener('my_custom_event_type', (data) => {
console.log(data);
});
```
至于**my_custom_event_type**只是一个示例,你可以根据情况自己去定义事件的类型来满足实际需要,这个事件通道非常适合用来打log调试使用,因为JSX本身并没有提供浏览器输出的console.log这样的日志打印功能,那我们就可以通过CSXSEvent来模拟一个
> 严格来说,上面的说法不对,JSX提供了$.write/$.writeln方法用来输出日志,但是只能在ExtendTookit和相关的官方debug环境下才能用
```
// JSX
var console = {
log: function(message) {
var eventObj = new CSXSEvent();
eventObj.type = "console_log_event";
eventObj.data = ' ' + message + '';
eventObj.dispatch();
}
};
console.log('log message from jsx');
```
## 2. CSEvent
上面我们介绍了**CSXSEve**nt,它可以通过宿主进行派发,也支持自定义事件类型,从JSX层进行派发,JS层监听。但是有了这些还不够,自定义的事件类型,只能做一些自己代码内部的通信,很多时候,我们希望监听Ps的一些行为,比如用户选中了一个图层,切换了一个工具等,然后通过这些行为我们继续做一些操作。这就需要用到CSEvent,它是CSInterface里头给JS层提供的一个事件对象,通过它我们可以监听宿主的一些操作事件。
**为了监听这些事件,我们先要派发一个事件!**
对,你没看错,为了监听Ps的事件,我们需要先派发一个注册事件的事件
```
// JS
var appId = csInterface.getApplicationID();
var extId = csInterface.getExtensionID();
var csEvent = new CSEvent();
csEvent.type = 'com.adobe.PhotoshopRegisterEvent';
csEvent.scope = 'APPLICATION';
csEvent.appId = appId;
csEvent.extensionId = extId;
csEvent.data = data; // data 是某个事件的ID,下文详述
csInterface.dispatchEvent(csEvent);
```
上面这个事件的派发,就是告诉宿主,我要开始监听**data**这个事件了,接着我们监听它的回调
```
// JS
csInterface.addEventListener('com.adobe.PhotoshopJSONCallback' + extId, function(result) {
console.log(result);
});
```
> 在CC2015之前的版本,监听的事件名叫做 PhotoshopCallback,之后的版本事件名称叫’com.adobe.PhotoshopJSONCallback’ + extId
这样,当Ps发生指定的动作时候,我们就能收到回调了。
在上面的代码中**data**是个什么?它指明的是我们要关心的宿主发生的事件动作,那它到底填什么呢, 下面是一个监听图层选择事件的代码
```
// JS
csInterface.evalScript(`app.stringIDToTypeID('select')`, function (data) {
var csEvent = new CSEvent();
csEvent.type = 'com.adobe.PhotoshopRegisterEvent';
csEvent.scope = 'APPLICATION';
csEvent.appId = appId;
csEvent.extensionId = extId;
csEvent.data = data;
csInterface.dispatchEvent(csEvent);
});
```
从我们前面学习到的知识告诉我们,JS调用了JSX的函数**app.stringIDToTypeID(‘select’),得到了一个data,它就是我们需要监听的事件ID,把它赋值给csEvent.data**。我们大概可以猜出来select就是选择的意思,代表了选择图层这个操作,那app.stringIDToTypeID又是什么?这里涉及到ActionManager相关的知识,我们后续会专门开篇介绍,这里不做展开,只要记住需要这么做就行了。
代码设置好之后,我们通过切换选择图层,就能在控制台里头打印输出了
[!(https://s4.ax1x.com/2022/02/28/bKBFV1.md.png)](https://imgtu.com/i/bKBFV1)
从上面的日志输出可以看到当前图层选择的一些数据,通过加工处理这个返回的结果,就可以拿到当前图层的信息了。 通过这种机制,我们的示例项目实现了一个实时显示用户选择图层的功能,如下图。[!(https://s4.ax1x.com/2022/02/28/bKde2Q.md.png)](https://imgtu.com/i/bKde2Q)
# 总结
这篇文章介绍了Js和JSX之间的几种交互方式,JS层通过evalScript方法来执行JSX的代码,JSX通过CSXSEvent来派发事件通知JS,JS通过注册Ps的事件来监听Ps的指定行为。
后面的教程,我们会进入JSX核心,讲述JSX DOM和Action Manager的使用,敬请期待~~ 学到东西,谢谢 666,好用的
页:
[1]