Day 19: EmberJS 入門指南

編者注:咱們發現了有趣的系列文章《30天學習30種新技術》,正在翻譯,一天一篇更新,年終禮包。下面是第19天的內容。javascript


到目前爲止,咱們這一系列文章涉及了BowerAngularJSGruntJSPhoneGapMeteorJS 這些JavaScript技術。今天我打算學習一個名爲Ember的框架。本文將介紹如何用Ember建立一個單頁面的社交化書籤應用。本教程將包括兩篇:第1篇介紹客戶端代碼和用HTML 5本地存儲持久保存數據,第2篇中咱們將使用一個部署在OpenShift上的REST後端。過幾天我會寫第2篇。css

emberjs

應用

咱們將開發一個社交化書籤應用,容許用戶提交和分享連接。你能夠在這裏查看這個應用。這個應用能夠作到:html

  • 當用戶訪問/時,他會看到以提交時間排序的報道列表。html5

    GetBookmarks Homepage

  • 當用戶訪問某個書籤時,例如#/stories/d6p88,用戶會看到關於這個報道的信息,例如是誰提交的,什麼時候提交的,以及文章的摘要。java

    GetBookMarks Story View

  • 最後,當用戶經過#/story/new提交新報道時,內容會存儲在用戶瀏覽器的本地存儲上。jquery

    GetBookMarks Submit Story.

什麼是Ember?

Ember是一個客戶端的JavaScript MV* 框架,用來構建野心勃勃的web應用。它依賴於jQueryHandlebars庫。若是你曾經在Backbone下工做,那麼你會發現Ember是一個武斷的Backbone,或者Backbone++。Ember能夠爲你完成不少事情,若是你遵循它的命名約定的話。Ember.js在這方面很突出。所以,若是咱們在應用中加入了url路由和報道,那麼咱們就有了這些:git

  • 報道的模板
  • StoriesRoute
  • StoriesController

請參考命名約定文檔來理解Ember的命名約定。github

Ember核心概念

本節將介紹咱們的示例應用中將涉及的四個EmberJS的核心概念:web

  1. 模型:模型表明咱們展現給用戶的應用領域內的對象。在上述例子中,一個報道就表明一個模型。報道,加上它的屬性,包括標題、url等,構成一個模型。模型能夠經過jQuery加載服務器端的JSON數據的方式來獲取和更新,也能夠經過Ember Data來獲取和更新。Ember Data是一個客戶端的ORM實現,能夠利用它方便地對底層的持久性存儲進行CRUD操做。Ember Data提供一個倉庫接口,能夠藉助提供的一些適配器配置。Ember Data提供的兩個核心適配器是RESTAdapter和FixtureAdapter。在本文中,咱們將使用LocalStorage適配器,該適配器將數據持久化爲 HTML 5 的LocalStorage。請參閱此文檔瞭解詳情。ajax

  2. 路由器和路由:路由器指定應用的全部路由。路由器將URL映射到路由。例如,當一個用戶訪問/#/story/new的時候,將渲染newstory模板。該模板展示了一個HTML表單。用戶可經過建立Ember.Route子類來定製路由。在上述例子中,用戶訪問/#/story/new將渲染一個基於newstory模板的默認模型。NewStoryRoute會負責將默認的模型分配給newstory模板。請參閱文檔瞭解詳情。

  3. 控制器:控制器能夠作兩件事——首先它裝飾路由返回的模型,接着它監聽用戶執行的行動。例如,當用戶提交報道的時候,NewStoryController負責經過Ember Data API將報道的數據持續化到存儲層。請參閱文檔瞭解詳情。

  4. 模版:模板向用戶展現應用的界面。每一個應用都有一個默認的應用模板。

Ember的Chrome插件

EmberJS提供了一個Chrome插件,所以調試ember應用很容易。這個插件能夠在 chrome web store 下載安裝。能夠查看Ember團隊作的視頻瞭解chrome插件的詳情。

Github倉庫

今天的示例程序的代碼可從github取得。

第一步 下載新手套裝

ember提供了一套新手裝備,所以開始使用框架很是簡單。新手套裝包括了須要用到的javascript文件(ember-*.jsjquery-*.jshandlerbars-*.js)以及示例應用。下載新手套裝,解壓縮,最後重命名爲getbookmarks

wget https://github.com/emberjs/starter-kit/archive/v1.1.2.zip
unzip v1.1.2.zip 
mv starter-kit-1.1.2/ getbookmarks

在瀏覽器中打開index.html,你會看到以下頁面:

Ember Starter Kit

第二步 啓用GruntJS監視

這一步是可選的,不過若是你作了這步,那麼你的生活質量將大大提升。若是你決定跳過這步,那麼每次你作了改動以後都須要刷新瀏覽器。在第7天的文章,我討論了GruntJS的在線重載功能。我沒有在EmberJS裏找到任何自動重載的功能,所以我決定使用GruntJS的livereload來提升效率。你須要Node、NPM和Grunt-CLI。請參考我第5天第7天的文章瞭解詳情。

getbookmarks文件夾內建立package.json,內容以下:

{
  "name": "getbookmarks",
  "version": "0.0.1",
  "description": "GetBookMarks application",
  "devDependencies": {
    "grunt": "~0.4.1",
    "grunt-contrib-watch": "~0.5.3"
  }
}

建立Gruntfile.js,內容以下:

module.exports = function(grunt) {


  grunt.initConfig({

    watch :{
      scripts :{
        files : ['js/app.js','css/*.css','index.html'],
        options : {
          livereload : 9090,
        }
      }
    }

  });

  grunt.loadNpmTasks('grunt-contrib-watch');

  grunt.registerTask('default', []);  
};

使用npm安裝依賴:

npm install grunt --save-dev
npm install grunt-contrib-watch --save-dev

index.html的頭部加入:

<script src="http://localhost:9090/livereload.js"></script>

調用grunt watch命令,同時在你的默認瀏覽器中打開index.html

; grunt watch
Running "watch" task
Waiting...OK

修改index.html,無需刷新就能看到改變:

GruntJS LiveReload And EmberJS in Action.png

第三步 理解新手模板應用

在新手模板中,除了css以外,有兩個和應用相關的文件——index.htmlapp.js。爲了理解模板應用的做用,咱們須要理解app.js

App = Ember.Application.create();

App.Router.map(function() {
  // put your routes here
});

App.IndexRoute = Ember.Route.extend({
  model: function() {
    return ['red', 'yellow', 'blue'];
  }
});

解釋下以上的代碼:

  1. 第一行建立了一個Ember應用的實例。

  2. 使用App.Route.map定義應用的路由。每一個Ember應用都有一個默認路由Index,綁定到/。因此,當調用/路由的時候,index模板將被渲染。index模板由index.html定義。感受到了不少「約定大於配置」了吧?

  3. 在Ember中,每一個模板都有一個model做爲支持。路由負責制定哪一個mobdel支持哪一個模板。在上述app.js中,IndexRoute返回一個字符串數組,做爲index模板的model。index模板迭代這個數組而後渲染一個列表。

第四步 移除新手模板代碼

移除js/app.js中的代碼,而後用如下內容替換:

App = Ember.Application.create();

App.Router.map(function() {
  // put your routes here
});

相應地,將index.html的內容替換爲:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>GetBookMarks -- Share your favorite links online</title>
  <link rel="stylesheet" href="css/normalize.css">
  <link rel="stylesheet" href="css/style.css">
  <script src="http://localhost:9090/livereload.js"></script>
</head>
<body>

  <script type="text/x-handlebars">
    {{outlet}}
  </script>

  <script type="text/x-handlebars" data-template-name="index">

  </script>
  <script src="js/libs/jquery-1.9.1.js"></script>
  <script src="js/libs/handlebars-1.0.0.js"></script>
  <script src="js/libs/ember-1.1.2.js"></script>
  <script src="js/app.js"></script>

</body>
</html>

第五步 添加Twitter Bootstrap

咱們將使用twitter bootstrap來給應用添加樣式。從官網下載twitter bootstrap包,而後複製bootstrap.css到css文件夾,同時複製字體文件夾。

接着在index.html中加入bootstrap.css,在頁首使用一個固定位置的導航條。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>GetBookMarks -- Share your favorite links online</title>
  <link rel="stylesheet" href="css/normalize.css">
  <link rel="stylesheet" type="text/css" href="css/bootstrap.css">
  <link rel="stylesheet" href="css/style.css">
  <script src="http://localhost:9090/livereload.js"></script>
</head>
<body>

  <script type="text/x-handlebars">
    <nav class="navbar navbar-default navbar-fixed-top" role="navigation">
      <div class="container">
        <div class="navbar-header">
          <a class="navbar-brand" href="#">GetBookMarks</a>
        </div>


      </div>
    </nav>
    <div id="main" class="container">
      {{outlet}}
    </div>
  </script>

  <script type="text/x-handlebars" data-template-name="index">

  </script>

  <script src="js/libs/jquery-1.9.1.js"></script>
  <script src="js/libs/handlebars-1.0.0.js"></script>
  <script src="js/libs/ember-1.1.2.js"></script>
  <script src="js/app.js"></script>

</body>
</html>

上述html中,<script type="text/x-handlebars">表明咱們的應用模板。應用模板使用{{outlet}}標籤爲其餘模板預留位置,其內容取決於url。

css/style.css中加入下面的代碼。這會在正文上方添加一個40px的空白。這樣才能正確地渲染固定位置的導航條。

body{
    padding-top: 40px;
}

第五步 提交新報道

咱們將開始實現提交新報道的功能。Ember建議你圍繞着URL思考。當用戶訪問#/story/new的時候,會展現一個表單。

App.Router.Map中增長一個綁定#/story/new的新路由:

App.Router.map(function() {
  this.resource('newstory' , {path : 'story/new'});
});

接着咱們在index.html中添加一個渲染表單的newstory模板:

<script type="text/x-handlebars" id="newstory">
    <form class="form-horizontal" role="form">
      <div class="form-group">
        <label for="title" class="col-sm-2 control-label">Title</label>
        <div class="col-sm-10">
          <input type="title" class="form-control" id="title" name="title" placeholder="Title of the link" required>
        </div>
      </div>
      <div class="form-group">
        <label for="excerpt" class="col-sm-2 control-label">Excerpt</label>
        <div class="col-sm-10">
          <textarea class="form-control" id="excerpt" name="excerpt" placeholder="Short description of the link" required></textarea>
        </div>
      </div>

      <div class="form-group">
        <label for="url" class="col-sm-2 control-label">Url</label>
        <div class="col-sm-10">
          <input type="url" class="form-control" id="url" name="url" placeholder="Url of the link" required>
        </div>
      </div>
      <div class="form-group">
        <label for="tags" class="col-sm-2 control-label">Tags</label>
        <div class="col-sm-10">
          <textarea id="tags" class="form-control" name="tags" placeholder="Comma seperated list of tags" rows="3" required></textarea>
        </div>
      </div>
      <div class="form-group">
        <label for="fullname" class="col-sm-2 control-label">Full Name</label>
        <div class="col-sm-10">
          <input type="text" class="form-control" id="fullname" name="fullname" placeholder="Enter your Full Name like Shekhar Gulati" required>
        </div>
      </div>
      <div class="form-group">
        <div class="col-sm-offset-2 col-sm-10">
          <button type="submit" class="btn btn-success" {{action 'save'}}>Submit Story</button>
        </div>
      </div>
  </form>
  </script>

訪問#/story/new便可查看錶單:

Submit Story Form

接着咱們在導航條中添加一個連接,這樣訪問報道提交表單就很容易。替換一下nav元素:

<nav class="navbar navbar-default navbar-fixed-top navbar-inverse" role="navigation">
      <div class="container">
        <div class="navbar-header">
          <a class="navbar-brand" href="#">GetBookMarks</a>

        </div>
        <ul class="nav navbar-nav pull-right">
            <li>{{#link-to 'newstory'}}<span class="glyphicon glyphicon-plus"></span> Submit Story{{/link-to}}</li>
        </ul>

      </div>
    </nav>

注意上面咱們用{{#link-to}}建立了一個指向路由的連接。請參閱文檔瞭解詳情。

表單已經有了,接下來要添加HTML 5本地存儲的功能。爲了添加本地存儲支持,咱們須要首先下載Ember DataLocal Storage Adapter JavaScript文件。將這些文件放在js/libs下。接着,在index.html中添加這些script標籤。

<script src="js/libs/jquery-1.9.1.js"></script>
<script src="js/libs/handlebars-1.0.0.js"></script>
<script src="js/libs/ember-1.1.2.js"></script>
<script src="js/libs/ember-data.js"></script>
<script src="js/libs/localstorage_adapter.js"></script>
<script src="js/app.js"></script>

如前所述,Ember Data是一個客戶端的ORM實現,它使在底層存儲進行CRUD操做很容易。這裏咱們將使用LSAdapter。在app.js中加入:

App.ApplicationAdapter = DS.LSAdapter.extend({
  namespace: 'stories'
});

接着是定義model。一篇報道須要有url、title(標題)、fullname(提交報道的用戶的全名)、excerpt(摘要),以及SubmittedOn(日期)信息。在下面的模型中,咱們使用了字符串和日期類型。適配器默認支持的屬性類型爲字符串、數字、布爾值和日期。

App.Story = DS.Model.extend({
    url : DS.attr('string'),
    tags : DS.attr('string'),
    fullname : DS.attr('string'),
    title : DS.attr('string'),
    excerpt : DS.attr('string'),
    submittedOn : DS.attr('date')

});

接着咱們編寫NewstoryController來持久化內容:

App.NewstoryController = Ember.ObjectController.extend({

 actions :{
    save : function(){
        var url = $('#url').val();
        var tags = $('#tags').val();
        var fullname = $('#fullname').val();
        var title = $('#title').val();
        var excerpt = $('#excerpt').val();
        var submittedOn = new Date();
        var store = this.get('store');
        var story = store.createRecord('story',{
            url : url,
            tags : tags,
            fullname : fullname,
            title : title,
            excerpt : excerpt,
            submittedOn : submittedOn
        });
        story.save();
        this.transitionToRoute('index');
    }
 }
});

以上代碼展現瞭如何從獲取表單中的值,而後使用store API在內存中建立記錄。爲了在localstorage中存儲記錄,咱們須要調用Story對象的save方法。最後,咱們將用戶重定向到index路由。

接着咱們測試下這個應用,建立一個新的報道,接着打開Chrome開發者工具,在資源區域你能夠查看這則報道。

第六步 顯示全部報道

接着咱們要作的是,當用戶訪問首頁的時候,展現全部報道。

正如我以前提到的,路由負責詢問model。咱們將加上IndexRoute,它會找出本地存儲中保存的全部報道。

App.IndexRoute = Ember.Route.extend({
    model : function(){
        var stories = this.get('store').findAll('story');
        return stories;
    }
});

每一個路由支持一個模板。IndexRoute支持index模板,所以咱們須要修改index.html

<script type="text/x-handlebars" id="index">
    <div class="row">
      <div class="col-md-4">
        <table class='table'>
          <thead>
            <tr><th>Recent Stories</th></tr>
          </thead>
          {{#each controller}}
            <tr><td>

              {{title}}

            </td></tr>

          {{/each}}
        </table>
      </div>
      <div class="col-md-8">
        {{outlet}}
      </div>
    </div>
  </script>

如今訪問/,咱們會看到一個報道的列表:

GetBookMarks View All Stories

還有一個問題,報道沒有按照時間順序排列。咱們將建立一個IndexController負責排序。咱們指定依照submittedOn屬性倒序排列,以確保新的報道出如今上面。

App.IndexController = Ember.ArrayController.extend({
    sortProperties : ['submittedOn'],
    sortAscending : false
});

修改以後,咱們會看到按照submittedOn屬性排序的報道。

Stories Sorted by Date

第七步 查看單獨的報道

最後要實現的功能是:用戶點擊某則報道的時候會看到詳細信息。咱們加一個路由:

App.Router.map(function() {
    this.resource('index',{path : '/'},function(){
        this.resource('story', { path:'/stories/:story_id' });
    });

    this.resource('newstory' , {path : 'story/new'});

});

以上的代碼展現瞭如何嵌套路由。

:story_id部分叫作動態字段,由於相應的報道 id會被注入URL。

而後咱們添加根據報道id獲取報道的StoryRoute。

App.StoryRoute = Ember.Route.extend({
    model : function(params){
        var store = this.get('store');
        return store.find('story',params.story_id);
    }
});

最後,咱們更新下index.html,給每一個報道添加連接:

<script type="text/x-handlebars" id="index">
    <div class="row">
      <div class="col-md-4">
        <table class='table'>
          <thead>
            <tr><th>Recent Stories</th></tr>
          </thead>
          {{#each controller}}
            <tr><td>
            {{#link-to 'story' this}}
              {{title}}
            {{/link-to}}
            </td></tr>

          {{/each}}
        </table>
      </div>
      <div class="col-md-8">
        {{outlet}}
      </div>
    </div>
  </script>

  <script type="text/x-handlebars" id="story">
    <h1>{{title}}</h1>
    <h2> by {{fullname}} <small class="muted">{{submittedOn}}</small></h2>
    {{#each tagnames}}

        <span class="label label-primary">{{this}}</span>

      {{/each}}
    <hr>
    <p class="lead">
      {{excerpt}}
    </p>

  </script>

修改完畢地後,能夠在瀏覽器中直接看到結果。

Story View

第八步 爲submittedOn日期添加格式

Ember下有輔助函數的概念。全部Handlebars模板均可以調用輔助函數。

咱們將使用moment.js庫爲日期添加格式。將如下代碼加入index.html。

<script src="http://cdnjs.cloudflare.com/ajax/libs/moment.js/2.4.0/moment.min.js"></script>

接着咱們將定義咱們的第一個輔助函數,該函數將日期轉爲人類可讀的形式:

Ember.Handlebars.helper('format-date', function(date){
    return moment(date).fromNow();
});

最後咱們在報道模板中加入format-data輔助函數。

<script type="text/x-handlebars" id="story">
    <h1>{{title}}</h1>
    <h2> by {{fullname}} <small class="muted">{{format-date submittedOn}}</small></h2>
    {{#each tagnames}}

        <span class="label label-primary">{{this}}</span>

      {{/each}}
    <hr>
    <p class="lead">
      {{excerpt}}
    </p>

  </script>

報道頁面的效果以下:

final result

今天就到這裏了。持續反饋。


原文 Day 19: Ember--The Missing EmberJS Tutorial
翻譯 SegmentFault

相關文章
相關標籤/搜索