Ember.js實現單頁面應用程序

1.1.1 摘要

單頁應用程序 (SPA) 是加載單個HTML 頁面並在用戶與應用程序交互時動態更新該頁面的Web應用程序。html

SPA使用AJAX和HTML5建立流暢且響應迅速的Web應用程序,不會常常進行頁面重載。 可是,這意味着許多工做在客戶端的JavaScript中進行。這致使咱們須要在客戶端中編程更多的Javascript代碼來處理數據的交互問題,幸運的是,咱們能夠藉助許多開放源代碼JavaScript框架來簡化建立SPA的任務,例如:Ember、Backbone、Angular、Knockout、DurandalJS和Hot Towel等等。jquery

目錄

1.1.2 正文

Ember是一個強大的JavaScript MVC框架,它用來建立複雜的Web應用程序,消除了樣板,而且提供了一個標準的應用程序架構,它支持用戶界面綁定、視圖、Web表示層而且很好的和其餘框架結合,爲了建立一個實時交互的Web應用程序中,這裏咱們使用了ASP.NET MVC的REST服務。web

MVC概念ajax

相信你們對於MVC模式再熟悉不過了,這裏咱們簡單說一下MVC模式:它目的是爲了分離視圖、模型和控制器而設計出來的;其中模型用來儲存數據,視圖用來向用戶展現應用程序,控制器充當模型和視圖之間的橋樑。chrome

SPA8

圖1 MVC模式數據庫

SPA1

圖2 Ember MVC編程

上圖是Ember實現MVC模式,它包括了View、Controller、Router、Template以及Model等概念,接下來,咱們將經過實現單頁面應用程序來介紹Ember和Web API結合使用。api

建立ASP.NET MVC 項目

首先,咱們建立一個ASP.NET MVC 4 Web應用程序,而後,選擇項目模板Web API,這裏爲了簡單因此沒有使用Ember.js的模板,若是你們想使用或學習請到這裏下載。服務器

SPA2

 

SPA3

圖3 建立ASP.NET MVC 4程序架構

接着,咱們在Script文件夾中添加引用腳本文件ember.js、ember-data.js、handlebars-1.0.0.js和jquery-2.0.3.js等,而後建立app文件,它用來存放客戶端的腳本文件,咱們建立application.js、models.js、routes.js和controllers.js文件。

SPA4

圖4 建立ASP.NET MVC 4程序

如今,咱們經過實現ASP.NET MVC應用程序來提供服務端的API,單頁面應用程序經過調用咱們的API接口對數據進行操做,接下來,咱們將給出具體的實現方法。

服務端的Model

接下來,咱們在Models文件中建立一個數據傳輸類Employee,具體定義以下:

/// <summary>
/// Defines a DTO Employee.
/// </summary>
public class Employee
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Title { get; set; }
    public string Department { get; set; }
    public string Remarks { get; set; }
}

接着,咱們使用EF6進行數據持久化操做,咱們知道EF的編程模式有3種:

  • Database First:數據庫先行
  • Model First:模型先行
  • Code First:代碼先行

前面,咱們已經定義數據傳輸對象Employee,但咱們沒有定義數據表,因此咱們將使用代碼先行(Code First)模型建立數據表。

接下來,咱們定義EmployeesContext類型,具體定義以下:

/// <summary>
/// Defines a DB context.
/// </summary>
public class EmployeesContext : DbContext
{
    /// <summary>
    /// Initializes a new instance of the <see cref="EmployeesContext"/> class.
    /// </summary>
    public EmployeesContext()
        : base("name=EmployeesContext")
    {
    }

    /// <summary>
    /// Gets or sets the employees.
    /// </summary>
    /// <value>
    /// The employees.
    /// </value>
    public DbSet<Employee> Employees { get; set; }
}

在構造函數EmployeesContext()中,咱們定義了數據庫鏈接的名稱爲EmployeesContext,接下來,咱們須要在Web.config文件中添加相應的數據庫鏈接,具體定義以下:

<connectionStrings>
  <add name="EmployeesContext" providerName="System.Data.SqlClient" connectionString="Data Source=Your_DataSourceName;Initial Catalog= Your_DataBaseName;Integrated Security=True" />
</connectionStrings>

RESTful服務

接下來,咱們須要提供接口讓客戶端程序調用,因此,咱們在Controller文件中建立REST API的web控件器EmployeesController,它繼承於ApiController而且定義方法GetEmployeesBy()和PutEmployee(),具體定義以下:

HTTP 動詞

URI

說明

GET

/api/employees

獲取全部員工的列表

GET

/api/employees/{id}

獲取 ID 等於 {id} 的員工信息

PUT

/api/employees/{id}

更新 ID 等於 {id} 的員工信息

POST

/api/employees

向數據庫添加新員工信息

DELETE

/api/employees/{id}

從數據庫中刪除員工信息

表1 REST API

/// <summary>
/// PUT api/Employees/30005
/// </summary>
/// <param name="id">The identifier.</param>
/// <param name="employee">The employee.</param>
/// <returns></returns>
public async Task<IHttpActionResult> PutEmployee(int id, Employee employee)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    if (id != employee.Id)
    {
        return BadRequest();
    }

    db.Entry(employee).State = EntityState.Modified;

    try
    {
        await db.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!EmployeeExists(id))
        {
            return NotFound();
        }
        // You can log the error.
        throw;
    }
    return Ok();
}

上面,咱們實現了REST API提供了接口GetEmployeesBy()和PutEmployee(),客戶端經過將類型置於URI的查詢字符串中進行調用;例如,咱們要獲取 IT部全部員工的信息,客戶端會發送Http GET請求/api/employees?department=IT,那麼Web API將自動將查詢參數綁定到 GetEmployeesBy()方法中。

也許有人會疑問:「Web API是如何自動將查詢綁定到不一樣的API方法中的呢?」首先,咱們要知道在客戶端是經過發送Http請求來調用Web API的,當咱們的Web API接受到Http請求時,它會根據路由選擇來綁定具體的方法。

接下來,讓咱們看看默認路由方法的實現,具體定義以下:

/// <summary>
/// Registers the specified configuration.
/// </summary>
/// <param name="config">The configuration.</param>
public static void Register(HttpConfiguration config)
{
    // Web API configuration and services
    // Web API routes
    config.Routes.MapHttpRoute(
        name: "DefaultApi",
        routeTemplate: "api/{controller}/{id}",
        defaults: new { id = RouteParameter.Optional }
    );
}

在WebApiConfig類中,已經提供了默認的路由實現,固然咱們也能夠從新實現該方法,可是咱們這裏仍是使用默認的路由方法。

例如:客戶端發送Http請求/api/employees?department=IT,路由選擇會到EmployeesController控制器中找參數是department的方法,最後綁定該方法調用。

SPA5

圖5 Http請求

如今,咱們已經實現了服務器端REST API了,接下來,咱們將經過EF的Code First模式建立數據庫表。

咱們的程序使用是EF 6和Web API 5.0,咱們能夠在程序包管理控制檯中輸入如下命令進行安裝。

  • Install-Package Microsoft.AspNet.WebApi -Version 5.0.0
  • Install-Package EntityFramework -Version 6.0.2

咱們也可使用Update-Package口令更新全部的包,Code First遷移有三個主命令,具體以下:

  • Enable-Migrations:啓用遷移。
  • Add-Migration:將根據自建立上次遷移以來您對模型所作的更改,爲下一次遷移搭建基架。
  • Update-Database:將全部掛起的遷移應用於數據庫。

當成功啓用數據遷移會在咱們的項目中增長Migrations文件而且會在咱們的數據庫中生成相應的數據庫表。

SPA6

圖6 數據遷移

SPA7

圖 7 Employees數據表

前面,咱們經過建立ASP.NET MVC應用程序來提供服務器端的API,接下來讓咱們實現Ember客戶端吧,關於Ember入門能夠參考這裏

Ember應用程序

首先,咱們在application.js文件中建立一個Ember應用程序,具體定義以下:

/// <summary>
/// Create an ember application.
/// </summary>
window.App = Ember.Application.create({
    // For debugging, log the information as below.
    LOG_TRANSITIONS: true,
    LOG_TRANSITIONS_INTERNAL: true
});

Ember Model

接着,咱們須要在客戶端中建立一個數據存儲對象Employee,具體定義以下:

/// <summary>
/// Create an employee model.
/// </summary>
App.Employee = DS.Model.extend({
    Id: DS.attr(),
    FirstName: DS.attr(),
    LastName: DS.attr(),
    Title: DS.attr(),
    Department: DS.attr(),
    Remarks: DS.attr(),
});

在以前的部分中,咱們已經定義了服務器端的數據模型Employee,因爲RESTful服務只返回JSON格式的對象,爲了方便把數據模型綁定到視圖中,咱們須要在客戶端中定義一個與服務器端對應的Ember數據模型Employee。

Ember Controller

控制器定義在Scripts/app/controllers.js中,若是咱們要表示單一的模型(單個Employee對象),那麼咱們能夠繼承ObjectController。

若是咱們表示多模型(一系列Employee對象),那麼就須要繼承ArrayController。

/// <summary>
/// To represent a collection of models by extending Ember.ArrayController.
/// </summary>
App.EmployeesController = Ember.ArrayController.extend();


/// <summary>
/// To represent a single model, extend Ember.ObjectController
/// </summary>
App.EmployeeController = Ember.ObjectController.extend({

    // Marks the model is edited or not.
    isEditing: false,
    // Binding corresponding event.
    actions: {
        edit: function() {
            this.set('isEditing', true);
        },
        save: function() {
            this.content.save();
            this.set('isEditing', false);
        },
        cancel: function() {
            this.set('isEditing', false);
            this.content.rollback();
        }
    }
});

上面,咱們定義了兩個控制器分別是EmployeeController和EmployeesController,在EmployeesController中,咱們實現了事件處理方法;當用戶點擊保存時調用save()方法,接着,咱們須要定義EmployeeAdapter對象序列化數據,而且調用Web API的方法進行保存。

/// <summary>
/// Create a custom adapter for employee, it will invoke our web api.
/// </summary>
App.EmployeeAdapter = DS.RESTAdapter.extend({
    // Override how the REST adapter creates the JSON payload for PUT requests.
    // Of course, we can use other verbs POST, DELETE and so forth.
    updateRecord: function(store, type, record) {
        var data = store.serializerFor(type.typeKey).serialize(record);
        return this.ajax(this.buildURL(type.typeKey, data.Id), "POST", { data: data });
    },
    namespace: 'api'
});

咱們實現了updateRecord()方法,獲取用戶提交的數據進行序列化,而後調用Web API進行保存。

Ember Route

路由定義在Scripts/app/routes.js中,這裏咱們定義了departments和about的路由。

/// <summary>
/// Defines departments and about router.
/// </summary>
App.Router.map(function() {
    this.route('about');
    this.resource('departments', function() {
        // The employees router under department.
        this.route('employees', { path: '/:department_name' });
    });
});

Ember Template

咱們知道Ember使用Handlebars模板引擎,那麼咱們定義的模板是基於Handlebars語法的,若是你們有使用過jQuery模板或其餘腳本模板,那麼對於掌握handlebars.js的使用就沒有太大的困難了,若是確實沒有使用過也不用擔憂,由於handlebars.js的使用也是挺簡單的。

首先,咱們定義了6個模板,它們分別是application、about、departments、departments/employees、_employees和error。

application.hbs:當應用程序啓動時默認加載的模板。

about.hbs:爲「/about」路由的模板。

departments.hbs:爲「/departments」路由的模板,顯示部門導航條。

departments/employees.hbs:爲「/departments/employees」路由的模板,根據部門獲取全部用戶信息。

_employees:如今每一個用戶信息的模板,這裏咱們要注意模板名開頭的下劃是有意義的,由於咱們使用了partial來渲染_employees,簡單來講,接收一個模板做爲其參數,而後恰當地渲染這個模板(具體請參考這裏)。

error:是如今一些錯誤信息的模板。

SPA10

圖 8 程序界面設計

<!-- application START -->   
 <script type="text/x-handlebars" data-template-name="application">
      <div class="container">    
         <h1>Ember SPA Demo</h1>    
          <div class="well">
              <div class="navbar navbar-static-top">
                  <div class="navbar-inner">
                      <ul class="nav nav-tabs">
                          <li>{{#linkTo 'departments'}}Departments{{/linkTo}} </li>
                          <li>{{#linkTo 'about'}}About{{/linkTo}} </li>
                      </ul>
                  </div>
              </div>
          </div>
          <div class="container">
              <div class="row">{{outlet}}</div>
          </div>
      </div>

      <div class="container">
          <p>&copy;2013 Jackson Huang</p>
      </div>
  
  </script>
 <!-- application END -->   

 <!-- about START -->   
 <script type="text/x-handlebars" data-template-name="about">
      <div class="container"></div>
      <h3>Ember SPA Demo</h3>
  </script>
 <!-- about END -->   

 <!-- departments START -->   
 <script type="text/x-handlebars" data-template-name="departments">
      <div class="span2">
          <div class="navbar">
              <div class="navbar-inner">
                  <ul class="nav nav-stacked">
                      {{#each item in model}}
             
                      <li>{{#linkTo 'departments.employees' item}}{{item.name}}{{/linkTo}}</li>
                      {{/each}}
         
                  </ul>
              </div>
          </div>
      </div>
      <div class="span8">{{outlet}}</div>
  </script>
 <!-- departments END -->   

 <!-- departments/employees START -->   
 <script type="text/x-handlebars" data-template-name="departments/employees">{{partial "employees"}}</script>
 <!-- departments/employees END -->   

 <!-- _employees START -->   
 <script type="text/x-handlebars" data-template-name="_employees">
      {{#if model}}
      <table class="table table-bordered table-condensed" >
          <thead>
              <tr><th>FirstName</th><th>LastName</th><th>Department</th><th>Title</th><th>Remarks</th></tr>
          </thead>
          <tbody>
          {{#each employee in model itemController="employee"}}
              <tr>
                  {{#if employee.isEditing}}
                  
                  <td>{{input type="text" value=employee.FirstName }}</td>
                  <td>{{input type="text" value=employee.LastName}}</td>
                  <td>{{view Ember.Select class="input-small" contentBinding="App.EmployeeController.departments" valueBinding="employee.Department"}}</td>
                  <td>{{input type="text" value=employee.Title }}</td>
                  <td>{{input type="text" value=employee.Remarks }}</td>
                  <td>
                      <button class="btn" {{action 'save'}}>Save</button>
                      <button class="btn" {{action 'cancel'}}>Cancel</button>
                  </td>

                  {{else}}

                  <td>{{employee.FirstName}}</td>
                  <td>{{employee.LastName}}</td>
                  <td>{{employee.Department}}</td>
                  <td>{{employee.Title}}</td>
                  <td>{{employee.Remarks}}</td>
                  <td>
                      <button class="btn" {{action 'edit'}}>Edit</button>
                  </td>
                  {{/if}}
                  </tr>
          {{/each}}
          </tbody>
      </table>
      {{else}}
      <p>No matches.</p>
      {{/if}}
  </script>
 <!-- _employees END -->   

 <!-- error START -->   
 <script type="text/x-handlebars" data-template-name="error">
        <h1>Error</h1>
        <h2>{{status}}.. {{statusText}}</h2>
 </script>
 <!-- error END –>   
上面,咱們分別定義了application、about、departments、departments/employees、_employees和error,其中,在departments/employees模板中,經過partial方法把模擬經過參數的形式傳遞給_employee模板。

 SPA9        

圖 9程序界面

如今,咱們已經完成了Ember單頁面應用程序,這裏我想你們推薦一個好用Chrome插件Ember Inspector,經過它咱們能夠更加容易理解Ember的模板,路由和控制之間的關係,從而提升咱們的開發效率。

1.1.3 總結

咱們經過一個單頁面應用程序的實現介紹了Ember和Web API的結合使用,其實,Ember中控制器、視圖、模型和路由的概念和ASP.NET MVC很是類似,若是咱們有ASP.NET MVC編程經驗,那麼對於掌握Ember的使用就沒有太多的困難,本文僅僅是介紹關於Ember.js一些基礎知識,若是你們想進一步學習,推薦你們到官方論壇學習或參考相應的文章。

最後,祝你們新年快樂,身體健康,闔家幸福,Code with pleasure。

參考

相關文章
相關標籤/搜索