摘要:文章来源模型,保存数据到数据库环境搭建以及使用创建第一个静态页面引入计算属性动态内容继续为读者介绍如何使用构建一个完整的复杂的项目。
文章来源:模型,保存数据到数据库
环境搭建以及使用Ember.js创建第一个静态页面
引入计算属性、action、动态内容
继续为读者介绍如何使用Ember构建一个完整的、复杂的项目。
第一个Ember.js模型在前面两篇中实现了如何获取界面输入的邮箱值,但是并没有真正保存到数据,仅仅只是获取界面输入的值并显示出来。在本篇中将为读者演示如何保存数据到数据库中。但是我并不会去创建一个数据库,而是使用firebase,更多有关firebase的信息请自行查阅资料学习(如果访问firebase官网很慢或者是无法访问那么你需要fanqiang)!
言归正传,回到Ember的模型介绍中来。简单讲Ember的模型其实就是一个与数据表对应的一个实体类,与Java中的JavaBean有点类似。
创建一个模型也非常简单,可以直接使用Ember CLI命令创建,下面的命令就是用于创建模型类,并在模型中增加一个string类型的属性email。
ember g model invitation email:string
命令执行完毕之后可以在项目对应目录下看到创建的文件app/models/invitaction.js,文件内如如下:
// app/models/invitation.js import DS from "ember-data"; export default DS.Model.extend({ email: DS.attr("string") });
有了模型类之后修改控制器index.js的代码,加入模型,通过模型来保存数据对象。
// app/controller/index.js import Ember from "ember"; export default Ember.Controller.extend({ headerMessage: "Coming Soon", responseMessage: "", // 设置默认值为空字符串 emailAddress: "", // 设置默认值为空字符串 // 使用正则表达式判断邮箱格式,如果正确则返回true反之返回false isValid: Ember.computed.match("emailAddress", /^.+@.+..+$/), // 把计算属性isValid绑定到isDisabled上 isDisabled: Ember.computed.not("isValid"), //当`disabled=false`时按钮可用,所以正好需要取反 actions: { saveInvitation: function() { const email = this.get("emailAddress"); // 创建一个模型对象 const newInvitaction = this.store.createRecord("invitation", { email: email }); newInvitaction.save(); //保存模型对象到store中 this.set("responseMessage", `Thank you! We"ve just saved your email address: ${this.get("emailAddress")}`); // 情况输入框内容 this.set("emailAddress", ""); } } });
等待项目重新启动,在界面输入正确的邮箱,点击按钮,可以在浏览器控制台看到如下错误信息:
为何会出现这些错误呢??其实很简单,我们并没有对应的后台服务区处理数据,目前仅仅是提交了数据而已,那么怎么处理呢?我们引入一个非常好用的数据库——firebase。firebase为Ember提交了非常丰富的API,我们不需要再自己写后台的处理程序,可以直接调用firebase提供的API即可完成数据的CRUD操作。更多有关firebase的使用教程请看EmberFire Guide,在这个参考文档上详细介绍了firebase如何与Ember整合,Ember如何调用firebase提供的API。
下面简单介绍如何把firebase整合到本项目中。
安装 ember install emberfire,会自动创建文件app/adapters/application.js
在firebase官网注册一个用户,并创建一个APP(如下图1位置创建APP)然后会得到一个管理连接(比如:luminous-xxx-xxx.firebaseIO.com)
修改项目中config/environment.js,在文件中增加firebase的配置。
图1
config/environment.js配置代码如下:
var ENV = { modulePrefix: "library-app", environment: environment, contentSecurityPolicy: { "connect-src": ""self" https://auth.firebase.com wss://*.firebaseio.com" }, firebase: "https://YOUR-FIREBASE-NAME.firebaseio.com/", //改成自己在firebase上APP的地址 baseURL: "/", locationType: "auto", EmberENV: { FEATURES: { // Here you can enable experimental features on an ember canary build // e.g. "with-controller": true } }, APP: { // Here you can pass flags/options to your application instance // when it is created } }; // ……其他代码省略
注意上述代码中的第5行,firebase属性的值是自己在firebase申请的APP的URL。一定记得要修改!!!
修改完成之后手动重启项目,记得是手动关闭终端运行的项目(ctrl+c关闭),然后再使用命令ember s启动项目。否则新安装的emberfire无法起作用。
等待项目启动完成,如果启动过程中没有出现错误,说明emberfire安装成功!
然后激动的时刻到了,在首页输入正确的邮箱,点击按钮,可以看到浏览器控制来不会报错了!并且在firebase官网的APP中看到刚刚新增的邮箱!!
注意:点击按钮提交后可能看到界面没有任何反应,先别急,由于firebase是外国的东西,在天朝访问都是比较慢,你懂的。提交后到响应回来可能比较慢。
从浏览器控制台打印的日志可以看出向firebase发送请求,截图如下:
并且在界面上提示了保存成功的信息!
最后在firebase官网上可以查看到刚刚提交数据。
可以感受到firebase的强大了吧!我们几乎没有做任何处理数据就直接保存到firebase了,并且会自动根据模型创建数据,不过需要注意的是我们在模型定义中并不需要定义id属性,firebase会自动生成一个唯一的id属性值,截图中的-KEr3XwUQjgLjb5yx0dp就是id属性值。
到此,数据的保存工作完成了,借助firebase大大简化了自己需要处理的东西,不需要自己创建数据库、数据表、以及保存数据sql等等!不知道你是否看明白了,如果有疑问请及时给我留言,我会尽力为你解答!
promise和thispromise(承诺)在JavaScript中是一个异步特性。这个特性还在完善之中,更多有关promise的介绍请看promises-book或者Mozilla MDN Promise。
在前面保存数据的代码中save()方法返回值就是一个promise,我们可以根据save()方法的返回值做不同的处理,比如保存失败时候的处理。
saveInvitation: function() { const email = this.get("emailAddress"); // 创建一个模型对象 const newInvitaction = this.store.createRecord("invitation", { email: email }); //保存模型对象到store中 newInvitaction.save().then(function(msg) { console.log("保存成功。"); }, function(reason) { console.log("保存失败!"); }); this.set("responseMessage", `Thank you! We"ve just saved your email address: ${this.get("emailAddress")}`); // 情况输入框内容 this.set("emailAddress", ""); }
如果你看过有关promise的介绍那么理解上述代码应该是很简单的,在方法then()中第一个函数(参数)会在save()执行成功的时候执行,第二个函数(参数)会在save()执行失败的时候执行。明白这个之后我们再修改控制器index.js的代码。我们把提示信息放在save()执行成功的时候执行方法中。
saveInvitation: function() { const email = this.get("emailAddress"); // 创建一个模型对象 const newInvitaction = this.store.createRecord("invitation", { email: email }); //保存模型对象到store中 newInvitaction.save().then(function(msg) { this.set("responseMessage", `Thank you! We"ve just saved your email address: ${this.get("emailAddress")}`); // 情况输入框内容 this.set("emailAddress", ""); }, function(reason) { this.set("responseMessage", `Saved: ${this.get("emailAddress")} failed!`); // 情况输入框内容 this.set("emailAddress", ""); }); }
等待项目自动重启完成,在界面输入正确邮箱,提交数据,此时并没有出现任何反应,并且会在浏览器控制台看到如下错误,
这又是什么原因呢?其实原因很简单,因为this作用域问题,由于是在then()内部使用了this导致此时的this指向的并不是控制器类了,只有在Ember的上下文中才能使用set()方法!我们用一个临时变量解决这个问题,代码修改为如下:
// app/controller/index.js import Ember from "ember"; export default Ember.Controller.extend({ headerMessage: "Coming Soon", responseMessage: "", // 设置默认值为空字符串 emailAddress: "", // 设置默认值为空字符串 // 使用正则表达式判断邮箱格式,如果正确则返回true反之返回false isValid: Ember.computed.match("emailAddress", /^.+@.+..+$/), // 把计算属性isValid绑定到isDisabled上 isDisabled: Ember.computed.not("isValid"), //当`disabled=false`时按钮可用,所以正好需要取反 actions: { saveInvitation: function() { const email = this.get("emailAddress"); // 创建一个模型对象 const newInvitaction = this.store.createRecord("invitation", { email: email }); var _this = this; //保存模型对象到store中 newInvitaction.save().then(function(msg) { _this.set("responseMessage", `Thank you! We"ve just saved your email address: ${_this.get("emailAddress")}`); // 情况输入框内容 _this.set("emailAddress", ""); }, function(reason) { _this.set("responseMessage", `Saved: ${_this.get("emailAddress")} failed!`); // 情况输入框内容 _this.set("emailAddress", ""); }); } } });
等待项目自动重启完成,在页面输入正确的邮箱并提交,可以看到此时效果与之前是一样的,然后去firebase查看结果,也是可以看到新增的数据。
虽然是用临时变量方式可以解决由于this作用域问题,但是还有更加优美的解决办法,如今几乎所有新版的浏览器引擎已经支持ES2015,可以使用ES2015的=>操作符解决this作用域问题,请看下面的处理代码:
saveInvitation: function() { const email = this.get("emailAddress"); // 创建一个模型对象 const newInvitaction = this.store.createRecord("invitation", { email: email }); //保存模型对象到store中 newInvitaction.save().then((response) => { console.log("response = " + response); this.set("responseMessage", `Thank you! We"ve just saved your email address: ${response.get("id")}`); // 情况输入框内容 this.set("emailAddress", ""); }, (reason) => { this.set("responseMessage", `Saved: ${this.get("emailAddress")} failed!`); // 情况输入框内容 this.set("emailAddress", ""); }); }
使用ES2015的特性之后不仅解决了this作用域问题,而且连关键字function都不需要了,使用=>操作会自动把外层this所指的对象传递到函数内部,并且修改了保存成功时的提示信息,使用${response.get("id")}从firebase响应的数据中获取到保存成功后返回的id值,返回的response就是一个模型invitation的对象,可以使用get()方法获取对象值。
再次测试,如果项目代码没有误那么你可以得到如下截图的提示信息(id值跟你的是不一样的),
如果你对this不是很懂,请看认真看下面文章的解释:
Mozilla MDN this
Javascript的this用法
创建管理页面前面已经介绍了如何整合firebase到项目中,并且已经成功保存增加的数据。可以在firebase上看到所有数据,我们创建一个后台页面去管理这些数据。
下面创建一个子路由和路由对应的模板页面,仍然是使用Ember CLI命令创建,命令如下:
ember g route admin/invitaction
命令执行完毕后会得到一个路由文件(app/routes/admin/invitaction.js)和一个模板文件(app/templates/admin/invitaction.hbs),命令会自动创建文件夹admin,子路由和子模板会放在子子目录下。
然后在首页增加菜单链接,修改navbar.hbs模板。
代码{{#link-to "admin.invitation" tagName="li"}}Invitations{{/link-to}}中admin.invitation是一个嵌套路由或者说是子路由。更多有关路由嵌套问题请看Ember.js 入门指南之十三{{link-to}} 助手。
在模板中使用表格遍历显示所有的邮箱数据。修改模板invitaction.hbs。
Invitations
ID | |
---|---|
{{invitation.id}} | {{invitation.email}} |
上述代码中{{#each}}{{/each}}是Ember提供的遍历表达式,此表达式用于遍历数组数据。本例子中用户遍历从路由的model回调中返回的数据。更多有关此表达式的介绍请看Ember.js 入门指南之十handlebars遍历标签。
修改路由app/routes/admin/invitations.js在model回调中获取服务器(firebase)上的数据。
// app/routes/admin/invitations.js import Ember from "ember"; export default Ember.Route.extend({ model() { return this.store.findAll("invitation"); } });
等待项目重启完成,可以在项目首页导航栏的右侧看到可以点击下拉的Admin菜单项,点击菜单看到子菜单项“Invitation”,点击“Invitation”进入到http://localhost:4200/admin/invitation。
在界面上可以看到之前新增的所有邮箱信息和firebase自动生成的ID属性值(由于firebase是老外的东西获取数据会比较慢,数据显示自然也会比较慢,稍等一会就在界面上看到了!)。如果你项目代码没问题也可以看到如下的截图。
到这一步我们已经可以完整的从服务器获取数据,并以列表形式显示在界面上。本教程的目标是创建一个简单的图书管理系统,前面的文章已经完成了类似于用户注册的功能,接下来我们创建一个library模型,用于保存书库信息。
同样是使用Ember CLI命令创建模型。
ember g model library name:string address:string phone:string
上述命令创建了一个包含三个属性的模型,这三个属性都是string类型的数据。创建完模型之后再继续创建三个模板,分别用户显示library列表新建library数据。
ember g template libraries ember g template libraries/index ember g template libraries/new
模板创建完毕之后手动在router.js中增加路由配置,这次我们不采用Ember CLI命令创建了!!!
// app/router.js import Ember from "ember"; import config from "./config/environment"; var Router = Ember.Router.extend({ location: config.locationType }); Router.map(function() { this.route("about"); this.route("contact"); this.route("admin", function() { this.route("invitation"); }); this.route("libraries", function() { this.route("new"); }); }); export default Router;
再更新首页模板navbar.hbs,增加一个菜单项“libraries”,其他代码不变。
修改模板libraries.hbs,增加菜单链接。
Libraries
{{outlet}}
等待项目重启完成,进入http://localhost:4200/libraries。可以看到如下图的界面
此时点击界面的上的“List all”和“Add new”除了看到URL变化之外还没任何效果,因为我们的子模板libraries/index.hbs、libraries/new.hbs还没有任何内容,下面在这两个模板中增加一些代码。
List
{{#each model as |library|}}{{/each}}{{library.name}}
Address: {{library.address}}
Phone: {{library.phone}}
Add a new local Library
{{input type="text" value=model.name class="form-control" placeholder="The name of the Library"}}{{input type="text" value=model.address class="form-control" placeholder="The address of the Library"}}{{input type="text" value=model.phone class="form-control" placeholder="The phone number of the Library"}}
模板new.hbs是一个表单,用于新增数据,通过点击按钮“Add to library list”提交表单数据,表单数据由路由libraries/new.js中的saveLibrary方法处理,此时此方法还没定义,在接下来的代码中会定义。
在{{action}}表达式中传递了一个参数model到处理的后台,表单中的其他属性会以model的属性方式传递到后台,之所以可以这样做是因为在模板对应的路由中返回了一个空的library对象,在接下来的路由libraries/new.js将看到。
等待项目重启完,在点击“List all”和“Add new”可以看到这两个子模板的内容渲染到父模板libraries.hbs的{{outlet}}上。不过由于并没有在路由中获取模型library的数据所以“List all”页面还没有任何数据,“Add new”页面是第一个新增数据吧表单。
下面在路由libraries/index.js中获取library的数据,并在model回调中返回到模板中遍历显示。
使用Ember CLI命令创建路由,创建过程会询问是否覆盖已经存在的模板文件,输入n选择否。
ember g route libraries/index ember g route libraries/new
路由创建完成之后分别在这两个路由中增加获取数据的代码。
// app/routes/libraries/index.js import Ember from "ember"; export default Ember.Route.extend({ model() { return this.store.findAll("library"); } });
// app/routes/libraries/new.js import Ember from "ember"; export default Ember.Route.extend({ model() { return this.store.createRecord("library"); }, actions: { // 处理模板上输入的数据 saveLibrary(newLibrary) { newLibrary.save().then(() => this.transitionTo("libraries")); }, willTransition() { // rollbackAttributes() removes the record from the store // if the model "isNew" this.controller.get("model").rollbackAttributes(); } } });
在此路由的model回调中我们创建了一个空的library对象,并返回到模板页面。这也是为什么能在模板中传递参数model的原因。
代码中方法willTransition()是Ember提供的内置方法,此方法的作用是当用户离开当前URL时会清空未保存到服务器的library数据,如果不重置modelEmber会在路由切换的时候自动保存数据到服务器上。
保存数据的方法saveLibrary()写的比较简洁,它的作用是:先调用save()方法保存数据,如果保存成功在调用方法transitionTo()跳转到路由libraries下(library首页),有关=>语法的介绍请看Mozill MDN Arrow_functions。
在上述代码中多次是用了this.controller,但是在路由中并没有这个属性controller而且也没有控制器文件app/controllers/libraries/new.js,运行项目并不会报错!这是为什么呢?这是因为Ember会自动生成一个虚拟的控制器并在保存在内存中,根据Ember的命名规则会自动生成一个与路由同名的控制器,
为了验证这个说法,打开浏览器的控制台,在打开标签“Ember”然后点击左侧的“/# Routes”,找到路由libraries这一块,可以看到如下截图的信息。
可以看到每个路由都对应着一个同名的控制器。
等待项目重启完毕,开始验证代码是否实现了所设想的要求。
首先在新增页面输入如下截图信息,然后点击按钮保存数据。
稍等片刻,等待数据保存完毕,可以看到界面顺利跳转到了http://localhost:4200/libraries下,如下图所示,并且看到了刚刚新增的数据,为了验证数据是否真的保存到服务器中,我们进入到firebase的APP中查看,可以看到数据以及保存到里library下。
library数据列表页面截图
firebase上保存的library数据截图
此时,如果你注释了方法willTransition()结果会是怎么样的呢!!如果没有这个方法去重置model,当你每次在“Add new”页面输入输入并且没有点击“Add to library list”保存数,然后切换到其他路由下(比如点击“List all”切换到路由libraries下)会自动保存一条数据到服务器。
在“Add new”页面输入如下截图数据
点击按钮“List all”切换到路由libraries下,可以看到在“Add new”页面添加的数据,如下图所示,但是如果你手动刷新页面后可以发现这条数据不见了,并且在firebase上也没有这条数据,可见这条数据仅仅是保存到Ember的store中,并没有真正保存到服务器上。这样的体验是非常糟糕的!!
其中,实现重置model的方式还有另外一种更加合适的方法,代码如下:
willTransition() { // rollbackAttributes() removes the record from the store // if the model "isNew" // this.controller.get("model").rollbackAttributes(); let model = this.controller.get("model"); if (model.get("isNew")) { model.destroyRecord(); } }
本篇的内容到此全部介绍完毕!本篇我们实现了数据的保存、显示,特别是library数据的保存。数据的保存、显示都需要与模型关联,模型在Ember是一个非常重要的内容!希望读者好好掌握模型。
家庭作业下面两个作业完成其中之一即可。本篇选择第一个,第二个请读者自行完成!动手才是真理。
增强contact的功能创建一个包括两个属性email和message的模型contact,参考代码
修改路由app/routes/contact.js返回一个空对象到模板上,参考代码
修改控制器contact.js,保存数据到firebase,参考代码
把放在控制器中的校验代码移动到模型app/models/contact.js中,参考代码
创建一个管理contact的后台页面http://localhost:4200/admin/contacts,参考代码
在项目首页的导航菜单上增加一个菜单项“Contacts”,点击菜单进入http://localhost:4200/admin/contact,参考代码
使用表格展示所有的contact数据,参考代码
使用路由和模型重构有关contact的代码把有关contact的检验放到模型类中
把控制器contact.js中保存数据的代码移动到同名的路由中
测试通过后删除控制器contact.js
作业演示结果
当输入的邮箱格式正确,并且message长度大于6时按钮“send”才可用。
保存成功后情况输入框,并且显示提示信息。当切换路由后再进入到http://localhost:4200/contact会清空保存成功的提示信息。
后台页面成功显示了新增的数据,即使手动刷新页面数据也不会丢失,可见数据已经保存到firebase,在此不再贴firebase上的数据截图了!
为了照顾懒人我把完整的代码放在GitHub上,如有需要请参考参考。博文经过多次修改,博文上的代码与github代码可能有出入,不过影响不大!如果你觉得博文对你有点用,请在github项目上给我点个star吧。您的肯定对我来说是最大的动力!!
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/79521.html
摘要:交易依然表示状态的变化迁移。这样一个模型的特点是状态是第一性的所有者是状态的属性,每一份状态只有一个所有者状态不断的被销毁和创建。值得指出的是,的编程模型之所以强大,更多是因为它有状态,而不是因为它的有限图灵完备。 在设计 CKB 的时候,我们想要解决三个方面的问题: 状态爆炸引起的公地悲剧及去中心化的丧失;计算和验证耦合在了一起使得无论是计算还是验证都失去了灵活性,难以扩展;交易与价...
摘要:为了理解底层公链的模型,我们前置了几篇概念性文章,讲述了我们应该以状态为中心设计区块链系统的,以及这么做带来的好处。交易依然表示状态的变化迁移。 为了理解底层公链 CKB 的 Cell 模型,我们前置了几篇概念性文章,讲述了我们应该以状态为中心设计区块链系统的,以及这么做带来的好处。并且在上一篇文章中,详细分析了比特币 UTXO 模型和以太坊的 Account 模型,以及进行了对比分析...
摘要:二在中函数是如何执行的函数我们之前已经都接触过了,函数无非有两部分数据和对数据的操作。函数的调用输出结果为我们使用了的调试,函数在执行时会将参数和函数中所用到的变量方法相同的地位,即在函数内部执行的时候不会区分是参数还是变量。 showImg(https://segmentfault.com/img/remote/1460000017790532); 准备写点乱七八糟的文章,对Java...
好的,下面是一篇关于使用CPU训练TensorFlow的编程技术文章: TensorFlow是一种非常流行的机器学习框架,它可以用于训练各种深度学习模型。虽然通常使用GPU进行训练,但在某些情况下,使用CPU进行训练可能更加适合。本文将介绍如何使用CPU训练TensorFlow,并提供一些编程技巧。 1. 确认TensorFlow版本 首先,您需要确认您正在使用的TensorFlow版本是否...
摘要:使用模型的代表是比特币。每一个比特币全节点都会维护当前所有的集合,这个集合我们就称为比特币账本的当前状态即当前的账本。 本文是对前几期秘猿区块链课堂中关于 Cell 模型的总结。底层公链 CKB 的 Cell 模型是一个高度抽象的模型,事实上,你不仅可以在 Cell 上实现 First-class Asset,也可以在 Cell 上模拟 Account。通过本文的介绍我们可以看出,Ce...
阅读 2326·2021-11-18 10:02
阅读 1884·2021-10-13 09:40
阅读 2977·2021-09-07 10:07
阅读 2081·2021-09-04 16:48
阅读 964·2019-08-30 13:18
阅读 2433·2019-08-29 14:03
阅读 2894·2019-08-29 12:54
阅读 3136·2019-08-26 11:41