zoukankan      html  css  js  c++  java
  • [译] 第十九天: Ember

    前言

    到目前为止 ,这个系列我们探讨了Bower, AngularJS, GruntJS, PhoneGap, 和MeteorJS JavaScript技术,今天,我决定学习一个叫Ember的框架。本文,我们来学习用Ember创建一个单页网摘网页。这个指南会写两篇文章,第一篇讲客户端和长期保存数据到HTML 5本地存储中,第二篇讲用RESTful后端部署到OpenShift. 接下来的几天会完成第二篇。 

    程序用例

    本文,我们来开发一个网摘程序,允许用户发布和共享链接,你可访问在线应用。这个应用可以做以下事:

    1. 当用户打开应用'/' url,可以看到一个文章列表,以提交日期排序。 
    2. 用户点击任意文章如#/storites/d6p88, 他可以看到文章由谁提交的,什么时候提交的和文章摘要。 

    3. 最后,用户可以到页面#/story/new提交新内容,这会保存在用户浏览器本地缓存中。 

    什么是Ember?

    Ember是一个客户端JavaScript MV* 框架,用来开发艰巨的Web程序,它有jQueryHandlebars库依赖,如果你用过Backbone, 那你可以发现Ember如同固执的Backbone或者Backbone++. 如果你遵循它的命名规范,Ember可以为你做很多事,Ember.js严格遵循命名规范,假如我们应用中有一个url路径 /stories, 那我们会有以下:

    • 一个stories模板
    • 一个StoriesRoute
    • 一个StoriesController 

    要更好的了解Ember命名规范,参考文档。 

    Ember核心概念

    这部分我们关注EmberJS四个主要核心概念,今天的demo会用到。

    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团队的简短视频。 

    Github仓库

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

    第一步:下载starter kit

    Ember提供了一个starter kit让我们快速入门。Starter kit包含需要的javascript文件(ember-*.js, jquery-*.js, handlerbars-*.js)和示例程序。下载并解压,最后按以下方式重命名为getbookmarks, 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
    View Code

    用你喜欢的流行浏览器(Chrome或者Firefox)打开index.html, 就可以看到示例程序。 

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

    这步是可选的,不过如果你执行了,会带给你惊喜。要是决定跳过,那每次用浏览器打开index.html或者我们做了任何改动后请重新加载你的浏览器。第七天的时候,我们讨论了用GruntJS实时加载自动更新,EmberJS里我没找到自动加载的功能,所以我决定创造性的用GruntJS实时加载。你需要安装Node, NPM, Grunt-CLI, 请参考我第天的博客,里面有详述。 

    在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

  • 相关阅读:
    vue-router 子路由时,父级component设置
    解决vscode运行yarn提示错误
    Can't resolve 'readable-stream/transform.js' in
    mongoose聚合——$group
    mongoose聚合——$project
    ubuntu在apt update时出现错误: http://xx/ubuntu bionic InRelease 无法解析域名“xxx”
    使用ElasticSearch问题记录
    Err:1 http://mirrors.aliyun.com/ubuntu xenial InRelease Temporary failure ...问题及踩到的坑
    mongoose-exists检查一个数组的元素是否在集合中已存在
    jQuery插件开发之datalist
  • 原文地址:https://www.cnblogs.com/endless-on/p/3507919.html
Copyright © 2011-2022 走看看