虽然后端开发人员并不一定要学前端,自测也可以借助别的工具完成。但是,了解前端开发的特性,困难点,你才能更好地设计 API,成为一个前端人员乐意合作的好伙伴。
用 evergrow 脚手架生成视图相关的文件是非常方便的。要接着构建 Image 模块的视图,只需要用命令 yo evergrow:view Image 就能够生成表格和详情视图所需的 4 个基本文件。
生成上面的几个基本文件后,其实你在浏览器输入 http://localhost:3000/image/page/list 就能够看到如下的默认表格视图。
根据上一章节的教程,应该不难从代码里找到路由对应的 Controller 为:
functionlistImagePage(req, res, next){
res.render('image/imageList')
}
和之前介绍的 RESTful API 不同, res 在这里调用的是 render 方法。这个方法调用模板引擎去寻找匹配的视图模板,处理后返回给前端。 模板引擎最基本的作用 是什么呢?
具体 Express 框架里怎么使用模板引擎我就不详细介绍了,感兴趣的同学可以看 官方文档 。在 evergrow 框架里面,具体的配置在 server-manager.js :
// View engine
app.set('views', './view/') // 模板放置的文件夹
app.set('view engine', 'html')
app.engine('html', require('ejs-mate')) // 我们选择的模板引擎是 EJS
上述路由对应的页面 /view/image/imageList.html 是一个包含了表格的视图。表格是一种相当常见的表现形式,尤其在企业级系统和后台管理页面。它多数用于展示多个同级别的数据。
实现这个页面的代码其实很简单,HTML 的视图模板和 JavaScript 加起来才 100 行左右的代码。必须注意的是 一开始别深入细节,先从整体结构上面来理解 。
我们来解构一下 HTML 视图模板。页面分了五大部分:
h2 标签) button 标签) table 标签,下图中的蓝色框部分) modal ,默认隐藏,绿色框部分)
HTML 语言强调的是页面布局。默认情况下,块状元素 h2 , table , div 它们就像叠积木一样从上往下堆叠在一起。所以,写前端最基本就是要想好整个页面,是怎么切割成一个个盒子模型(box model),然后组装在一起。
CSS 能更丰富和精确地定位和布局页面。非纯前端不用了解太细,碰到什么常见的,看得懂就差不多了。比如说, class="col-sm-12" 是 Bootstrap 框架的 Grid System 。Grid System 其实就是把你的屏幕像栅栏那样平均分割成 12 列。那个 CSS 的意思就是它的大小占满整个屏幕。
用 HTML 和 CSS 完成页面布局后,我们就需要用 JavaScript 来为页面的元素赋予行为,比如为用户的鼠标点击做出反应。页面最下方 <% ... %> 中间的部分,其实是在后台被模板引擎执行的。怎么用你先不用理会,只要知道引用了 common.js 和 imageList.js 两个文件就可以了。
<%
block('moduleScript').append(
Loader('/public/build/imageList.min.js')
.js('/public/js/common/common.js')
.js('/public/js/image/imageList.js')
.done(assets, config.site_static_host)
)
%>
```
### Vue.js
这个框架是一个轻量级的**双向数据绑定**的前端框架,我们只需要知道最基本的使用方法就够了。我们对着 `/public/js/image/imageList.js` 里面的代码一点点来看吧。
首先 `el: '#imageList'` 对应了上图红色框框 `id="imageList"` 部分。它表示 Vue.js 框架监控这个 `id` 对应的元素里面包含的所有元素。
#### 数据
[Data Binding]: http://v1.vuejs.org/guide/syntax.html
`data` 部分是 Vue.js 要监控的 **数据**。如果页面的元素用到了这些数据,当你代码改变这些数据的时候,页面引用的地方也会相应更改。`url` 在黄色框框的 `:data-url="url"` 使用了。`:` 这个小冒号就是让 Vue.js 监控它的意思,并把 `url` 对应的数据 `'/image'` 赋值给 `data-url` 这个给 `bootstrap-table` 库在绘制表格时用到的参数。小冒号是 `v-bind:` 的缩写。都是 Vue.js 的 [Data Binding][] 语法。仔细看看 `pageList` 在页面的使用有什么不同?
```javascript
data: {
url: '/image',
pageList: [10, 20, 30],
errorMsg: ''
}
methods 部分是 Vue.js 监控的 行为 。第一个行为就是点击工具栏的 Create 按钮的时候,页面跳转到新建图片的页面。它的绑定方式是 v-on:click="createImage" 。另一个行为是在弹窗里修改完图片资料后,点击 Save 保存的。绑定的方式是 @click="saveImage()" 。后面一种写法也是缩写。无论绑定方法名,还是像后一种那样绑定表达式都是可以的。
methods: {
createImage: function(){
window.location = '/image/page/load'
},
saveImage: function(){
let id = $('#imageId').val()
let params = {
method: 'post',
url: '/image/' + id,
data: {}
}
ajaxRequest(params, (data) => {
$('#imageTable').bootstrapTable('refresh')
$('#imageDialog').modal('toggle')
})
}
}
Vue.js 的这些绑定方式,在编程里属于 声明式编程 。HTML 和 CSS 语言其实也是声明式编程。下面我们通过对 jQuery 的学习,来对比一下 命令式编程 的不同之处。
仅仅在 HTML 里面用一个 table 标签其实是不足够的,里面的标题和内容还需要 thead , tbody , tr 和 td 等标签才能完成。所以,代码里其实通过调用 bootstrap-table 这个库,按照我们的配置,包括获取数据的路由,和要显示的列等生成你们看到的表格。
var columns = [{
field: 'createdAt',
title: 'Created At',
width: 60,
align: 'center',
halign: 'center',
formatter: timeFormatter
}, {
field: 'createdUser',
title: 'Created By',
width: 60,
align: 'center',
halign: 'center'
}, {
field: 'updatedAt',
title: 'Updated At',
width: 60,
align: 'center',
halign: 'center',
formatter: timeFormatter
}, {
field: 'updatedUser',
title: 'Updated By',
width: 60,
align: 'center',
halign: 'center'
}, {
field: 'deleted',
title: '',
width: 60,
align: 'center',
halign: 'center',
formatter: function(value, row, index){
return value === true ? 'Deleted' : ''
}
}, {
field: '',
title: '',
width: 60,
align: 'center',
halign: 'center',
events: {
'click .edit': (e, value, row, index, activeBtn) => {
$('#imageId').val(row._id)
$('#imageDialog').modal('toggle')
}
},
formatter: function(value, row, index){
return `<div class="btn-group dropdown">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Action <span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li class="edit"><a href="#">Edit</a></li>
</ul>
</div>`
}
}]
// 省略中间部分
ready: function(){
$('#imageTable').bootstrapTable({
columns: columns,
queryParams: (p) => {
for (let v in self.params) {
p[v] = self.params[v]
}
return p
}
})
}
当页面的元素准备好后, ready 方法会被调用。jQuery 库(也就是这个美元符号 $ )根据我们提供的 ID #imageTable ,还有表格列的定义 columns 来生成。
那么,我们要在表格加上 image-model.js 里面用到的 gender 和 url 数据,只需要多加两列的配置就可以了。通过提供一个 formatter 方法,还能自定义数据的显示方式。
{
field: 'url',
title: 'Image',
width: 60,
align: 'center',
halign: 'center',
formatter: function(value, row, index){
if (value) {
return `<a href="/image/page/load/${row._id}"><img src="${value}" width="50px" height="50px"></a>`
}
return ''
}
}, {
field: 'gender',
title: 'Gender',
width: 60,
align: 'center',
halign: 'center',
formatter: function(value, row, index){
switch(value) {
case 'F':
return '女'
case 'M':
return '男'
default:
return ''
}
}
}
打开详情页是编辑数据的一种方式。不过,在单页面应用,或者想提供一种更便捷的数据更改方式的话,弹窗也是一种使用手段。
在操作列有一个下拉菜单,里面的选项通过 li 标签实现。把它的 class 属性值 edit 和事件定义 events 里面的点击事件( click )绑定后,点菜单项就会触发 Event Handler 里面的两行代码,弹窗就显示出来了。
events: {
'click .edit': (e, value, row, index, activeBtn) => {
$('#imageId').val(row._id)
$('#imageDialog').modal('toggle')
}
}
// 省略中间部分
<li class="edit"><a href="#">Edit</a></li>
Event Handler 里面的两行代码其实就是命令式编程的形式(一步一步教电脑怎么做):
imageId 的元素: $('#imageId') .val(row._id) $('#imageDialog') .modal('toggle') 相应的, saveImage 方法里面也是一步步列出保存数据到服务器前要经过的步骤:
默认的弹窗只有些象征性的文字,我们可通过下面的代码,实现修改 Model 里的 gender 和 deleted 数据的功能。(虽然图片链接不能直接改,但是因为接口限制了数据 url 不能为空,所以还是加上隐藏元素,保存时也一起发给服务器)
<inputtype="hidden"id="imageUrl"/>
<divclass="form-group">
<labelfor="gender">Gender</label>
<selectclass="form-control"id="gender">
<optionvalue="F">Female</option>
<optionvalue="M">Male</option>
</select>
</div>
<divclass="checkbox">
<label>
<inputtype="checkbox"id="deleted"> Deleted?
</label>
</div>
events: {
'click .edit': (e, value, row, index, activeBtn) => {
$('#imageId').val(row._id)
$('#imageUrl').val(row.url)
$('#gender').val(row.gender)
$('#deleted').prop('checked', row.deleted)
$('#imageDialog').modal('toggle')
}
}
// 省略中间部分
saveImage: function(){
var id = $('#imageId').val()
var gender = $('#gender').val()
var url = $('#imageUrl').val()
var params = {
method: 'post',
url: '/image/' + id,
data: {
gender: gender,
url: url
}
}
ajaxRequest(params, (data) => {
$('#imageTable').bootstrapTable('refresh')
$('#imageDialog').modal('toggle')
})
}
/view/image/imageProfile.html 和 /js/image/imageProfile.js 实现图片和字段 gender , deleted 的显示,并且能够修改字段 gender , deleted 。