到目前为止 ,这个系列我们探讨了Bower, AngularJS, GruntJS, PhoneGap, Meteor, Ember和TimelineJS Javascript技术。今天的30天挑战,我决定学习一款叫Yeoman的高效前端开发工具。本文,我们先了解Yeoman基础,然后用Yeoman开发一个Ember应用,这里不再讲EmberJS基础,你可参考第19天的博客。
什么是Yeoman?
Yeoman是一个开源的高效客户端开发工具,它集成了工具和框架,有助于开发者快速高效并遵循最好的用户体验构建web应用。它的灵感来自Ruby on Rails 概念。Yeoman包含三个工具:
- Yo: 一个基架工具,当你需要开始新项目时为你生成所有架构模板,它避免了样板代码,利于开始新项目和配置grunt任务。
- Grunt: 基于Javascript的命令行构建工具,帮你自动完成需要重复的任务。你可以把它看作Javascript的Make或者Ant. 它可以执行像压缩,编译,单元测试,代码审查等任务,详细内容参考第5天关于GruntJS的博客。
- Bower: 客户端包管理工具,可用作搜索,安装,卸载web资源如Javascript, HTML和CSS. 它不是一款封闭的工具,为使用这种技术的开发者提供了大量选择。详细内容参考第1天关于Bower的博客。
如果你要说服自己学习Yeoman, 可以看看它网站上whyyeoman部分。
前提准备
安装Yeoman之前先安装:
- Node: Yeoman需要NPM. NPM是一个node包管理,绑定在Nodejs安装中,所以,请从 http://nodejs.org 下载最新的node.js.
- Git: 需要git来从git仓库获取有些包的代码,所以,安装git.
准备条件做好后,你可以输入以下命令安装yeoman.
$ npm install -g yeoman
以上命令会全局安装yeoman, -g 代表全局安装,如果你还没装Grunt和bower, 这也会给你安装好。
安装Yeoman Ember GeneratorYeoman依赖Generators完成web基架,对现代Javascript MV*框架有多种generators, 我们用Ember generator. NPM用于安装generators.
$ npm install -g generator-ember.
本文我们开发个网摘程序允许用户发布和分享链接,你可以查看在线程序,和第19天的一样,可以参考之前的用例来了解。
Github仓库今天的demo放在 github: day24-yeoman-emberjs-demo.
创建Ember程序讲完基础后我们来开始开发程序。
在机器上新建目录,更改程序目录。
$ mkdir getbookmarks$ cd getbookmarks
然后运行yo ember, 它会问你是否想用Twitter Bootstrap, 一般我的程序都用它,所以我输入Yes.
$ yo ember_-----_| ||--(o)--| .--------------------------.--------- | Welcome to Yeoman, |( __ ) | ladies and gentlemen! |/___A___\ '__________________________'| ~ |__'.___.'__[?] Would you like to include Twitter Bootstrap for Sass? Yes
输入yes后,Yeoman会给出Ember程序架构,自动运行bower和npm安装程序所需的依赖。
来看看Yeoman生成的Ember程序,这个程序有三个顶层目录:app, node_modules, test. 还有配置文件--.bowerrc, .gitignore, .jshintrc, Gruntfile.js, package.json. 程序结构如图。
所有程序特定代码都砸app目录,这个程序架构遵循Ember最佳体验。
- Bower_components目录存放所有客户端依赖,如Ember, Twitter Boostrap等,Bower在这个文件夹安装所有依赖,这个路径可以改到 .bowerrc文件夹。
- images目录存放所有特定图片,Yeoman优化这个目录的所有图片。
- Index.html文件包含所有ember.js依赖并按序排列,所有bootstrap依赖,和Gruntfile.js用于替换(或者移除)引用到non-optimized脚本或者HTML文件里的格式表单的'build'注释。
- scripts目录包含所有Ember程序控制器,视图,模型和路由。
- styles目录有程序指定的css文件,这个css导入bootstrap格式。
- templates目录包含程序handlebar模板。
现在,运行启动内嵌的预览服务器,grunt服务器采用我第7天讲到的livereload.
$ grunt server
这会在默认浏览器里打开程序。
生成Story模型
第19天开发的GetBookmarks程序有一个Ember模型叫Story,Yeoman subgenerator可用于生成更小的Story模型,要生成Story模型,执行以下命令。
$ yo ember:model Story
输出如下。
create app/scripts/models/story_model.jsinvoke ember:controller:/usr/local/lib/node_modules/generator-ember/model/index.jscreate app/scripts/controllers/stories_controller.jscreate app/scripts/controllers/story_edit_controller.jscreate app/scripts/routes/stories_route.jscreate app/scripts/routes/story_route.jscreate app/scripts/routes/story_edit_route.jsinvoke ember:view:/usr/local/lib/node_modules/generator-ember/controller/index.jscreate app/scripts/views/story_view.jscreate app/scripts/views/story_edit_view.jscreate app/scripts/views/stories_view.jscreate app/templates/story.hbscreate app/templates/story_edit.hbscreate app/templates/stories.hbscreate app/scripts/views/bound_text_field_view.jsinvoke ember:router:/usr/local/lib/node_modules/generator-ember/controller/index.jsconflict app/scripts/router.js
[?] Overwrite app/scripts/router.js? overwriteforce app/scripts/router.js
这会在app/scripts/models 目录下生成story_model.js, 连同还生成相应的视图,控制器和路由。如果你对此不太了解可参照我第19天的博客。
用以下代码更新story_model.
Emberapp.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')
});
请重启Grunt 服务器以使改动生效。
安装Ember LocalStorage适配器我们用HTML 5 LocalStorage存储数据,用bower安装适配器。
$ bower install --save ember-localstorage-adapter
然后更新index.html依赖
<script src&#61;"bower_components/ember-localstorage-adapter/localstorage_adapter.js">script>
同时用以下代码更新app/scripts/store.js.这会用LSAdapter(Local Storage Adapter)而不是FixtureAdapter配置程序。
Getbookmarks.Store &#61; DS.Store.extend();
Getbookmarks.ApplicationAdapter &#61; DS.LSAdapter.extend({namespace: &#39;stories&#39;
});
用以下代码替换router.js.
Getbookmarks.Router.map(function () {this.resource(&#39;index&#39;,{path : &#39;/&#39;});this.resource(&#39;story&#39;, { path: &#39;/story/:story_id&#39; });this.resource(&#39;story_edit&#39;, { path: &#39;/story/new&#39; });
});
以上代码&#xff0c;我们定义了三个路由。
- index路由对应的根路径。
- 查看独立文章用story路由。
- 用story_edit路由新建文章&#xff0c;当用户查看&#39;#/story/new&#39;, 一个表格会显示给用户。
现在添加表格&#xff0c;用于用户打开&#39;#/story/new&#39;时显示&#xff0c;用以下代码更新 app/templates/story_edit.hbs.
<form class&#61;"form-horizontal" role&#61;"form"><div class&#61;"form-group"><label for&#61;"title" class&#61;"col-sm-2 control-label">Titlelabel><div class&#61;"col-sm-10"><input type&#61;"title" class&#61;"form-control" id&#61;"title" name&#61;"title" placeholder&#61;"Title of the link" required>div>div><div class&#61;"form-group"><label for&#61;"excerpt" class&#61;"col-sm-2 control-label">Excerptlabel><div class&#61;"col-sm-10"><textarea class&#61;"form-control" id&#61;"excerpt" name&#61;"excerpt" placeholder&#61;"Short description of the link" required>textarea>div>div><div class&#61;"form-group"><label for&#61;"url" class&#61;"col-sm-2 control-label">Urllabel><div class&#61;"col-sm-10"><input type&#61;"url" class&#61;"form-control" id&#61;"url" name&#61;"url" placeholder&#61;"Url of the link" required>div>div><div class&#61;"form-group"><label for&#61;"tags" class&#61;"col-sm-2 control-label">Tagslabel><div class&#61;"col-sm-10"><textarea id&#61;"tags" class&#61;"form-control" name&#61;"tags" placeholder&#61;"Comma seperated list of tags" rows&#61;"3" required>textarea>div>div><div class&#61;"form-group"><label for&#61;"fullname" class&#61;"col-sm-2 control-label">Full Namelabel><div class&#61;"col-sm-10"><input type&#61;"text" class&#61;"form-control" id&#61;"fullname" name&#61;"fullname" placeholder&#61;"Enter your Full Name like Shekhar Gulati" required>div>div><div class&#61;"form-group"><div class&#61;"col-sm-offset-2 col-sm-10"><button type&#61;"submit" class&#61;"btn btn-success" {{action &#39;save&#39;}}>Submit Storybutton>div>div>form>
现在打开 http://localhost:9000/#/story/new 可以看到提交表格。
更新 StoryEditController save功能&#xff0c;会把文章保存到本地存储中。
Getbookmarks.StoryEditController &#61; Ember.ObjectController.extend({save: function(){var url &#61; $(&#39;#url&#39;).val();var tags &#61; $(&#39;#tags&#39;).val();var fullname &#61; $(&#39;#fullname&#39;).val();var title &#61; $(&#39;#title&#39;).val();var excerpt &#61; $(&#39;#excerpt&#39;).val();var submittedOn &#61; new Date();var store &#61; this.get(&#39;store&#39;);console.log(&#39;Store .. &#39;&#43;store);var story &#61; store.createRecord(&#39;story&#39;,{url : url,tags : tags,fullname : fullname,title : title,excerpt : excerpt,submittedOn : submittedOn});story.save();this.transitionToRoute(&#39;index&#39;);}
});
接下来的功能是实现在侧边栏显示文章列表。
在application_route.js, 我们会从本地存储中获取所有文章。
Getbookmarks.ApplicationRoute &#61; Ember.Route.extend({model : function(){var stories &#61; this.get(&#39;store&#39;).findAll(&#39;story&#39;);return stories;}
});
接下来更新application.hbs加载文章标题和链接&#xff0c;用以下代码更新。
<div><nav class&#61;"navbar navbar-default navbar-fixed-top" role&#61;"navigation"><div class&#61;"navbar-header"><button type&#61;"button" class&#61;"navbar-toggle" data-toggle&#61;"collapse" data-target&#61;".navbar-ex1-collapse"><span class&#61;"sr-only">Toggle navigationspan><span class&#61;"icon-bar">span><span class&#61;"icon-bar">span><span class&#61;"icon-bar">span>button><a class&#61;"navbar-brand" href&#61;"#">GetBookmarksa>div><div class&#61;"collapse navbar-collapse navbar-ex1-collapse"><ul class&#61;"nav navbar-nav pull-right"><li>{{#link-to &#39;story_edit&#39;}}<span class&#61;"glyphicon glyphicon-plus">span> Submit Story{{/link-to}}li>ul>div>nav><div class&#61;"container" id&#61;"main"><div class&#61;"row"><div><div class&#61;"col-md-3"><div class&#61;"well sidebar-nav"><table class&#61;&#39;table&#39;><thead><tr><th>Recent Storiesth>tr>thead>{{#each controller}}<tr><td>{{#link-to &#39;story&#39; this}}{{title}}{{/link-to}}td>tr>{{/each}}table>div>div><div class&#61;"col-md-9">{{outlet}}div>div>div>div>
div>
程序界面会重新加载更新。
查看单独文章
最后一个功能是当用户打开 http://localhost:9000/#/story/:id 会显示单独的文章&#xff0c;:id对应文章id, 用以下代码更新story_route.js.
Getbookmarks.StoryRoute &#61; Ember.Route.extend({model : function(params){var store &#61; this.get(&#39;store&#39;);return store.find(&#39;story&#39;,params.story_id);}
});
用以下代码更新 app/templates/story.hbs.
<h1>{{title}}h1>
<h2> by {{fullname}} <small class&#61;"muted">{{submittedOn}}small>h2>
{{#each tagnames}}<span class&#61;"label label-primary">{{this}}span>
{{/each}}
<hr>
<p class&#61;"lead">{{excerpt}}
p>
最后&#xff0c;运行grunt build命令生成一个分布式程序&#xff0c;grunt build命令使用app目录下的源代码文件&#xff0c;返回到dist下的分布式程序中。
$ grunt build
这就是今天的内容&#xff0c;继续给反馈吧。
原文&#xff1a;https://www.openshift.com/blogs/day-24-yeoman-ember-the-missing-tutorial