前言
到目前为止 ,这个系列我们探讨了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?
如果你要说服自己学习Yeoman, 可以看看它网站上whyyeoman部分。
前提准备
安装Yeoman之前先安装:
- Node: Yeoman需要NPM. NPM是一个node包管理,绑定在Nodejs安装中,所以,请从 http://nodejs.org 下载最新的node.js.
- Git: 需要git来从git仓库获取有些包的代码,所以,安装git.
安装Yeoman
准备条件做好后,你可以输入以下命令安装yeoman.
$ npm install -g yeoman
以上命令会全局安装yeoman, -g 代表全局安装,如果你还没装Grunt和bower, 这也会给你安装好。
安装Yeoman Ember Generator
Yeoman依赖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.js invoke ember:controller:/usr/local/lib/node_modules/generator-ember/model/index.js create app/scripts/controllers/stories_controller.js create app/scripts/controllers/story_edit_controller.js create app/scripts/routes/stories_route.js create app/scripts/routes/story_route.js create app/scripts/routes/story_edit_route.js invoke ember:view:/usr/local/lib/node_modules/generator-ember/controller/index.js create app/scripts/views/story_view.js create app/scripts/views/story_edit_view.js create app/scripts/views/stories_view.js create app/templates/story.hbs create app/templates/story_edit.hbs create app/templates/stories.hbs create app/scripts/views/bound_text_field_view.js invoke ember:router:/usr/local/lib/node_modules/generator-ember/controller/index.js conflict app/scripts/router.js [?] Overwrite app/scripts/router.js? overwrite force 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="bower_components/ember-localstorage-adapter/localstorage_adapter.js"></script>
同时用以下代码更新app/scripts/store.js.这会用LSAdapter(Local Storage Adapter)而不是FixtureAdapter配置程序。
Getbookmarks.Store = DS.Store.extend(); Getbookmarks.ApplicationAdapter = DS.LSAdapter.extend({ namespace: 'stories' });
更新路由
用以下代码替换router.js.
Getbookmarks.Router.map(function () { this.resource('index',{path : '/'}); this.resource('story', { path: '/story/:story_id' }); this.resource('story_edit', { path: '/story/new' }); });
以上代码,我们定义了三个路由。
- index路由对应的根路径。
- 查看独立文章用story路由。
- 用story_edit路由新建文章,当用户查看'#/story/new', 一个表格会显示给用户。
提交新Story
现在添加表格,用于用户打开'#/story/new'时显示,用以下代码更新 app/templates/story_edit.hbs.
<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>
现在打开 http://localhost:9000/#/story/new 可以看到提交表格。
更新 StoryEditController save功能,会把文章保存到本地存储中。
Getbookmarks.StoryEditController = Ember.ObjectController.extend({ 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'); console.log('Store .. '+store); var story = store.createRecord('story',{ url : url, tags : tags, fullname : fullname, title : title, excerpt : excerpt, submittedOn : submittedOn }); story.save(); this.transitionToRoute('index'); } });
列出所有文章
接下来的功能是实现在侧边栏显示文章列表。
在application_route.js, 我们会从本地存储中获取所有文章。
Getbookmarks.ApplicationRoute = Ember.Route.extend({ model : function(){ var stories = this.get('store').findAll('story'); return stories; } });
接下来更新application.hbs加载文章标题和链接,用以下代码更新。
<div> <nav class="navbar navbar-default navbar-fixed-top" role="navigation"> <div class="navbar-header"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex1-collapse"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">GetBookmarks</a> </div> <div class="collapse navbar-collapse navbar-ex1-collapse"> <ul class="nav navbar-nav pull-right"> <li>{{#link-to 'story_edit'}}<span class="glyphicon glyphicon-plus"></span> Submit Story{{/link-to}}</li> </ul> </div> </nav> <div class="container" id="main"> <div class="row"> <div> <div class="col-md-3"> <div class="well sidebar-nav"> <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> <div class="col-md-9"> {{outlet}} </div> </div> </div> </div> </div>
程序界面会重新加载更新。
查看单独文章
最后一个功能是当用户打开 http://localhost:9000/#/story/:id 会显示单独的文章,:id对应文章id, 用以下代码更新story_route.js.
Getbookmarks.StoryRoute = Ember.Route.extend({ model : function(params){ var store = this.get('store'); return store.find('story',params.story_id); } });
用以下代码更新 app/templates/story.hbs.
<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>
构建产品
最后,运行grunt build命令生成一个分布式程序,grunt build命令使用app目录下的源代码文件,返回到dist下的分布式程序中。
$ grunt build
这就是今天的内容,继续给反馈吧。
原文:https://www.openshift.com/blogs/day-24-yeoman-ember-the-missing-tutorial