客户端脚本在用户计算机浏览器的 JavaScript 运行时中运行。它们非常适合处理界面事件以及更改 DOM 元素和微件属性。
客户端脚本还可以通过应用制作工具 API 触发与服务器的交互。例如,客户端脚本可以提取和修改数据库中的数据,或者调用服务器脚本。
异步操作和回调函数
客户端脚本可以在应用制作工具中异步运行,以保持应用用户界面及时响应。在某些浏览器应用中,当操作需要等待某一外部资源时,整个应用都会随之停滞。界面亦会等待,并且没有响应。
使用异步代码时,界面和其他应用操作会保持及时响应,但您的脚本可能不会按编写的顺序运行。要在异步操作完成后运行脚本(例如处理操作结果的代码),请使用回调函数。
回调函数是在操作完成时运行的客户端脚本。您可以指定操作成功时运行的函数,或者指定针对成功和失败场景运行不同函数的对象。
成功专用回调函数
如果您添加成功专用回调函数,则仅在操作成功时该函数才会执行。如果操作失败,则没有任何反应。举个简单的例子,您可以按照下列方式使用 createItem () 异步创建记录并发送有关的提醒:
widget.datasource.createItem(function (record) {
alert('Record with ID ' + record.id + ' was created in the database.');
});
成功和失败回调函数
以下是上述示例的扩展,使用这种方法可以在失败时得到通知并执行错误报告、清理或回滚。
widget.datasource.createItem({
success: function (record) {
alert('Record with ID ' + record.id + ' was created in the database.'); // executes if record was created
},
failure: function (error) {
console.info("No new record for you!"); // executes if record wasn't created
}
});
无回调函数
如果省略回调或传递 null 作为回调,则代码将继续,无需等待操作完成。例如:
widget.datasource.createItem();
console.info("warning, record is probably not yet created!");
问题排查
以下是排查客户端脚本以查找和修复错误的一些策略:
在脚本编辑器中查找语法错误。如果键入内容存在语法问题,应用制作工具脚本编辑器会自动警告。检查脚本行号左侧的警告标签。这些警告可以识别遗忘括号等常见错误。
在浏览器的 JavaScript 控制台中查找运行时错误。打开应用的预览实例或已部署版本,然后打开浏览器的 JavaScript 控制台。在 Chrome 中,按
Ctrl+Shift+j
或Alt-Cmd-j
打开 JavaScript 控制台。常见的运行时异常包括null
解引用 (dereferencing) 或是未捕获的throw
语句。您可以向脚本添加debugger;
语句以中断脚本执行并找出问题区域(DevTools 必须向debugger;
语句开放才能生效)。如需详细了解调试说明,请参阅 Chrome DevTools 中的调试 JavaScript 使用入门。使用日志和提醒跟踪脚本的执行情况。使用浏览器的内置
console.log()
函数将日志发送到 JavaScript 控制台。或者使用alert()
打开一个提醒框,该提醒框会中断脚本的执行,直到您将其解除为止。
客户端脚本示例
在绑定表达式中使用脚本
您可以使用 JavaScript 执行绑定表达式中的计算。举例来说,如果 Name
字段为空,或者 Age
字段的值小于 18,则按钮 enabled
属性的以下表达式将停用该按钮:
(@widget.parent.children.NameTextBox.value).length != 0 &&
@widget.parent.children.AgeTextBox.value >= 18
应用制作工具解析器首先解析以 @
开头的绑定表达式,以查找 Name
和 Age
的值。然后它将替换表达式中的这些值,并评估 JavaScript 的其他部分。
第一行的括号表示绑定表达式的结束。没有它们,应用制作工具会观察更改的 length
属性,而不是 value
属性。
调用服务器脚本
调用服务器脚本是一种异步操作,因此您必须使用回调来处理脚本的结果。
论坛示例应用包含调用服务器脚本的客户端脚本示例。在此应用的 View 页面,客户端脚本在用户点击 Submit 按钮时运行。该脚本确认应用制作工具创建了记录并运行了运行服务器脚本 notifyForumOwnerAboutNewMessageServer
的客户端函数 notifyForumOwnerAboutNewMessageClient
。
为了清楚起见,我们在论坛示例应用中修改了以下代码。如需在用户点击 Submit 按钮时触发客户端脚本,可以自定义 onClick 事件:
handleSubmitButtonClick(widget)
此操作会调用以下客户端脚本中的 handleSubmitButtonClick
函数,然后调用 notifyForumOwnerAboutNewMessageClient
函数。notifyForumOwnerAboutNewMessageClient
函数调用服务器脚本中的 notifyForumOwnerAboutNewMessageServer
函数:
function notifyForumOwnerAboutNewMessageClient(forumKey, messageKey) {
google.script.run
.withSuccessHandler(function(result) {
console.log('Email sent');
})
.withFailureHandler(function(error) {
console.log('Email not sent ' + error.message);
})
.notifyForumOwnerAboutNewMessageServer(forumKey, messageKey);
}
function handleSubmitButtonClick(widget) {
widget.datasource.createItem(function(createdRecord) {
var forumKey = createdRecord.Forum._key;
var messageKey = createdRecord._key;
notifyForumOwnerAboutNewMessageClient(forumKey, messageKey);
});
}
以下服务器脚本会检查论坛版主是否已订阅论坛,然后通知订阅的版主该论坛有新帖:
function notifyForumOwnerAboutNewMessageServer(forumKey, messageKey) {
var forum = app.models.Forum.getRecord(forumKey);
if (forum.OwnerSubscribed) {
var message = app.models.Message.getRecord(messageKey);
try {
MailApp.sendEmail({
to: forum.Owner,
subject: 'New message in your forum: "' + forum.Title +
'" by ' + message.Author,
htmlBody: message.Text
});
}
catch (e) {
console.log('Sending email notification for forumKey="%s" ' +
'and messageKey="%s" failed.', forumKey, messageKey);
console.log(e.message + '-> ' + e.stack);
}
}
}
动态加载外部脚本
您可以使用外部 JavaScript 库同步加载多个第三方脚本,但其中有些需要额外步骤。需要回调参数的库(如 Google API 客户端库)需要通过向 Settings > General > onAppStart
添加脚本进行动态加载。
以下脚本加载了 Google+ 库:
// Suspends app loading until after the Google Client API loads.
loader.suspendLoad();
// Defines a callback function, for the client API. It must be global,
// so it's explicitly attached to the window object.
window.OnGapiClientLoad = function() {
// Uses the Google Client API to load the Google+ library.
gapi.client.load("plus", "v1", function() {
// Continues loading the app once Google+ loads.
loader.resumeLoad();
});
};
var script = document.createElement("script");
script.setAttribute("type", "text/javascript");
// Specifies the name of the callback function in the "onload"
// parameter of the URL.
var url = "https://apis.google.com/js/client.js?onload=OnGapiClientLoad";
script.setAttribute("src", url);
document.getElementsByTagName("head")[0].appendChild(script);
调用 Google API
下面是最后一个示例,会稍微深入一点,让您了解实际应用制作工具脚本是什么样子。它使用了 Google JavaScript API。
该脚本在页面中显示 Google 地图,并根据用户输入进行更新。它使用名为 Map 的页面,页面包含以下微件:
- 名为
Street
和Zip
的两个文本字段。 - 名为
MapDiv
的 200x200 HtmlArea,它有一个调用loadMaps()
的onAttach
事件 - 一个按钮,带有一个调用
updateMap()
的onClick()
处理程序
脚本:
var map;
var geocoder;
var marker;
// Called by loadMaps() to set up widgets.
function createMap() {
var div = app.pages.Map.descendants.MapDiv.getElement();
map = new google.maps.Map(div, {
center: new google.maps.LatLng(-34.397, 150.644),
mapTypeId: google.maps.MapTypeId.ROADMAP,
zoom: 10,
});
geocoder = new google.maps.Geocoder();
marker = new google.maps.Marker({map: map});
}
// Returns an address string from Street and Zip text fields.
function getAddress() {
var page = app.pages.Map;
var street = page.descendants.Street.value;
var zip = page.descendants.Zip.value;
return street + ", " + zip;
}
// Positions the map at the given location coordinates.
function showLocation(locations, status) {
if (locations.length > 0) {
var latLng = locations[0].geometry.location;
map.panTo(latLng);
marker.setPosition(latLng);
}
}
// Sets up the Google Maps library.
function loadMaps() {
google.load("maps", "3", {callback: createMap});
}
function updateMap() {
geocoder.geocode({address: getAddress()}, showLocation);
}
脚本的重点:
- MapDiv 使用其
onAttach()
处理程序来设置 Google Maps API。 - 该脚本使用
getAddress()
从 Street 和 Zip 字段拉取数据,以获取用户输入。