现在您已经熟悉了MEAN应用程序的机制,是时候开始定制在本系列的第一部分中创建并在第二部分中浏览的MEAN.JS应用程序了。 在第三部分中,我将演示该应用程序的基本CRUD功能。 您还将学到一些有关响应式Web设计和Bootstrap的知识。
您将在本系列其余部分中构建的应用程序被亲切地称为UGLI:“用户组列表和信息”应用程序。 自2010年以来,我一直在运行HTML5丹佛用户组(之前是Boulder Java用户组,在那之前是丹佛Java用户组),所以我是本地用户组的忠实支持者也就不足为奇了。 让我感到惊讶的是,缺少运行用户组的专用软件。 (是皮鞋匠的孩子总是赤脚,对吗?)现在是时候解决这个问题了。
许多用户组在Meetup.com上找到了在线主页。 我使用MEAN和UGLI应用程序的目标不是替换Meetup.com。 相反,我想与它进行深度整合。 Meetup.com在运行一个成功的用户组所需的大多数核心功能上都表现出色:注册新用户,发布会议详细信息,处理RSVP等。 但是,用户组负责人仍然缺少一些关键功能,包括管理演示者列表以及链接到幻灯片。 UGLI将填补空白。 (请参阅下载以获取完整的示例代码。)
调整品牌
使应用程序UGLI的首要任务是调整应用程序的品牌。 需要在应用程序的服务器端的config和app目录中进行一些更改。 其他要求在客户端的公共目录中。
从存储在config / env / all.js中的元数据开始。 将标题更改为HTML5 Denver(或您选择的用户组),并将描述更改为HTML5 Denver用户组,如清单1所示。
清单1. config / env / all.js
'use strict';
module.exports = {
app: {
title: 'HTML5 Denver',
description: 'HTML5 Denver User Group',
keywords: 'MongoDB, Express, AngularJS, Node.js'
},
config / env / development.js中的标题也需要更改,如清单2所示。如您上次了解的那样,development.js和all.js在运行时合并。
清单2. config / env / development.js
'use strict';
module.exports = {
db: 'mongodb://localhost/test-dev',
app: {
title: 'HTML5 Denver'
},
接下来,更改显示在导航栏左上角的品牌。 为此,请编辑public / modules / core / views / header.client.view.html。 在第9行附近找到带有navbar-brand
类的anchor标签,并将正文更改为HTML5 Denver
,如清单3所示。
清单3. public / modules / core / views / header.client.view.html
<div class="container" data-ng-controller="HeaderController">
<div class="navbar-header">
<button class="navbar-toggle" type="button" data-ng-click="toggleCollapsibleMenu()">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a href="/#!/" class="navbar-brand">HTML5 Denver</a>
</div>
<!-- ...snip... -->
</div>
要验证您的更改,请在命令行中输入mongod
启动MongoDB,然后通过输入grunt
启动您的应用程序。 在浏览器中查看网络应用,以在菜单和标题栏中查看您的品牌。
要完成品牌更改,您需要替换显示在主页正文中的public / modules / core / views / home.client.view.html中的样板文本。 创建一个名为home.client.view.html.original的副本,以便以后可以参考(如果需要)。
该文件使用Bootstrap框架的功能来确保您的网站从一开始就可用于移动设备。 在继续之前,熟悉Bootstrap提供的12列网格布局很有帮助。
了解Bootstrap和响应式Web设计
查看任何印刷版报纸或杂志,您会看到正在使用的列。 有时,图像或标题对于某种设计风格而言跨越一栏以上,但是基本的柱状布局是几乎所有打印页面的基础。
网页没有什么不同。 例如,访问TIME网站。 您最初会看到基于列的布局。 但是请注意,将浏览器窗口的宽度从全屏减小到极窄时会发生什么。 当您缩小窗口时,可见列的数量会减少,而当您增大窗口时,可见列的数量会增加。
此效果称为响应式网页设计 ,因为网页会响应该网页并将其设计调整为请求该网页的设备的屏幕尺寸。 现代化的Web开发人员可以构建从最小的手持设备到坐在桌子上或挂在墙上的最大屏幕的无缝网站。 积极地为智能手机,平板电脑,笔记本电脑等创建单独的离散网站,并使用单独的http://m.*和http://www.* URL,这是20世纪的一种过时策略。
自适应网页设计不是万能的策略; 而是一种“在所有设备上外观和感觉都不错的网站”策略。 您不必选择用户使用哪种设备来访问您的网站,因此至关重要的是,您的设计必须具有内置的灵活性以进行相应的调整。
与传统计算机相比,移动设备访问许多受欢迎的网站(包括Facebook和Instagram)的频率更高。 Twitter的用户群主要是移动用户。 Twitter规范了其响应式Web设计策略,并将其作为Bootstrap开源。 Bootstrap具有12列的布局,该布局可以根据用于定义列CSS类而增大或缩小。
请注意MEAN.JS示例应用程序主页上的MongoDB,Express,AngularJS和Node.js的四列,如图1所示。
图1. Bootstrap的列式布局示例
现在查看清单4所示的public / modules / core / views / home.client.view.html的源代码,以查看Bootstrap的12列布局在起作用。
清单4. public / modules / core / views / home.client.view.html
<div class="row">
<div class="col-md-3">
<h2><strong>M</strong>ongoDB</h2>
</div>
<div class="col-md-3">
<h2><strong>E</strong>xpress</h2>
</div>
<div class="col-md-3">
<h2><strong>A</strong>ngularJS</h2>
</div>
<div class="col-md-3">
<h2><strong>N</strong>ode.js</h2>
</div>
</div>
如果将class="row"
添加到父div
,则可以将class="col- xx - N "
属性添加到子div
以将它们划分为列。 N
值必须是1到12之间的数字,并且xx
值取决于您要针对以下哪个设备优化布局的大小:
-
xs
适用于超小型设备(宽度小于768像素) - 小型装置的
sm
(768至991像素) - 适用于中型设备的
md
(介于992和1,199像素之间) -
lg
适用于大型设备(1200像素或更大)
有关更多信息,请参见Bootstrap CSS文档的“ 网格系统”部分。
因为清单4中的每一列都针对中型( md
)设备进行了优化,所以如果您在屏幕宽度小于992像素的设备上访问此页面,则这些列将垂直堆叠而不是水平堆叠。 使浏览器窗口足够窄以触发此更改,如图2所示。
图2.移动设备上的响应式Web设计示例
现在,使用您到目前为止所学的知识,是时候用一些UGLI特定的文本替换home.client.view.html中的样板文本。
首先,从W3C HTML5徽标页面下载256像素HTML5徽标,然后将其复制到public / modules / core / img / brand / HTML5_Logo_256.png。 然后用清单5中的源替换public / modules / core / views / home.client.view.html中的现有HTML。
清单5. public / modules / core / views / home.client.view.html
<section data-ng-controller="HomeController">
<div class="jumbotron text-center">
<div class="row">
<div class="col-md-4">
<img alt="HTML5" class="img-responsive center-block"
src="modules/core/img/brand/HTML5_Logo_256.png" />
</div>
<div class="col-md-8">
<h1>The HTML story is still being written.</h1>
<h2><em>Come hear the latest chapter at the HTML5 Denver User Group.</em></h2>
</div>
</div>
</div>
</section>
当您在宽浏览器窗口中查看网站时,HTML5徽标会出现在文本旁边,如图3所示。
图3.新的UGLI主页
当您使浏览器窗口足够狭窄时,徽标会堆叠在文本的顶部,如图4所示。
图4.新的UGLI主页,它将出现在移动设备上
看到使用Bootstrap使您的网站适合移动设备的操作有多么容易? Bootstrap是我为客户建立的每个新网站的基础。
现在是时候解决MEAN堆栈中的CRUD了。
基本增删改查
Meetup.com在帮助我管理用户组事件方面做得很好。 但是在事件过去之后,事件的时间方面并不像当晚发生的谈话那么重要。
换句话说,该网站的一个用户故事是:“下次会议将要讨论什么?” 最初的Meetup.com对此用户故事非常满意。
我希望通过UGLI应用程序解决的第二个用户故事“向我展示与MEAN堆栈相关的所有讨论,无论它们何时发生”。 要实现这个故事,您必须围绕一个名为Talk
的新模型对象创建一个CRUD基础结构。 值得庆幸的是,可以使用Yeoman生成器来帮助建立该基础结构。
在应用程序的根目录中,输入yo meanjs:crud-module talks
。 根据提示:
- 选择所有四个补充文件夹(css,img,指令和过滤器)。
- 回答
Yes
,将CRUD模块链接添加到菜单。 - 当生成器询问您要使用哪个菜单时,接受默认设置(
topbar
)。
清单6显示了交互式命令行序列。
清单6.使用Yeoman生成器生成新的CRUD模块
$ yo meanjs:crud-module talks
[?] Which supplemental folders would you like to include in your angular module?
css, img, directives, filters
[?] Would you like to add the CRUD module links to a menu? Yes
[?] What is your menu identifier? topbar
create app/controllers/talks.server.controller.js
create app/models/talk.server.model.js
create app/routes/talks.server.routes.js
create app/tests/talk.server.model.test.js
create public/modules/talks/config/talks.client.routes.js
create public/modules/talks/controllers/talks.client.controller.js
create public/modules/talks/services/talks.client.service.js
create public/modules/talks/tests/talks.client.controller.test.js
create public/modules/talks/config/talks.client.config.js
create public/modules/talks/views/create-talk.client.view.html
create public/modules/talks/views/edit-talk.client.view.html
create public/modules/talks/views/list-talks.client.view.html
create public/modules/talks/views/view-talk.client.view.html
create public/modules/talks/talks.client.module.js
注意,在清单6中,生成器创建了所需的服务器端基础结构(存储在app目录中):路由,控制器,模型和单元测试。 它还在public / modules / talks目录下构建了所有客户端工件。
稍后,您将向Talk
对象添加一些自定义字段。 在执行此操作之前,请通过在浏览器中访问网站来查看默认情况。
点击右上角和类型的登入链接的用户名和密码在本系列前面创建,或点击注册并创建一个新的证书。
登录后,您可以在左上方看到“ 对话”菜单。 从菜单中选择New Talk ,以打开一个HTML表单,该表单提供一个孤独的Name字段,如图5所示。
图5.定制之前的New Talk表单
这是一个很好的开始,但是您需要的不仅仅是一个简单的文本字段,以捕获Talk
所有属性。
添加新字段以保持持久性
要将新字段添加到Talk
,必须编辑六个文件-四个用于显示,两个用于持久性:
- app / models / talk.server.model.js
- 公共/模块/控制器/talks.client.controller.js
- public / modules / talks / views / create-talk.client.view.html
- public / modules / talks / views / edit-talk.client.view.html
- public / modules / talks / views / view-talk.client.view.html
- public / modules / talks / views / list-talks.client.view.html
我将首先解决持久性问题。 解决方案的一半在服务器端,另一半在客户端。
服务器端模型(在app / models / talk.server.model.js中定义)是应用程序真实性的来源。 在其中,您将为字段命名,提供数据类型,添加验证规则,等等。
客户端控制器(在public / modules / controllers / talks.client.controller.js中定义)收集来自用户的数据输入,并通过HTTP请求将数据推送到服务器。 控制器还通过线路提取JSON数据,并将其交给视图进行展示。
这种架构有趣的是,模型对象永远不会离开服务器。 相反,对象是从客户端发送的数据中实现的,并在HTTP响应中序列化为JSON。
该应用程序有两个控制器-一个在服务器端,另一个在客户端-但是只有客户端控制器才有意义。 服务器端控制器只需将传入的JSON倒入模型对象中,因此在向模型中添加其他字段时,无需对服务器端控制器进行任何调整。 客户端控制器需要进行一些调整以适应新字段。
打开app / models / talk.server.model.js,向服务器端模型添加新字段,如清单7所示。您可以看到在另外两个元数据字段旁边定义的期望name
字段( 如图5所示): created
和user
。
清单7. app / models / talk.server.model.js
/**
* Talk Schema
*/
var TalkSchema = new Schema({
name: {
type: String,
default: '',
required: 'Please fill Talk name',
trim: true
},
created: {
type: Date,
default: Date.now
},
user: {
type: Schema.ObjectId,
ref: 'User'
}
});
基于JSON的架构是不言自明的。 定义新字段时,可以指定数据类型,默认值,要显示的必填字段的错误消息以及许多其他改进。 有关更多信息,请参见Mongoose文档 。
为description
, presenter
和slidesUrl
添加新字段,如清单8所示。在这种情况下, description
和presenter
都是必填字段。 slidesUrl
字段是可选的。
清单8. app / models / talk.server.model.js
/**
* Talk Schema
*/
var TalkSchema = new Schema({
name: {
type: String,
default: '',
required: 'Please fill Talk name',
trim: true
},
description: {
type: String,
default: '',
required: 'Please fill Talk description',
trim: true
},
presenter: {
type: String,
default: '',
required: 'Please fill Talk presenter',
trim: true
},
slidesUrl: {
type: String,
default: '',
trim: true
},
created: {
type: Date,
default: Date.now
},
user: {
type: Schema.ObjectId,
ref: 'User'
}
});
此时,您的服务器端后端已准备就绪,可以接受新字段。 现在,您需要解决客户端控制器。 打开public / modules / controllers / talks.client.controller.js并添加新字段,如清单9所示。
清单9. public / modules / controllers / talks.client.controller.js
// Create new Talk
$scope.create = function() {
// Create new Talk object
var talk = new Talks ({
name: this.name,
description: this.description,
presenter: this.presenter,
slidesUrl: this.slidesUrl
});
// Redirect after save
talk.$save(function(response) {
$location.path('talks/' + response._id);
}, function(errorResponse) {
$scope.error = errorResponse.data.message;
});
// Clear form fields
this.name = '';
this.description = '';
this.presenter = '';
this.slidesUrl = '';
};
$scope.create
函数是将表单字段聚合到JSON对象中的地方,然后将其发送到服务器进行持久化。 在将模型中的相应字段添加到控制器后,您将获得故事的持久性部分。
现在是时候将重点转移到表示层了,以便您的用户可以查看新字段并与之交互。
添加新字段以显示
查看public / modules / talks / views /。 其中有四个文件与CRUD生命周期有关:
- create-talk.client.view.html
- edit-talk.client.view.html
- view-talk.client.view.html
- list-talks.client.view.html
打开create-talk.client.view.html,如清单10所示。
清单10.生成的create-talk.client.view.html
<section data-ng-controller="TalksController">
<div class="page-header">
<h1>New Talk</h1>
</div>
<div class="col-md-12">
<form class="form-horizontal" data-ng-submit="create()" novalidate>
<fieldset>
<div class="form-group">
<label class="control-label" for="name">Name</label>
<div class="controls">
<input type="text" data-ng-model="name" id="name" class="form-control"
placeholder="Name" required>
</div>
</div>
<div class="form-group">
<input type="submit" class="btn btn-default">
</div>
<div data-ng-show="error" class="text-danger">
<strong data-ng-bind="error"></strong>
</div>
</fieldset>
</form>
</div>
</section>
复制与Name
字段相关的代码块三遍,以支持Description
, Presenter
和slidesUrl
,如清单11所示。请注意,我将Description
字段设置为textarea
而不是简单的文本字段。 另外,我从slidesUrl
字段中删除了required
属性,并将input type
从text
更改为url
。
清单11.更新的create-talk.client.view.html
<section data-ng-controller="TalksController">
<div class="page-header">
<h1>New Talk</h1>
</div>
<div class="col-md-12">
<form class="form-horizontal" data-ng-submit="create()" novalidate>
<fieldset>
<div class="form-group">
<label class="control-label" for="name">Name</label>
<div class="controls">
<input type="text" data-ng-model="name" id="name" class="form-control"
placeholder="Name" required>
</div>
</div>
<div class="form-group">
<label class="control-label" for="description">Description</label>
<div class="controls">
<textarea data-ng-model="description" id="description" class="form-control"
placeholder="Description" required></textarea>
</div>
</div>
<div class="form-group">
<label class="control-label" for="presenter">Presenter</label>
<div class="controls">
<input type="text" data-ng-model="presenter" id="presenter" class="form-control"
placeholder="Presenter" required>
</div>
</div>
<div class="form-group">
<label class="control-label" for="slidesUrl">Slides</label>
<div class="controls">
<input type="url" data-ng-model="slidesUrl" id="slidesUrl" class="form-control"
placeholder="Slides Url">
</div>
</div>
<div class="form-group">
<input type="submit" class="btn btn-default">
</div>
<div data-ng-show="error" class="text-danger">
<strong data-ng-bind="error"></strong>
</div>
</fieldset>
</form>
</div>
</section>
在Web浏览器中,新修改的New Talk页面如图6所示。
图6.定制后的New Talk表单
当您对更改感到满意时,请打开edit-talk.client.view.html并在那里进行相应的更改,如清单12所示。
清单12. edit-talk.client.view.html
<div class="col-md-12">
<form class="form-horizontal" data-ng-submit="update()" novalidate>
<fieldset>
<div class="form-group">
<label class="control-label" for="name">Name</label>
<div class="controls">
<input type="text" data-ng-model="talk.name" id="name" class="form-control"
placeholder="Name" required>
</div>
</div>
<div class="form-group">
<label class="control-label" for="description">Description</label>
<div class="controls">
<textarea data-ng-model="talk.description" id="description" class="form-control"
placeholder="Description" required></textarea>
</div>
</div>
<div class="form-group">
<label class="control-label" for="presenter">Presenter</label>
<div class="controls">
<input type="text" data-ng-model="talk.presenter" id="name" class="form-control"
placeholder="Presenter" required>
</div>
</div>
<div class="form-group">
<label class="control-label" for="slidesUrl">Slides</label>
<div class="controls">
<input type="url" data-ng-model="talk.slidesUrl" id="name" class="form-control"
placeholder="Slides Url">
</div>
</div>
<div class="form-group">
<input type="submit" value="Update" class="btn btn-default">
</div>
<div data-ng-show="error" class="text-danger">
<strong data-ng-bind="error"></strong>
</div>
</fieldset>
</form>
</div>
请注意,用于编辑HTML与您之前修改的创建表单略有不同。 编辑时,您已经有一个Talk
对象,因此data-ng-model
属性以完全限定的方式引用字段,例如talk.name
而不是name
。 在Web浏览器中查看您的更改,如图7所示。
图7.定制后的Edit Talk表单
view-talk.client.view.html页面是对象的只读视图。 用户在保存新的Talk
,更新现有的Talk
或从列表页面中选择Talk
后,可以在此视图中进行登陆。 进行清单13所示的更改。
清单13. edit-talk.client.view.html
<div class="page-header">
<h1 data-ng-bind="talk.name"></h1>
<h2><em>by {{talk.presenter}}
<span ng-if="talk.slidesUrl !== '' ">[<a href="{{talk.slidesUrl}}">slides</a>]</span></em></h2>
<p>{{talk.description}}</p>
</div>
回想一下slidesUrl
字段是可选的。 在视图页面中,您正在使用ng-if
指令(如果已填充)有条件地显示该字段。 通过在浏览器中查看页面来验证此行为,如图8所示。
图8.定制后的View Talk表单
您需要调整的最后一个视图是列表视图。 打开list-talks.client.view.html并进行清单14所示的调整。
清单14. list-talks.client.view.html
<div class="list-group">
<a data-ng-repeat="talk in talks" data-ng-href="#!/talks/{{talk._id}}" class="list-group-item">
<h4 class="list-group-item-heading" data-ng-bind="talk.name"></h4>
<p><em>by {{talk.presenter}}</em></p>
</a>
</div>
请注意, data-ng-repeat
指令用于显示从服务器返回的对话列表中的每个对话。 在浏览器中查看结果,如图9所示。
图9.定制后的List Talks表单
结论
此时,您已经对MEAN堆栈的各个部分之间的交互方式有了很好的了解。 您正在使用Bootstrap的自适应Web设计功能来确保您的网站在所有设备上看起来都不错,而不仅仅是具有101键和鼠标的传统设备。 您已经体验了使用Yeoman生成器向您的应用程序添加新的CRUD模块的强大功能和便捷性。 生成器将所有原始工件放在正确的目录中,从而使您可以轻松地定制它们。
在下一期中,您将看到将来自远程源的数据合并到您的应用程序中是多么容易。 具体来说,您将开始直接通过Meetup的RESTful API从Meetup.com提取事件数据。 在此之前,请尽情掌握MEAN。
翻译自: https://www.ibm.com/developerworks/opensource/library/wa-mean3/index.html