到目前为止,我们这一系列文章涉及了Bower、AngularJS、GruntJS、PhoneGap和MeteorJS 这些JavaScript技术。今天我打算学习一个名为Ember的框架。本文将介绍如何用Ember创建一个单页面的社交化书签应用。本教程将包括两篇:第1篇介绍客户端代码和用HTML 5本地存储持久保存数据,第2篇中我们将使用一个部署在OpenShift上的REST后端。过几天我会写第2篇。

应用

我们将开发一个社交化书签应用,允许用户提交和分享链接。你可以在这里查看这个应用。这个应用可以做到:

  • 当用户访问/时,他会看到以提交时间排序的报道列表。

  • 当用户访问某个书签时,例如#/stories/d6p88,用户会看到关于这个报道的信息,例如是谁提交的,何时提交的,以及文章的摘要。

  • 最后,当用户通过#/story/new提交新报道时,内容会存储在用户浏览器的本地存储上。

什么是Ember?

Ember是一个客户端的JavaScript MV* 框架,用来构建野心勃勃的web应用。它依赖于jQuery和Handlebars库。如果你曾经在Backbone下工作,那么你会发现Ember是一个武断的Backbone,或者Backbone++。Ember可以为你完成很多事情,如果你遵循它的命名约定的话。Ember.js在这方面很突出。因此,如果我们在应用中加入了url路由和报道,那么我们就有了这些:

  • 报道的模板
  • StoriesRoute
  • StoriesController

请参考命名约定文档来理解Ember的命名约定。

Ember核心概念

本节将介绍我们的示例应用中将涉及的四个EmberJS的核心概念:

  1. 模型:模型代表我们展示给用户的应用领域内的对象。在上述例子中,一个报道就代表一个模型。报道,加上它的属性,包括标题、url等,构成一个模型。模型可以通过jQuery加载服务器端的JSON数据的方式来获取和更新,也可以通过Ember Data来获取和更新。Ember Data是一个客户端的ORM实现,可以利用它方便地对底层的持久性存储进行CRUD操作。Ember Data提供一个仓库接口,可以借助提供的一些适配器配置。Ember Data提供的两个核心适配器是RESTAdapter和FixtureAdapter。在本文中,我们将使用LocalStorage适配器,该适配器将数据持久化为 HTML 5 的LocalStorage。请参阅此文档了解详情。

  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,你会看到如下页面:

第二步 启用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,无需刷新就能看到改变:

第三步 理解新手模板应用

在新手模板中,除了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即可查看表单:

接着我们在导航条中添加一个链接,这样访问报道提交表单就很容易。替换一下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 Data和Local 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>

现在访问/,我们会看到一个报道的列表:

还有一个问题,报道没有按照时间顺序排列。我们将创建一个IndexController负责排序。我们指定依照submittedOn属性倒序排列,以确保新的报道出现在上面。

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

修改之后,我们会看到按照submittedOn属性排序的报道。

第七步 查看单独的报道

最后要实现的功能是:用户点击某则报道的时候会看到详细信息。我们加一个路由:

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>

修改完毕地后,可以在浏览器中直接看到结果。

第八步 为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>

报道页面的效果如下:

今天就到这里了。持续反馈。


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

Day 19: EmberJS 入门指南相关推荐

  1. 入门指南目录页 -PaddlePaddle 飞桨 入门指南 FAQ合集-深度学习问题

    入门指南目录页 -PaddlePaddle 飞桨 入门指南 FAQ合集 GT_Zhang关注 0.1012019.08.01 18:43:34字数 1,874阅读 795 Hi,欢迎各位来自Paddl ...

  2. 游戏风格化角色创建入门指南视频教程

    游戏风格化角色创建入门指南视频教程 时间 33小时 包括项目文件 1920X1080 MP4 语言:英语+中文字幕(根据原英文字幕机译更准确)+原英文字幕 游戏中的风格化角色创建入门指南  百度一下 ...

  3. mongoDB 入门指南、示例

    http://www.cnblogs.com/hoojo/archive/2011/06/01/2066426.html mongoDB 入门指南.示例 上一篇:简单介绍mongoDB 一.准备工作 ...

  4. 【AI参赛经验】深度学习入门指南:从零开始TinyMind汉字书法识别——by:Link

    各位人工智能爱好者,大家好! 由TinyMind发起的#第一届汉字书法识别挑战赛#正在火热进行中,比赛才开始3周,已有数只黑马冲进榜单.目前TOP54全部为90分以上!可谓竞争激烈,高手如林.不是比赛 ...

  5. numpy 点乘_Numpy入门指南

    Numpy 入门指南 array基本属性 Numpy的主要对象是同构多维数组.它是一个元素表,所有类型都相同,由非负整数元组构成索引. Numpy的数组类被调用为ndarray.存在以下属性: •nd ...

  6. 博士大佬为机器学习总结的人工智能入门指南!

    今天给大家推荐一位认识的好朋友:top985高校AI博士(本硕博985),CSDN博客专家,其开源了周志华西瓜书<机器学习>纯手推笔记!荣登趋势榜,标星600+ Github | 博士大佬 ...

  7. RMAN快速入门指南

    RMAN快速入门指南   1. What is RMAN? 什么是RMAN?    4 2. Terminology 专业词汇解释    4 2.1. Backup sets 备份集合    4 2. ...

  8. AlphaGo制胜绝招:蒙特卡洛树搜索入门指南

    作者 | int8.io 编译 | 张健欣 编辑 | Emily Chen AI 前线导读:本文是一篇关于蒙特卡洛树搜索的入门指南,介绍什么是蒙特卡洛树搜索及其各个细节的基本概念,然后通过一个简单的例 ...

  9. PYTHON系列-从零开始的爬虫入门指南

    入门 0.准备工作 需要准备的东西: Python.scrapy.一个IDE或者随便什么文本编辑工具. 1.技术部已经研究决定了,你来写爬虫. 随便建一个工作目录,然后用命令行建立一个工程,工程名为m ...

最新文章

  1. 来客推商城V3多用户uni-app商城源码如何搭建?盈利模式怎么样的?
  2. day 33(套接字,TCP 与 UDP)
  3. 24个很酷的 CSS3 文本效果示例及教程
  4. C语言求网格的最大不重复路径数的算法(附完整源码)
  5. 最大权闭合子图(最小割)
  6. install scrapy with pip and easy_install
  7. MSSQL 触发器 暂停 和 启动
  8. 职场:如何成为PPT高手【01思维篇】
  9. python有趣的简单代码-python有趣代码
  10. backdrop-filter filter
  11. opencv中 idft与 mathlab中 ifft2结果不对应的解决方案
  12. 基于RTEMS的阿波罗STM32F429开发板(正点原子)适配
  13. 耐心排序之最长递增子序列(LIS)
  14. 使用NanoHTTPD在Android上建立本地服务器
  15. C-Lodop云打印服务系统存在任意文件读取漏洞
  16. Android常见设计模式——代理模式(Proxy Pattern)(二)
  17. 谷歌小语种外链代发,谷歌外链购买平台哪个好?
  18. linux系统无法启动 备份恢复,Linux运维 第二阶段 (十四) 备份与恢复及常见故障排除...
  19. 服务器2008系统如何设置休眠时间,Win7休眠和睡眠怎么开启(Win2008)
  20. [随笔]2019年总结 2020年计划

热门文章

  1. TalkingData大规模机器学习的应用
  2. 常见Web技术之间的关系,你了解多少?
  3. 分布式项目启动时报错:Duplicate spring bean id XXX
  4. 织梦调用css的标签,织梦dede常用的调用标签(个人总结)
  5. TabLayout-Android M新控件
  6. CSDN内容颜色、位置以及图片大小改变
  7. sql 删除重复记录保留一条_从零学会SQL·二——简单查询
  8. linux 插件 概念,服务端概念功能介绍
  9. springboot 获取application参数_LOOK ! SpringBoot的外部化配置最全解析
  10. linux time shell统计脚本运行时间