[译] 第十九天: 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

相关文章
相关标签/搜索