现代的B/S系统软件有下面几个特色。
1.单页面应用
单页面应用(single-page application,简称SPA)指的是一种类似于原生客户端软件的更流畅的用户体验的页面。在单页面的应用中,所有的资源(HTML、Javascript、CSS)都是按需动态加载到页面上的,且不需要服务端控制页面的转向。
2.响应式设计
响应式设计(Responsive web design,简称RWD)指的是不同的设备(电脑、平板、手机)访问相同的页面的时候,得到不同的页面视图,而得到的视图是适应当前屏幕的。当然就算在电脑上,我们通过拖动浏览器窗口的大小, 也通用得到合适的视图。
3.数据导向
数据导向是对于页面导向而言的,页面上的数据获得是通过消费后台的REST服务来实现的,而不是通过服务器渲染的动态页面(如JSP)来实现的,一般数据交换使用的格式是JSON。
本节将针对Bootstrap 和 AngularJS 进行快速入门式的引导,如需深入学习,请参考官网或相关专题书籍。
7.7.1 Bootstrap
1.什么是Bootstrap
Bootstrap官方定义:Bootstrap是开发响应式和移动优先的Web应用的最流行的HTML、CSS、JavaScript框架。
2.下载并引入Bootstrap
下载地址:http://getbootstrap.com/getting-started/, 如图
下载的压缩包的目录结构如图
最简单的Bootstrap页面模板如下:
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1">
<!-- 上面3个meta标签必须是head 的头三个标签,其余的head内标签在此3个之后 -->
<title>Bootstrap基本模板</title>
<!-- Bootstrap的CSS -->
<link href="bootstrap/css/bootstrap.min.css" rel="stylesheet">
<!-- HTML5 shim and Respond.js 用来让IE 8 支持 HTML 5 元素和媒体查询 -->
<!-- [if lt IE 9]>
<script src="js/html5shiv.min.js"></script>
<script src="js/respond.min.js"></script>
<[endif]-->
</head>
<body>
<h1>你好,Bootstrap</h1>
<!-- Jquery是Bootstrap脚本插件必需的 -->
<script src=js/jquery.min.js></script>
<!-- 包含所有编译的插件 -->
<script src="bootstrap/js/bootstrap.min.js"></script>
</body>
</html>
3.CSS支持
Bootstrap的CSS样式为基础的HTML元素提供了美观的样式,此外还提供了一个高级的网格系统用来做页面布局。
(1)布局网格
在Bootstrap里,行使用的样式为row,列使用col-md-数字,此数字范围为1~12,所有列加起来的和也是12,代码如下:
<div class="row">
<div class="col-md-1">。col-md-1</div>
<div class="col-md-1">。col-md-1</div>
<div class="col-md-1">。col-md-1</div>
<div class="col-md-1">。col-md-1</div>
<div class="col-md-1">。col-md-1</div>
<div class="col-md-1">。col-md-1</div>
<div class="col-md-1">。col-md-1</div>
<div class="col-md-1">。col-md-1</div>
<div class="col-md-1">。col-md-1</div>
<div class="col-md-1">。col-md-1</div>
<div class="col-md-1">。col-md-1</div>
<div class="col-md-1">。col-md-1</div>
</div>
<div class="row">
<div class="col-md-8">.col-md-8</div>
<div class="col-md-4">.col-md-4</div>
</div>
<div>
<div class="col-md-4">.col-md-4</div>
<div class="col-md-4">.col-md-4</div>
<div class="col-md-4">.col-md-4</div>
</div>
<div>
<div class="col-md-6">.col-md-6</div>
<div class="col-md-6">.col-md-6</div>
</div>
(2)html元素
Bootsrtap为html元素提供了大量的样式,如表单元素、按钮、图标等。更多内容请查看:http://getbootstrap.com/css/。
4.页面组件支持
Bootstrap为我们提供了大量的页面组件,包括字体图标、下拉框、导航条、进度条、缩略图等,更多请阅读http://getboostrap.com/components/。
5.javascript支持
Bootstrap为我们提供了大量的JavaScript插件,包含模式对话框、标签页、提示、警告等,更多内容请查看http://getbootstrap.com/javascript/。
7.7.2 AngularJS
1.什么是AngularJS
AugularJS官方定义:AngularJS是HTML开发本应该的样子,它是用来设计开发Web应用的。
AngularJS使用声明式模板+数据绑定(类似于JSP、Thymeleaf)、MVW(Model-View——Whatever)、MVVM(Model-View-ViewModel)、MVC(Model-View-Controller)、依赖注入和测试,但是这一切的实现却只借助纯客户端的JavaScript。
HTML一般是用来声明静态页面的,但是通常情况下我们希望页面是基于数据动态生成的,这也是我们很多服务端模板引擎出现的原因;而AngularJS可以只通过前端技术就能实现动态的页面。
2.下载并引入AngularJS
AngularJS下载地址:https://code.angularjs.org/
最简单的AngularJS页面
<!DOCTYPE html>
<html ng-app> <!-- ng-app所作用的范围是AngularJS起效的范围,本例是整个页面有效。 -->
<head>
<sctipt src="js/angular.min.js"></sctipt> <!-- 载入AngularJS的 脚本 -->
</head>
<body>
<div>
<label>名字:</label>
<input type="text" ng-model="yourName" placeholder="输入你的名字"> <!-- ng-model定义整个AngularJS的前端数据模型,模型的名称为yourName,模型的值来自你输入的值若输入的值改变,则数据模型值也会变 -->
<hr>
<h1>你好{{yourName}}!</h1><!-- 使用{{模型名}}来读取模型中的值。 -->
</div>
</body>
</html>
3.模块、控制器和数据绑定
我们对MVC的概念已经烂熟于心了,但是平时的MVC都是服务端的MVC,这里用AngularJS实现了纯页面端MVC,即实现了视图模板、数据模型、代码控制的分离。
再来看看数据绑定,数据绑定是将视图和数据模型绑定在一起。如果视图变了,则模型的值就变了;如果模型值变了,则视图也会跟着改变。
AngularJS为了分离代码达到复用的效果,提供了一个module(模块)。定义一个模块需使用下面的代码。
无依赖模块:
angular.module('firstModule',[]);
有依赖模块
angular.module('firstModule',['moduleA','moduleB']);
我们看到了V就是我们的页面元素,M就是我们的ng-model,那C呢?我们可以通过下面的代码来定义控制器,页面使用ng-controller来和其关联:
augular.module('firstModule',[])
.controller('firstController',funtion(){
...
};
);
<div ng-controller="firstController">
...
</div>
4.Scope和Event
(1)Scope
Scope是AngularJS的内置对象,用$Scope来获得。在Scope中定义的数据是数据模型,可以通过{{模型名}}在视图上获得。Scope主要是在编码中需要对数据模型进行处理的时候使用,Scope的作用范围与在页面声明的范围一致(如在controller内使用,scope的作用范围是页面声明ng-controller标签元素的作用范围)。
定义:
$scope.greeting='Hello'
获取
{{greeting}}
(2)Event
因为Scope的作用范围不同,所以不同的Scope之间若要交互的话需要通过事件(Event)来完成。
1)冒泡事件(Emit)冒泡事件负责从子Scope向上发送事件,示例如下。
子Scope发送:
$scope.$emit('EVENT_NAME_EMIT','message');
父Scope接受:
$scope.$on(''EVENT_NAME_EMIT',function(event,data){
....
})
2)广播事件(Broadcast)。广播事件负责从父Scope向下发送事件,示例如下。
父Scope发送:
$scope.$broadcast('EVENT_NAME_BROAD','message');
子scope接受
$scope.$on(''EVENT_NAME_BROAD',function(event,data){
...
})
5.多视图和路由
多视图和路由是AngularJS实现单页面应用的技术关键,AngularJS内置了一个$routeProvider对象来负责页面加载和页面路由转向。
需要注意的是,1.2.0之后的AngularJS将路由功能移出,所以使用路由功能要另外引入angular-route.js
例如
angular.module('firstModule').config(function($routeProvider){
$routeProvider.when('/view1,{ //此处定义的是某个页面的路由名称
controller:'Controller1', //此处定义的是当前页面使用的控制器。
templateUrl:'view1.html', //此处定义的要加载的真实页面
}).when('/view2',{
controller:'Controller2',
templateUrl:'view2.html',
});
})
在页面上可以用下面代码来使用我们定义的路由
<ul>
<li><a href="#/view1">view1</a></li>
<li><a href="#/view2">view2</a></li>
</ul>
<ng-view></ng-view> <!--此处为加载进来的页面显示的位置 -->
依赖注入
依赖注入是AngularJS的一大酷炫功能。可以实现对代码的解耦,在代码里可以注入AngularJS的对象或者我们自定义的对象。下面示例是在控制器中注入$scope,注意使用依赖注入的代码格式。
angular.module('firstModule')
.controller("diController",['$scope',
function($scope){
...
}]);
7.Service和Factory
AngularJS为我们内置了一些服务,如$location、$time、$rootScope 。很多时候我们需要自己定制一些服务,AngularJS为我们提供了Service和Factory。
Service和Factory的区别是:使用Service的话,AngularJS会使用new来初始化对象;而使用Factory会直接获得对象。
(1)Service
定义:
angular.module('firstModule').service('helloService',function(){
this.sayHello=function(name){
alert('Hello '+name);
}
});
注入调用:
angular.module('firstModule'
.controller("diController",['$scope','helloService',
function($scope,helloService){
helloService.sayHello('lmz');
}]);
(2)Factory
定义:
angular.module('firstModule').service('helloFactory',function(){
return{
sayHello:function(name){
alert('Hello ' + name);
}
}
});
注入调用:
angular.module('firstModule')
.controller("diController",['$scope', 'helloFactory',
function($scope , helloFactory) {
helloFactory.sayHelle('lmz');
}]);
8.http操作
AngularJS内置了$http对象用来进行Ajax的操作:
$http.get(url)
$http.post(url,data)
$http.put(url,data)
$http.delete(url)
$http.head(url)
9.自定义指令
AngularJS内置了大量的指令(directive),如ng-repeat、ng-show、ng-model等。即使用一个简短的指令可实现一个前端组件。
比方说,有一个日期的js/jQuery插件,使用AngularJS封装后,在页面上调用此插件可以通过指令来实现,例如:
元素指令:<date-picker></date-picker>
属性指令:<input type="text" date-picker/>
样式指令:<input type="text" class="date-picker"/>
注释指令:<!--directive:date-picker -->
定义指令:
angular.module('myApp',[]).directive('helloWorld',function(){
return {
restrict:'AE', //支持使用属性、元素
replace:true,
template:'<h3>Hello,World!</h3>
};
});
调用指令,元素标签:
<hello-world/>
<hello:world/>
或者属性方式
<div hello-world />
7.3.3 实战
在前面两节我们快速介绍了Bootstrap和AngularJS,本节我人将它们和Spring Boot串起来做个例子。
在例子中,我们使用Bootstrap制作导航使用AngularJS实现导航切换页面的路由功能,并演示AngularJS通过$http服务和Spring Boot提供的REST服务,最后演示用指令封装jQuery UI的日期选择器。
1.新建Spring Boot项目
初始化一个Spring Boot项目,依赖只需选择Web 。
项目信息:
groupId:com.wisely
arctifactId:ch7_7
package:com.wisely.ch7_7
准备Bootstrap、AngularJS、jQuery、jQueryUI相关的资源到 src/main/resources/static 下,结构如图
另外要说明的是,本例的页面都是静态页面,所以全部放置在/static目录下。
2.制作导航
页面位置:src/main/resources/static/action.html:
<!DOCTYPE html>
<html lang="zh-cn" ng-app="actionApp">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>实战</title>
<link href="bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="jqueryui/jquery-ui.min.css" rel="sylesheet">
<style type="text/css">
.content{
padding:100px 15px;
text-align: center;
}
</style>
<!--[if lt IE 9]>
<script src="js/html5shiv.min.js"></script>
<script src="js/respond.min.js"></scripte>
<![endif]-->
</head>
<body>
<!-- 使用Bootstrap定义的导航,并配合AngularJS的路由,通过路由名称#/oper和#/directive切换视图 -->
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div id="navbar" class="collapse navbar-collapse">
<ul class=nav navbar-nav>
<li><a href="#/oper">后台交互</a>
<li><a href="#/directive">自定义指令</a></li>
</ul>
</div>
</div>
</nav>
<!-- 通过<ng-view></ng-view>展示载入的页面 -->
<div class="content">
<ng-view></ng-view>
</div>
<!-- 加载本例所需的脚本,其中jquery-ui.min.js的脚本是为我们定制指令所用;app.js定义AngularJS的模块和路由;directives.js为自定义的指令;controllers.js是控制器定义之处 -->
<script src="js/jquery.min.js"></script>
<script src="jqueryui/jquery-ui.min.js"></script>
<script src="bootstrap/js/bootstrap.min.js"></script>
<script src="js/angular.min.js"></script>
<script src="js/angular-route.min.js"></script>
<script src="js-action/app.js"></script>
<script src="js-action/directives.js"></script>
<script src="js-action/controllers.js"></script>
</body>
</html>
3.模块和路由定义
页面位置: src/main/resources/static/js-action/app.js:
var actionApp = angular.module('actionApp',['ngRoute']); //定义模块actionApp,并依赖于路由模块ngRout。
actionApp.config(['$routeProvider'] , function($routeProvider){ //配置路由,并注入$routeProvider来配置
$routeProvider.when('/oper',{ // /oper为路由名称
controller:'View1Controller', //controller定义的是路由的控制器名称
templateUrl:'views/view1.html', //templateUrl定义的是视图的真正地址
}).when('/directive',{
controller:'View2Controller',
templateUrl:'views/view.html',
});
});
控制器定义
脚本位置: src/main/resources/static/js-action/controllers.js
//定义控制器View1Controller,并注入$rootScope、$scope和$http。
actionApp.controller('View1Controller',['$rootScope','$scope','$http',
function($rootScope,$scope,$http){
//使用$scope.$on监听$viewContentLoaded事件,可以在页面内容加载完成后进行一些操作。
$scope.$on('$viewContentLoaded',function(){
console.log('页面加载完成');
});
//这段代码是核心 代码,请结合下面的View1的界面一起理解
$scope.search=function(){ //在scope内定义一个方法search,在页面上通过ng-click调用。
personName = $scope.personName; //通过$scope.personName获取页面定义的ng-model = "personName" 的值。
$http.get('search',{ //使用$http.get向服务器地址search发送get请求。
params:{personName:personName} //使用params增加请求参数
}).success(function(data){ //用success方法作为请求成功后的回调。
$scope.person=data; //将服务端返回的数据data通过$scope.person赋给模型person,这样页面视图上可以通过{{person.name}}、{{person.age}}、{{person.address}}来调用,且模型person值改变后,视图是自动更新的
});
};
}]);
actionApp.controller('View2Controller',['$rootScope','$scope',function($rootScope,$scope){
$scope.$on('$viewContentLoaded',function(){
console.log('页面加载完成');
});
}]);
5.View1的界面(演示与服务端交互)
页面位置 src/main/resources/static/views/view1.html
<div class="row">
<label for="attr" class="col-md-2 control-label">名称</label>
<div class="col-md-2">
<!-- 定义数据模型 ng-model="personName" -->
<input type="text" class="form-control" ng-model="personName">
</div>
<div class="col-md-1">
<!-- 通过ng-click="search()"调用控制器中定义的方法 -->
<button class="btn btn-primary" ng-click="search()">查询</button>
</div>
</div>
<div class="row">
<div class="col-md-4">
<ul class="list-group">
<li class="list-group-item">名字:{{person.name}}
<li class="list-group-item">年龄:{{person.age}}
<li class="list-group-item">地址:{{person.address}}
</ul>
</div>
</div>
6.服务端代码
传值对象Javabean:
package com.wisely.ch7_7;
/**
* @author mengzhi.ling
*
*/
public class Person {
private String name;
private Integer age;
private String address;
public Person() {
super();
}
public Person(String name,Integer age,String address) {
super();
this.name=name;
this.age=age;
this.address=address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
控制器:
package com.wisely.ch7_7;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@SpringBootApplication
public class Ch77Application {
@RequestMapping(value="/search",produces= {MediaType.APPLICATION_JSON_VALUE})
public Person search(String personName) {
return new Person(personName,32,"shenzhen");
}
public static void main(String[] args) {
SpringApplication.run(Ch77Application.class, args);
}
}
7.自定义指令
脚本位置: src/main/resources/static/js-action/directives.js:
actionApp.directive('datePicker',function(){ //定义一个指令名为datePicker。
return{
restrict:'AC', //限制为属性指令和样式指令。
link:function(scope,elem,attrs){ //使用link方法来定义指令,在Link方法内可使用当前scope、当前元素及元素属性。
elem.datepicher(); //初始化jqueryui的datePicker(jquery的写法是$('#id').datePicker()).
}
};
});
通过上面的代码我们就定制了一个封装jqueryui的datePicker的指令,本例只是为了演示的目的,主流的脚本框架已经被很多人封装过了,有兴趣的读者可以访问http://ngmodules.org/网站,这个网站包含了大量的AngularJS的第三方模块、插件和指令。
8.View2的页面(演示自定义指令)
页面地址: src/main/resources/static/views/view2.html:
<div class="row">
<label for="arrt" class="col-md-2 control-label">属性形式</label>
<div class="col-md-2">
<!-- 使用属性形式调用指令 -->
<input type="text" class="form-control" date-picker>
</div>
</div>
<div class="row">
<label for="style" class="col-md-2 control-label">样式形式</label>
<div class="col-md-2">
<!-- 使用样式形式调用指令 -->
<input type="text" class="form-control date-picker"
</div>
</div>
9.运行
菜单及路由切换如图
与后台交互如图
自定义指定如图