模型,保存数据到数据库

文章来源:模型,保存数据到数据库javascript

  1. 环境搭建以及使用Ember.js建立第一个静态页面
  2. 引入计算属性、action、动态内容

继续为读者介绍如何使用Ember构建一个完整的、复杂的项目。html

第一个Ember.js模型

在前面两篇中实现了如何获取界面输入的邮箱值,可是并无真正保存到数据,仅仅只是获取界面输入的值并显示出来。在本篇中将为读者演示如何保存数据到数据库中。可是我并不会去建立一个数据库,而是使用firebase,更多有关firebase的信息请自行查阅资料学习(若是访问firebase官网很慢或者是没法访问那么你须要fanqiang)!java

言归正传,回到Ember的模型介绍中来。简单讲Ember的模型其实就是一个与数据表对应的一个实体类,与Java中的JavaBean有点相似。
建立一个模型也很是简单,能够直接使用Ember CLI命令建立,下面的命令就是用于建立模型类,并在模型中增长一个string类型的属性emailgit

ember g model invitation email:string

命令执行完毕以后能够在项目对应目录下看到建立的文件app/models/invitaction.js,文件内如以下:github

// app/models/invitation.js

import DS from 'ember-data';

export default DS.Model.extend({
  email: DS.attr('string')
});

有了模型类以后修改控制器index.js的代码,加入模型,经过模型来保存数据对象。web

// 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。sql

下面简单介绍如何把firebase整合到本项目中。数据库

  1. 安装 ember install emberfire,会自动建立文件app/adapters/application.js
  2. 在firebase官网注册一个用户,并建立一个APP(以下图1位置建立APP)而后会获得一个管理链接(好比:luminous-xxx-xxx.firebaseIO.com)
  3. 修改项目中config/environment.js,在文件中增长firebase的配置。

图1
图1ubuntu

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的数据

能够感觉到firebase的强大了吧!咱们几乎没有作任何处理数据就直接保存到firebase了,而且会自动根据模型建立数据,不过须要注意的是咱们在模型定义中并不须要定义id属性,firebase会自动生成一个惟一的id属性值,截图中的-KEr3XwUQjgLjb5yx0dp就是id属性值。

到此,数据的保存工做完成了,借助firebase大大简化了本身须要处理的东西,不须要本身建立数据库、数据表、以及保存数据sql等等!不知道你是否看明白了,若是有疑问请及时给我留言,我会尽力为你解答!

promise和this

promise(承诺)在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值跟你的是不同的),

使用ES2015后保存截图

若是你对this不是很懂,请看认真看下面文章的解释:

  1. Mozilla MDN this
  2. Javascript的this用法

建立管理页面

前面已经介绍了如何整合firebase到项目中,而且已经成功保存增长的数据。能够在firebase上看到全部数据,咱们建立一个后台页面去管理这些数据。

下面建立一个子路由和路由对应的模板页面,仍然是使用Ember CLI命令建立,命令以下:

ember g route admin/invitaction

命令执行完毕后会获得一个路由文件(app/routes/admin/invitaction.js)和一个模板文件(app/templates/admin/invitaction.hbs),命令会自动建立文件夹admin,子路由和子模板会放在子子目录下。
而后在首页增长菜单连接,修改navbar.hbs模板。

<!-- app/templates/navbar.hbs -->
<nav class="navbar navbar-inverse">
  <div class="container-fluid">
    <div class="navbar-header">
      <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#main-navbar">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      {{#link-to 'index' class="navbar-brand"}}Library App{{/link-to}}
    </div>

    <div class="collapse navbar-collapse" id="main-navbar">
      <ul class="nav navbar-nav">
            {{#link-to 'index' tagName="li"}}<a href>Home</a>{{/link-to}}
            {{#link-to 'about' tagName="li"}}<a href>About</a>{{/link-to}}
            {{#link-to 'contact' tagName="li"}}<a href>Contact</a>{{/link-to}}
      </ul>
      <!-- 后台管理页面连接 -->
      <ul class="nav navbar-nav navbar-right">
          <li class="dropdown">
              <a class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Admin<span class="caret"></span></a>
              <ul class="dropdown-menu">
                  {{#link-to 'admin.invitation' tagName="li"}}<a href="">Invitations</a>{{/link-to}}
              </ul>
          </li>
      </ul>
    </div><!-- /.navbar-collapse -->
  </div><!-- /.container-fluid -->
</nav>

代码{{#link-to 'admin.invitation' tagName="li"}}<a href="">Invitations</a>{{/link-to}}admin.invitation是一个嵌套路由或者说是子路由。更多有关路由嵌套问题请看Ember.js 入门指南之十三{{link-to}} 助手

在模板中使用表格遍历显示全部的邮箱数据。修改模板invitaction.hbs

<!-- app/templates/admin/invitations.hbs -->

<h1>Invitations</h1>

<table class="table table-bordered table-striped">
    <thead>
      <tr>
          <th>ID</th>
          <th>E-mail</th>
      </tr>
    </thead>
    <tbody>
    {{#each model as |invitation|}}
        <tr>
            <th>{{invitation.id}}</th>
            <td>{{invitation.email}}</td>
        </tr>
    {{/each}}

    </tbody>
</table>

上述代码中{{#each}}{{/each}}是Ember提供的遍历表达式,此表达式用于遍历数组数据。本例子中用户遍历从路由的model回调中返回的数据。更多有关此表达式的介绍请看Ember.js 入门指南之十handlebars遍历标签
修改路由app/routes/admin/invitations.jsmodel回调中获取服务器(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是老外的东西获取数据会比较慢,数据显示天然也会比较慢,稍等一会就在界面上看到了!)。若是你项目代码没问题也能够看到以下的截图。

Invitation数据截图

数据的CRUD操做

到这一步咱们已经能够完整的从服务器获取数据,并以列表形式显示在界面上。本教程的目标是建立一个简单的图书管理系统,前面的文章已经完成了相似于用户注册的功能,接下来咱们建立一个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”,其余代码不变。

<ul class="nav navbar-nav">
    {{#link-to 'index' tagName="li"}}<a href="">Home</a>{{/link-to}}
    {{#link-to 'libraries' tagName="li"}}<a href="">Libraries</a>{{/link-to}}
    {{#link-to 'about' tagName="li"}}<a href="">About</a>{{/link-to}}
    {{#link-to 'contact' tagName="li"}}<a href="">Contact</a>{{/link-to}}
</ul>

修改模板libraries.hbs,增长菜单连接。

<!-- app/templates/libraries.hbs -->
<h1>Libraries</h1>

<div class="well">
    <ul class="nav nav-pills">
      {{#link-to 'libraries.index' tagName="li"}}<a href="">List all</a>{{/link-to}}
      {{#link-to 'libraries.new' tagName="li"}}<a href="">Add new</a>{{/link-to}}
    </ul>
</div>
<!-- 子模板new.hbs、index.hbs会渲染到outlet上 -->
{{outlet}}

等待项目重启完成,进入http://localhost:4200/libraries。能够看到以下图的界面

libraries界面

此时点击界面的上的“List all”和“Add new”除了看到URL变化以外还没任何效果,由于咱们的子模板libraries/index.hbslibraries/new.hbs尚未任何内容,下面在这两个模板中增长一些代码。

<!-- app/templates/libraries/index.hbs -->
<h2>List</h2>

{{#each model as |library|}}
    <div class="panel panel-default">
        <div class="panel-heading">
            <h3 class="panel-title">{{library.name}}</h3>
        </div>
        <div class="panel-body">
            <p>Address: {{library.address}}</p>
            <p>Phone: {{library.phone}}</p>
        </div>
    </div>
{{/each}}
<!-- app/templates/libraries/new.hbs -->
<h2>Add a new local Library</h2>

<div class="form-horizontal">
    <div class="form-group">
        <label class="col-sm-2 control-label">Name</label>
        <div class="col-sm-10">
            {{input type="text" value=model.name class="form-control" placeholder="The name of the Library"}}
        </div>
    </div>
    <div class="form-group">
        <label class="col-sm-2 control-label">Address</label>
        <div class="col-sm-10">
            {{input type="text" value=model.address class="form-control" placeholder="The address of the Library"}}
        </div>
    </div>
    <div class="form-group">
        <label class="col-sm-2 control-label">Phone</label>
        <div class="col-sm-10">
            {{input type="text" value=model.phone class="form-control" placeholder="The phone number of the Library"}}
        </div>
    </div>
    <div class="form-group">
        <div class="col-sm-offset-2 col-sm-10">
            <button type="submit" class="btn btn-default" {{action 'saveLibrary' model}}>Add to library list</button>
        </div>
    </div>
</div>

模板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这一块,能够看到以下截图的信息。

自动生成控制器

能够看到每一个路由都对应着一个同名的控制器。

等待项目重启完毕,开始验证代码是否实现了所设想的要求。
首先在新增页面输入以下截图信息,而后点击按钮保存数据。

新增library数据

稍等片刻,等待数据保存完毕,能够看到界面顺利跳转到了http://localhost:4200/libraries下,以下图所示,而且看到了刚刚新增的数据,为了验证数据是否真的保存到服务器中,咱们进入到firebase的APP中查看,能够看到数据以及保存到里library下。

libraries页面

library数据列表页面截图

firebase页面

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的功能

  1. 建立一个包括两个属性emailmessage的模型contact参考代码
  2. 修改路由app/routes/contact.js返回一个空对象到模板上,参考代码
  3. 修改控制器contact.js,保存数据到firebase,参考代码
  4. 把放在控制器中的校验代码移动到模型app/models/contact.js中,参考代码
  5. 建立一个管理contact的后台页面http://localhost:4200/admin/contacts参考代码
  6. 在项目首页的导航菜单上增长一个菜单项“Contacts”,点击菜单进入http://localhost:4200/admin/contact参考代码
  7. 使用表格展现全部的contact数据,参考代码

使用路由和模型重构有关contact的代码

  1. 把有关contact的检验放到模型类中
  2. 把控制器contact.js中保存数据的代码移动到同名的路由中
  3. 测试经过后删除控制器contact.js

做业演示结果

新增contact信息

当输入的邮箱格式正确,而且message长度大于6时按钮“send”才可用。

新增contact信息成功

保存成功后状况输入框,而且显示提示信息。当切换路由后再进入到http://localhost:4200/contact会清空保存成功的提示信息。

后台Contact列表

后台页面成功显示了新增的数据,即便手动刷新页面数据也不会丢失,可见数据已经保存到firebase,在此再也不贴firebase上的数据截图了!



为了照顾懒人我把完整的代码放在GitHub上,若有须要请参考参考。博文通过屡次修改,博文上的代码与github代码可能有出入,不过影响不大!若是你以为博文对你有点用,请在github项目上给我点个star吧。您的确定对我来讲是最大的动力!!

相关文章
相关标签/搜索