[譯] 第十九天: Ember - 缺失的EmberJS指南

前言

到目前爲止 ,這個系列咱們探討了Bower, AngularJS, GruntJS, PhoneGap, 和MeteorJS JavaScript技術,今天,我決定學習一個叫Ember的框架。本文,咱們來學習用Ember建立一個單頁網摘網頁。這個指南會寫兩篇文章,第一篇講客戶端和長期保存數據到HTML 5本地存儲中,第二篇講用RESTful後端部署到OpenShift. 接下來的幾天會完成第二篇。 javascript

程序用例

本文,咱們來開發一個網摘程序,容許用戶發佈和共享連接,你可訪問在線應用。這個應用能夠作如下事:css

  1. 當用戶打開應用'/' url,能夠看到一個文章列表,以提交日期排序。 
  2. 用戶點擊任意文章如#/storites/d6p88, 他能夠看到文章由誰提交的,何時提交的和文章摘要。 

  3. 最後,用戶能夠到頁面#/story/new提交新內容,這會保存在用戶瀏覽器本地緩存中。 

什麼是Ember?

Ember是一個客戶端JavaScript MV* 框架,用來開發艱鉅的Web程序,它有jQueryHandlebars庫依賴,若是你用過Backbone, 那你能夠發現Ember如同執拗的Backbone或者Backbone++. 若是你遵循它的命名規範,Ember能夠爲你作不少事,Ember.js嚴格遵循命名規範,假如咱們應用中有一個url路徑 /stories, 那咱們會有如下:html

  • 一個stories模板
  • 一個StoriesRoute
  • 一個StoriesController 

要更好的瞭解Ember命名規範,參考文檔。 html5

Ember核心概念

這部分咱們關注EmberJS四個主要核心概念,今天的demo會用到。java

  1. Model: 模型呈現了咱們要展現給用戶的程序域對象,以上咱們討論的程序用例中,一篇文章表明一個模型,文章隨同它的屬性如標題,url等組成了模型,模型能夠重置或更新,靠jQuery從服務器加載JSON數據或者程序使用Ember數據. Ember數據是客戶端ORM實施,使對底層長期存儲執行CRUD操做更簡單。Ember數據提供了一個倉庫接口,可配置一系列提供的適配器。兩個核心適配器是RESTAdapter和FixtureAdapter. 本文咱們用LocalStorage適配器,用於長期保存數據到HTML 5 本地存儲。詳情請參考文檔
  2. RouterRoute: 路由器用於指定程序路由,一個url對應一個路由。例如,用戶打開'/#/story/new', 那newstroy模板就會被加載,這個newstory模板表明了一個HTML表格,用戶能夠經過建立Ember.Route子類自定義路由。以上示例中,假設用戶打開'/#/story/new', 想在newstory加載默認模型,NewStoryRoute會給一個默認模型給newstory. 詳情請參考文檔
  3. Controller: 控制器可作兩件事:一是裝飾路由返回的模板,二是艦艇用戶執行動做。例如,當用戶用相關數據提交文章後,NewStoryController會用Ember Data API將數據持久保存到底層存儲中,詳情請參考文檔
  4. Template: 模板表明程序的用戶接口,每一個程序都有一個默認模板,當程序啓動時被加載。標頭,頁腳,導航和其餘經常使用內容應該放置在這個模板裏。Ember.js用Handlebars 模板庫來加強程序用戶接口。 

Ember Chrome擴展

EmberJS也提供了一個chrome擴展,使程序易於調試,這個擴展在chrome web store有,更多瞭解請參考Ember團隊的簡短視頻。 jquery

Github倉庫

今天的demo放在github: day19-emberjs-demo. git

第一步:下載starter kit

Ember提供了一個starter kit讓咱們快速入門。Starter kit包含須要的javascript文件(ember-*.js, jquery-*.js, handlerbars-*.js)和示例程序。下載並解壓,最後按如下方式重命名爲getbookmarks, getbookmarks是程序名字。github

$ wget https://github.com/emberjs/starter-kit/archive/v1.1.2.zip

$ unzip v1.1.2.zip

$ mv starter-kit-1.1.2/ getbookmarks
View Code

 

用你喜歡的流行瀏覽器(Chrome或者Firefox)打開index.html, 就能夠看到示例程序。 web

第二步:激活GruntJS Watch(可選)

這步是可選的,不過若是你執行了,會帶給你驚喜。要是決定跳過,那每次用瀏覽器打開index.html或者咱們作了任何改動後請從新加載你的瀏覽器。第七天的時候,咱們討論了用GruntJS實時加載自動更新,EmberJS裏我沒找到自動加載的功能,因此我決定創造性的用GruntJS實時加載。你須要安裝Node, NPM, Grunt-CLI, 請參考我第天的博客,裏面有詳述。 ajax

在getbookmarks文件夾新建package.json文件,粘貼如下內容。

{

  "name": "getbookmarks",

  "version": "0.0.1",

  "description": "GetBookMarks application",

  "devDependencies": {

    "grunt": "~0.4.1",

    "grunt-contrib-watch": "~0.5.3"

  }

}
View Code

 

再新建個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', []); 

};
View Code

 

用npm安裝依賴。

$ npm install grunt --save-dev

$ npm install grunt-contrib-watch --save-dev
View Code

 

而後調用grunt watch命令,在你瀏覽器裏打開index.html.

$ grunt watch

Running "watch" task

Waiting...OK
View Code

 

對index.html作些改動,不刷新瀏覽器你就能夠看到改動生效了。 

第三步:理解開始模板程序

在開始模板裏,有兩個程序相關文件(除了css)-index.html和app.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'];

  }

});
View Code

 

以上代碼:

  1. 第一行 App = Ember.Application.create(); 建立了Ember程序實例,它將建立Ember程序新實例,使它在瀏覽器的JavaScript環境做爲參數可用。
  2. App.Router.map用於定義程序路由,每一個Ember程序有一個默認路由調用'/' url下可用的引用,當'/'路由被調用了,index模板就會被加載。基於程序url模板加載,不少默認配置,index模板定義在index.html裏。
  3. 在Ember裏,每一個模板由一個模型支持,一個路由指定模板該由哪一個模型支持,以上app.js中,IndexRoute爲index模板做爲模型返回一個字符串數組,index模板只是遞歸這個數組和顯示列表。 

第四步:移除開始模板代碼

請刪除js/app.js文件的全部內容,粘貼如下內容。

App = Ember.Application.create(); 

App.Router.map(function() {

  // put your routes here

});
View Code

 

一樣的替換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>
View Code

 

第五步:添加Twitter Bootstrap

咱們用twitter boostrap來做爲程序樣式,從官網下載最新Twitter Bootstrap包,複製bootstrap.css到css文件夾,fonts文件夾到getbookmarks文件夾。 

接下來,添加bootstrap.css樣式表到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" 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>
View Code

 

以上html代碼,<script type="text/x-handlebars"> 表明咱們application模板,這個模板有一個{{outlet}}標籤控制全部其餘模板,會根據url改變。 

同時添加如下css到css/style.css, 它爲主體添加40px的上邊距,這爲適當加在主體固定頂部導航欄設置的。

body{

    padding-top: 40px;

}
View Code

 

第六步:提交新文章

開始執行提交新文章功能,在Ember裏,建議你根據URL(s)而定,當用戶瀏覽'#/story/new' url, 那麼一個表格就應該呈現給用戶。 

在App.Router.Map裏給'#/story/new' 添加一個新的路由。

App.Router.map(function() {

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

});
View Code

 

而後在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>
View Code

 

要看這個表格,只需打開 '#/story/new'. 

而後在導航欄添加一個連接,讓咱們輕鬆導航到文章提交表格,用如下內容替代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>
View Code

 

以上須要注意的是{{#link-to}}的用法幫助,{{#link-to}}幫助用於建立路由的連接,詳情請參考文檔 

如今咱們能夠瀏覽表格,來添加存儲到HTML 5本地存儲的功能,要添加本地存儲支持,須要先下載Ember Data本地存儲適配器,放到js/libs文件夾,而後添加腳本標籤到index.html. 

<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>
View Code

 

前面討論過,Ember數據是客戶端ORM實施,使對底層長期存儲執行CRUD操做更簡單。這裏咱們用LSAdapter(Local Storage Adapter). 添加如下內容到app.js.

App.ApplicationAdapter = DS.LSAdapter.extend({

  namespace: 'stories'

});
View Code

 

而後定義模型,一個文章應該有一個url, 標題,提交文章的用戶的全名,文章摘要,提交時間。咱們也能夠指定屬性的類型,以下模型,咱們用字符型和日期格式。默認適配器支持屬性類型有字符型,數字,布爾型和日期格式。

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') 

});

 
View Code

 

接下來寫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');

    }

 }

});
View Code

 

以上代碼獲取全部表格值,而後用store API建立一個in-memory記錄,要保存記錄到本地,咱們須要在Story對象上調用save方法,最後,指向index路由。 

如今你能夠測試程序,建立一個新文章,而後到Chrome 開發者工具,在源文件部分查看文章。 

第七步:顯示全部文章

接下來的邏輯步驟是當用戶查看主頁時顯示全部文章。 

如我以前提到的,一個路由對應一個查詢模型,咱們來添加IndexRoute用於在本地存儲找到全部保存的文章。

App.IndexRoute = Ember.Route.extend({

    model : function(){

        var stories = this.get('store').findAll('story');

        return stories;

    }

});

 
View Code

 

每一個路由支持一個模板,這個IndexRoute支持index 模板,在index.html添加index模板。 

<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>
View Code

 

若是咱們訪問你的程序主頁,能夠看到文章列表。每一個循環遞歸基於文章集合,這裏,控制器至關於indexController.

如今咱們能夠在'/'路由看到文章。

有一個問題是文章不是按日期排序的,要按提交日期排序,須要建立IndexController用於負責模板的排序,咱們說了想按提交日期排序,而且按降序來確保最新的在頂部。

App.IndexController = Ember.ArrayController.extend({

    sortProperties : ['submittedOn'],

    sortAscending : false

});
View Code

 

更新以後,能夠看到是按提交日期排序了。 

第八步:查看單獨文章

這個程序咱們要作的最後一個功能是當用戶點擊一篇文章,他應該能夠看到詳細內容,要執行這個須要添加如下路由。

App.Router.map(function() {

    this.resource('index',{path : '/'},function(){

        this.resource('story', { path:'/stories/:story_id' });

    }); 

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

});
View Code

 

以上代碼是一個嵌套路由示例。 

:story_id部分調用動態字段,由於相應的文章id會加到URL. 

接下來咱們添加StoryRoute用於找到文章id對應的文章。

App.StoryRoute = Ember.Route.extend({

    model : function(params){

        var store = this.get('store');

        return store.find('story',params.story_id);

    }

});
View Code

 

最後,更新index.html從index模板連接到每一個文章,用{{#link-to}}在每一個循環裏完成。

<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>
View Code

 

更新好後,能夠在你瀏覽器裏看到這些更新。 

第九步:格式化提交日期的格式

Ember有助手概念,這些助手功能能夠從任意Handlebars模板調用。 

咱們用moment.js庫格式化日期,添加如下內容到index.html.

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

 

而後,定義第一個助手用於格式化日期便於咱們閱讀。

Ember.Handlebars.helper('format-date', function(date){

    return moment(date).fromNow();

});
View Code

 

最後添加format-date助手。

<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>
View Code

 

你能夠看到這些更新。 

這就是今天的內容,繼續給反饋吧。

原文:https://www.openshift.com/blogs/day-19-ember-the-missing-emberjs-tutorial

相關文章
相關標籤/搜索