Chapter 3—Application Logic 应用逻辑

在这一章中,您将学习如何添加JavaScript逻辑到你的nativescript APP,你会使用构建 NativeScript 框架的基本模式, MVVM ,或是“ Model View ViewModel ”。下面是这些词的意思:

  • Model(模型):模型定义和表示数据。将模型从各种可能使用的视图中分离出来,就可以实现代码重用。

  • View(视图):视图代表UI,在 NativeScript 是写在XML文件里的。视图常与视图模型数据绑定,使得在JavaScript里对视图模型作的改动立即触发UI组件的视觉效果变化。

  • View Model(视图模型): 视图模型包含应用逻辑(通常包括模型),并把数据暴露给视图。NativeScript提供一个叫做“ Observable ”的模块,这有助于创建一个可以绑定到视图的视图模型对象。

独立划分模型、视图和视图模型的最大好处是,你可以使用双向数据绑定;就是说,模型里面的数据变化会立即影响反应在视图上,反之亦然。另一个好处是代码重用,所以你经常可以跨视图使用模型和视图。

杂货店里,目前你只接触了视图 (login.xml),在本章你将要创建一个视图模型。为此,我们首先需要向你介绍另外一种文件: code-behind(后台代码文件)。

目录

3.1: 后台代码 {#code-behind}

NativeScript里后台代码文件就是一份和视图有相同名字的JavaScript文件。比如,登录页面的视图叫作 login.xml,那么它的 台代码 文件就叫作 login.js。代码后置文件是你放置所有与视图本身进行交互的代码的地方。

我们通过一个例子来看你在后台代码文件里能做什么。

操作: 构建登录视图模型


打开login.xml 在最上面添加一个loaded属性到<Page>UI组件。 就像这样:

<Page loaded="loaded">

然后,在app/views/login/login.js里粘贴下面的代码以定义一个loaded()函数:

exports.loaded = function() {

console.log("hello");

};

提示:


关键字exports隶属于**CommonJS, 在CommonJS这个标准基础上 NativeScript 和 Node.js 才实现了模块。在基于 CommonJS** 的 JavaScript 模块里,名为exports的自由变量是一个对象,一个模块可以向它添加属性和方法来配置外部 API 。在后台代码文件里使用 exports 暴露方法便于在视图(XML文件)中使用。即是,后台代码文件里定义的 exports.loaded 才让视图里的 loaded="loaded" 发生作用。

更改之后运行app, NativeScript 触发了你在后台代码文件里创建的 loaded() 方法,就会在命令行记录里看到“hello”。

应用逻辑 - 图1

这个简单的例子展示了你如何能向UI组件添加属性来运行视图的伴生JS文件里的方法。我们来添加这些属性里的另外一个: tap

操作: 激活按钮


你可以添加一个能在用户点击或触摸按钮时触发的 tap 属性。在app/views/login/login.xml文件里,替换屏幕底部的两个按钮为使用此标记:

<Button text="Sign in" tap="signIn" />

<Button text="Sign up for Groceries" class="link" tap="register" />

接着,在app/views/login/login.js 文件底部,粘贴下面的signIn() andregister() 方法:

exports.signIn = function() {

alert("Signing in");

};

exports.register = function() {

alert("Registering");

};

到此, 如果你运行app 并点击任一个按钮,你会看到相应的警告框弹出:

应用逻辑 - 图2应用逻辑 - 图3

既然你能看到点击手势生效,那就让它们做一些比弹出警告更有意思的事情吧。

3.2: Navigating screens导航页面 {#navigating-screens}

当你点击 “Sign up for Groceries” 按钮,你期望一个导航切换到注册页面。这在 NativeScript 里是非常简单就办到的。

操作: 实现登录页面的 “Sign up” 按钮的导航切换


app/views/login/login.js,添加这行到文件顶部:

var frameModule = require("ui/frame");

然后, 用下面的版本替换现有的 register()方法:

exports.register = function() {

var topmost = frameModule.topmost();

topmost.navigate("views/register/register");

};

该方法使用了 frame 模块, 这个 NativeScript 模块在你的app里 负责导航。现在,你通知顶层 frame ,或者用户看到的 frame ,导航到注册视图。

如果你运行app并点击 “Sign up for Groceries” 按钮,你会被带到我们已经为你预先搭建好的 注册界面。

应用逻辑 - 图4应用逻辑 - 图5

既然你能进入注册界面,就跟着注册一个账户用于后面的的教程。

操作: 注册一个账户


打开杂货店点击 “Sign up for Groceries” 按钮进入注册页面。填写email地址和密码,点击 “Sign up” 按钮创建一个账户。

你可以使用一个虚假的邮件地址和密码,一定记住你的登录凭据,因为后面会用到。

提示:


虽然我们的杂货店app不使用复杂的导航策略,你还有几种可能来跳出这个框框,比如**TabViewSegmentedBar。 SideDrawer 组件也可免费用在 Telerik公司的 UI for NativeScript **产品上。

3.3: Accessing UI components获取UI组件 {#accessing-ui-components}

是时候看下数据如何在前段和后端之间来回流动的形式了。

操作:从前端传递数据到视图模型


打开 login.xml 向email文本域添加一个 id="email" 属性 。它的标记应该看起来像这样:

<TextField id="email" hint="Email Address" keyboardType="email" autocorrect="false" autocapitalizationType="none" />

里面有一个 id 属性就位,你可以在你的后台代码文件获取到这个 TextField 。为此,先打开app/views/login/login.js 然后在该文件顶部添加下面两行代码, 紧跟着 frameModule 的下面。

var page;

var email;

接着, 在 login.js 里编辑loaded() 方法获得当前页面的引用:

exports.loaded = function(args) {

page = args.object;

};

提示:


能这样做是因为NativeScript在方法参数里传递了 一个<Page> 的引用 给 loaded 事件句柄,参数按惯例叫作 args

最后,编辑 signIn() 方法获取文本域 TextField 组件的引用并log它的内容:

exports.signIn = function() {

email = page.getViewById("email");

console.log(email.text);

};

所有的 NativeScript UI组件,包括 <Page>,都继承于 View 类,这赋予它们很多操作UI的方法。在本例你使用 getViewById() 方法通过其 id 属性来获取email文本域的引用。

要看看这是如何实际工作的,启动app,在email文本域输入一些文本,然后点击 “Sign in” 按钮。如果一切正常,你会看到输入的文本记录在你的命令行了。

应用逻辑 - 图6

通过在 JavaScript 里获取UI组件,你能操作这些组件在前端的外观和行为。然而,单个地获取这些UI组件是非常手动的程序,也使得跟踪UI状态比较困难。这里就该视图模型出马了。

3.4: Adding a view model添加一个视图模型 {#34}

NativeScript 通过一个叫做 ‘Observable’的模块 提供视图模型功能 。

Observable就是 MVVM 设计模式里的视图模型。它提供了一个机制用于双向数据绑定,能够让UI和后台代码文件直接通讯。这意味着如果用户改变了UI里的数据,这个改变会自动反馈到视图模型,反之亦然。

操作: 创建一个视图模型并绑定到视图

要实现双向数据绑定需要使用 Observable ,打开 login.xml ,把现有的 TextField UI组件替换为下面的两个,每个都包含一个新的 text 属性:

<TextField id="email" text="{{ email }}" hint="Email Address" keyboardType="email" autocorrect="false" autocapitalizationType="none" />

<TextField secure="true" text="{{ password }}" hint="Password" />

注意:

两对花括号包裹 text 属性值的用法界定了一个数据绑定值。你将在视图模型里用同样的名字设置相应的属性。

添加下面的代码到 app/views/login/login.js 顶部。该代码从 observable 模块获得了 Observable 构造器的引用,并调用构造器定义了一个新的user对象,这就是你在本页使用到的视图模型

var Observable = require("data/observable").Observable;

var user = new Observable({

email: "user@domain.com",

password: "password"

});

现在,用下面的代码替换现有的 loaded()方法,它 user 设置为该页面绑定的上下文。

exports.loaded = function(args) {

page = args.object;

page.bindingContext = user;

};

刚才发生了什么?

  1. 你正在基于 NativeScript的 Observable 模块创建一个 user 视图模型。你创这个视图模型带有两个属性: email and password,它们预先设置了些杂乱的值。
  2. 你通过设置page的 bindingContext 属性将pege绑定到user视图模型。这就明确地让花括号语法生效了。

简言之,放置在page的 binding context 里的属性可以由XML元素使用{ {属性名} }语法获取到。因为 JavaScript 设置了视图模型的 email"user@domain.com" ,又因为你使用 <TextField text="{{ email }}"> 绑定了这个email地址 text field 到属性,所以运行app就会看到 “user@domain.com” 出现在前端。

应用逻辑 - 图7应用逻辑 - 图8

真正酷的是这个绑定是双向的。这意味着,用户在文本域输入文本的时候,这些改动会立即反映到你的视图模型。

要使用这些值,并通过你的app 联系上后台服务让这个登录有用,你将需要发起 HTTP请求的能力。在NativeScript里发起 HTTP 就使用fetch模块。我们就来看下 NativeScript模块如何工作的。