JavaScript学习总结(二)——延迟对象、跨域、模板引擎、弹出层、AJAX示例
一、AJAX示例
AJAX全称为“Asynchronous JavaScript And XML”(异步JavaScript和XML) 是指一种创建交互式网页应用的开发技术、改善用户体验,实现无刷新效果。
1.1、优点
不需要插件支持
优秀的用户体验
提高Web程序的性能
减轻服务器和带宽的负担
1.2、缺点
浏览器对XMLHttpRequest对象的支持度不足,几乎所有浏览器现在都支持
破坏浏览器“前进”、“后退”按钮的正常功能,可以通过简单的插件弥补
对搜索引擎的支持不足
1.3、jQuery AJAX示例
在HTML5中对原生的AJAX核心对象XMLHttpRequest进行升级,也就是XHR2,功能更加强大。
jQuery对AJAX封装的非常好,这里以简单的商品管理为示例使用jQuery完成AJAX应用。
Product.java bean:
package com.gomall.bean; /*** * 产品 * * @author Administrator * */ public class Product { /** 编号 */ private int id; /** 名称 */ private String name; /** 价格 */ private double price; /** 图片 */ private String picture; /** 详细 */ private String detail; @Override public String toString() { return "Product [id=" + id + ", name=" + name + ", price=" + price + ", picture=" + picture + ", detail=" + detail + "]"; } public Product(int id, String name, double price, String picture) { super(); this.id = id; this.name = name; this.price = price; this.picture = picture; } public Product(int id, String name, double price, String picture, String detail) { super(); this.id = id; this.name = name; this.price = price; this.picture = picture; this.detail = detail; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public String getPicture() { return picture; } public void setPicture(String picture) { this.picture = picture; } public String getDetail() { return detail; } public void setDetail(String detail) { this.detail = detail; } }
IProductService.java:
package com.gomall.service; import java.util.List; import com.gomall.bean.Product; public interface IProductService { /**获得所有*/ ListgetAll(); /**添加 * @return */ boolean add(Product entity); /**根据编号获得产品对象*/ Product findById(int id); /**根据编号获得产品对象 * @return */ boolean deleteById(int id); }
ProductService.java:
package com.gomall.service; import java.util.ArrayList; import java.util.List; import java.util.Random; import com.gomall.bean.Product; public class ProductService implements IProductService { public static ArrayListproducts; static { products = new ArrayList<>(); Random random = new Random(); for (int i = 1; i <= 10; i++) { Product product = new Product(i, "华为Mate9MHA-AL00/4GB RAM/全网通华为超级闪充技术双后摄设计" + random.nextInt(999), random.nextDouble() * 1000, "pic(" + i + ").jpg", "产品详细"); products.add(product); } } /* * (non-Javadoc) * * @see com.gomall.service.IProductService#getAll() */ @Override public List getAll() { return products; } /* * (non-Javadoc) * * @see com.gomall.service.IProductService#add(com.gomall.bean.Product) */ @Override public boolean add(Product entity) { try { entity.setId(products.size() + 1); entity.setPicture("pic(" + entity.getId() + ").jpg"); // uploadify // 上传图片 products.add(entity); } catch (Exception e) { e.printStackTrace(); return false; } return true; } /* * (non-Javadoc) * * @see com.gomall.service.IProductService#findById(int) */ @Override public Product findById(int id) { for (Product product : products) { if (product.getId() == id) { return product; } } return null; } /* * (non-Javadoc) * * @see com.gomall.service.IProductService#deleteById(int) */ @Override public boolean deleteById(int id) { try { Product product = findById(id); if (product != null) { products.remove(product); } } catch (Exception e) { e.printStackTrace(); return false; } return true; } }
ProductAction.java:
package com.gomall.action; import java.io.IOException; import java.io.PrintWriter; import java.util.Date; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.codehaus.jackson.map.ObjectMapper; import com.gomall.bean.Product; import com.gomall.service.IProductService; import com.gomall.service.ProductService; @WebServlet("/Product") public class ProductAction extends HttpServlet { private static final long serialVersionUID = 1L; public ProductAction() { super(); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { /*模拟网络延时*/ try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } request.setCharacterEncoding("utf-8"); response.setCharacterEncoding("utf-8"); String act = request.getParameter("act"); IProductService productService = new ProductService(); /**用于序列化json*/ ObjectMapper mapper = new ObjectMapper(); PrintWriter out=response.getWriter(); if (act.equals("getAll")) { String json = mapper.writeValueAsString(productService.getAll()); out.append(json); } else if (act.equals("area")) { String callback=request.getParameter("callback"); out.append(callback+"('"+new Date()+"')"); } else if (act.equals("getJSONP")) { String callback=request.getParameter("callback"); String json = mapper.writeValueAsString(productService.getAll()); out.append(callback+"("+json+")"); } else if (act.equals("getAllCORS")) { /**向响应的头部中添加CORS信息*/ response.addHeader("Access-Control-Allow-Origin", "*"); response.addHeader("Access-Control-Allow-Methods", "GET,POST"); String json = mapper.writeValueAsString(productService.getAll()); out.append(json); } else if(act.equals("del")){ /**向响应的头部中添加CORS信息*/ response.addHeader("Access-Control-Allow-Origin", "*"); response.addHeader("Access-Control-Allow-Methods", "GET,POST"); int id=Integer.parseInt(request.getParameter("id")); String json = mapper.writeValueAsString(productService.deleteById(id)); out.append(json); } else if(act.equals("add")){ /**向响应的头部中添加CORS信息*/ response.addHeader("Access-Control-Allow-Origin", "*"); response.addHeader("Access-Control-Allow-Methods", "GET,POST"); String name=request.getParameter("name"); double price=Double.parseDouble(request.getParameter("price")); String detail=request.getParameter("detail"); Product entity=new Product(0, name, price, "",detail); String json = mapper.writeValueAsString(productService.add(entity)); out.append(json); } } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
客户端跨域调用:
DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>AJAXtitle> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /> <style type="text/css"> @CHARSET "UTF-8"; * { margin: 0; padding: 0; font-family: microsoft yahei; font-size: 14px; } body { padding-top: 20px; } .main { width: 90%; margin: 0 auto; border: 1px solid #777; padding: 20px; } .main .title { font-size: 20px; font-weight: normal; border-bottom: 1px solid #ccc; margin-bottom: 15px; padding-bottom: 5px; color: blue; } .main .title span { display: inline-block; font-size: 20px; background: blue; color: #fff; padding: 0 8px; background: blue; } a { color: blue; text-decoration: none; } a:hover { color: orangered; } .tab td, .tab, .tab th { border: 1px solid #777; border-collapse: collapse; } .tab td, .tab th { line-height: 26px; height: 26px; padding-left: 5px; } .abtn { display: inline-block; height: 20px; line-height: 20px; background: blue; color: #fff; padding: 0 5px; } .btn { height: 20px; line-height: 20px; background: blue; color: #fff; padding: 0 8px; border: 0; } .abtn:hover, .btn:hover { background: orangered; color: #fff; } p { padding: 5px 0; } fieldset { border: 1px solid #ccc; padding: 5px 10px; } fieldset legend { margin-left: 10px; font-size: 16px; } .pic { height: 30px; width: auto; } #divFrom{ display: none; } style> head> <body> <div class="main"> <h2 class="title"><span>商品管理span>h2> <table border="1" width="100%" class="tab" id="tabGoods"> <tr> <th>编号th> <th>图片th> <th>商品名th> <th>价格th> <th>详细th> <th>操作th> tr> table> <p style="color: red" id="message">p> <p> <a href="#" class="abtn" id="btnSave">添加a> <input type="submit" value="删除选择项" class="btn" /> p> <div id="divFrom"> <form id="formPdt"> <fieldset> <legend>添加商品legend> <p> <label for="name"> 名称: label> <input type="text" name="name" id="name" /> p> <p> <label for="price"> 价格: label> <input type="text" name="price" id="price" /> p> <p> <label for="detail"> 详细: label> <textarea id="detail" name="detail" cols="60">textarea> p> fieldset> form> div> div> <link rel="stylesheet" type="text/css" href="js/artDialog6/ui-dialog.css" /> <script src="js/jQuery1.11.3/jquery-1.11.3.min.js" type="text/javascript" charset="utf-8">script> <script src="js/artDialog6/dialog-min.js" type="text/javascript" charset="utf-8">script> <script type="text/javascript"> var app = { url: "http://localhost:8087/JavaScript001/", //提供服务的域名 add: function() { var d = dialog({ title: '添加商品', content: $('#divFrom').html(), okValue: '添加', modal:true, backdropOpacity:0.3, ok: function() { var that = this; $.ajax({ type: "post", data: $(".ui-dialog #formPdt").serialize() + "&act=add", success: function(data) { if(data) { app.log("添加成功!"); app.loadAll(); that.close(); } else { app.log("添加失败!"); } } }); return false; }, cancelValue: '关闭', cancel: function() { alert('你点了取消按钮') }, onclose:function(){ alert("关闭了"); } }); d.show(); }, del: function() { //closest离当前元素最近的td父元素 id = $(this).closest("td").data("id"); var that = $(this); $.ajax({ type: "get", data: { "id": id, "act": "del" }, success: function(data) { if(data) { that.closest("tr").remove(); app.log("删除成功!"); } else { app.log("删除失败!"); } } }); }, loadAll: function() { $.ajax({ type: "get", data: { "act": "getAllCORS" }, success: function(data) { $("#tabGoods tr:gt(0)").remove(); $.each(data, function(index, obj) { var tr = $(""); //行 $(" ").html(obj.id).appendTo(tr); //编号 var imgTd = $(" "); $(" ", { "src": app.url + "images/" + obj.picture, "class": "pic" }).appendTo(imgTd); //图片 imgTd.appendTo(tr); $("
").html(obj.name).appendTo(tr); $(" ").html(Math.ceil(obj.price)).appendTo(tr); $(" ").html(obj.detail).appendTo(tr); $(" ").html("删除").data("id", obj.id).appendTo(tr); $("#tabGoods").append(tr); }); } }); }, init: function() { /*动态绑定删除事件*/ $("#tabGoods").on("click", "a.del", {}, app.del); /*绑定添加事件*/ $("#btnSave").click(app.add); /*设置全局AJAX默认值*/ $.ajaxSetup({ dataType: "json", url: app.url + "Product?type=meat-and-filler&format=json", beforeSend: app.ajaxBefore, complete: app.clearMsg, error: function(xhr, textStatus, errorThrown) { app.log("错误" + textStatus + errorThrown); } }); this.loadAll(); }, clearMsg: function() { this.box.remove(); }, ajaxBefore: function() { this.box=dialog({ modal:true }); this.box.show(); }, log: function(msg) { $("#message").html(msg); } }; app.init(); script> body> html>
运行结果:
删除:
二、延迟对象(Deferred)
deferred对象就是jQuery1.5版以后新增加的回调函数解决方案。
2.0、方法作为参数传递
在静态语言如C#中可以使用委托将方法作为参数传递,C++中可以使用方法指针,因为JavaScript是动态语言,方法作为参数传递非常便捷,示例如下:
示例1:
DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>title>
head>
<body>
<script type="text/javascript">
//将方法作为参数带给另一个方法
//C# 委托
//C++ 方法指针
//Java 没有,匿名内部类,接口
function handler(arr, f1) {
for(var i = 0; i < arr.length; i++) {
arr[i] = f1(arr[i]);
}
return arr;
}
function add(n) {
return n + 1;
}
function sub(n) {
return n - 1;
}
var result = handler([1, 2, 3], function(n) {
return n * 3;
});
console.log(result);
script>
body>
html>
结果:
示例2:
DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>title>
head>
<body>
<script type="text/javascript">
function hello(f){
console.info("问好:");
var x=100;
f(x);
};
hello(function(n){
console.log("Hello!"+n);
});
hello(function(n){
console.log("你好!"+n);
});
script>
body>
html>
结果:
函数自动提升
在Javascript中定义一个函数,有两种写法:
function foo() { }
var foo = function () { }
两种写法完全等价。但是在解析的时候,前一种写法会被解析器自动提升到代码的头部,因此违背了函数应该先定义后使用的要求,所以建议定义函数时,全部采用后一种写法。
示例:
DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>title>
head>
<body>
<script type="text/javascript">
console.log(typeof f1);
f1();
console.log(typeof f2); //undefined
f2();
var f2 = function() {
console.log('f2');
}
//函数自动提升
function f1() {
console.info('f1');
}
script>
body>
html>
结果:
当函数作为参数传递时应该在调用前判断,避免异常,示例:
DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>title>
head>
<body>
<script type="text/javascript">
//将方法作为参数带给另一个方法
//C# 委托
//C++ 方法指针
//Java 没有,匿名内部类,接口
function handler(arr, f1) {
if(f1&&typeof f1==="function") {
for(var i = 0; i < arr.length; i++) {
arr[i] = f1(arr[i]);
}
}
else{
throw "未指定参数";
}
return arr;
}
function add(n) {
return n + 1;
}
function sub(n) {
return n - 1;
}
var result = handler([1, 2, 3], function(n) {
return n * 3;
});
console.log(result);
result=handler([2, 3, 8],function(){});
console.log(result);
script>
body>
html>
结果:
回调方法在异步请求中的使用:
DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>title>
head>
<body>
<script src="../js/jQuery1.12.3/jquery-1.12.3.min.js" type="text/javascript" charset="utf-8">script>
<script type="text/javascript">
var fun=function(student){
console.log(student.name);
};
function getStudents(){
//异步
$.getJSON("student.json",function(data){
if(fun)
{
fun(data);
}
});
}
var student=getStudents(function(student){
console.log(student.name);
});
script>
body>
html>
结果:
2.1、回调函数
先看一个示例:
首先,为什么要使用Deferred?
DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>回调title>
head>
<body>
<script src="js/jQuery1.11.3/jquery-1.11.3.min.js" type="text/javascript" charset="utf-8">script>
<script type="text/javascript">
var student;
$.get("student.json",function(data){
student=data;
},"json");
alert(student);
script>
body>
html>
student.json文件:{"name":"tom","id":"01"}
运行结果:
因为AJAX是异步执行的,类似高级语言中的多线程,当发起ajax请求时会有网络延迟,而代码并没有在$.get的位置被阻塞,alert先执行,但数据并没有从远程获取到,所以结果是undefined。
其实初学者经常会犯这种错误,如:
function getStudentById(id){ $.get("students.do",{"id":id},function(stu){ return stu; },"json"); }
上面的代码是有问题的,原因如前面的示例是一样的。怎么解决,如果你认为是异步带来的问题,当然通过同步是可以解决的,如:
$.ajax({ type:"get", url:"student.json", async:false, /*非异步,同步*/ success:function(data){ student=data; } });
结果:
如果将所有的ajax请求修改为同步的,则ajax的好处就大打折扣了,如果即要异步又要解决上面的问题,可以使用回调方法。
示例:
DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>回调title>
head>
<body>
<script src="js/jQuery1.11.3/jquery-1.11.3.min.js" type="text/javascript" charset="utf-8">script>
<script type="text/javascript">
/*callback是一个当ajax请求成功时的回调方法*/
function getStudent(callback) {
$.get("student.json", function(data) {
callback(data);
}, "json");
}
/*调用getStudent函数,传入参数,参数也是一个函数*/
getStudent(function(stu){
alert(stu.id);
});
/*把一个方法作为参数传递给另一个方法可以认为是委托,如C++中的函数指针类似*/
getStudent(function(stu){
alert(stu.name);
});
script>
body>
html>
结果:
从这里看回调很完美,其实不然,实际开发中要复杂得多,如当第一个ajax请求完成才可以完成第二个,当第二个完成才可以完成第三个,可能最一个请求要等前面的所有请求都成功时才允许执行或才有条件执行,如
使用ajax编辑用户信息,先加载用户对象,再加载省,加载市,加县,可能代码会这样写:
$.get("url1",function(){
$.get("url2",function(){
$.get("url3",function(){
...
});
});
});
当回调越来越多,嵌套越深,代码可读性就会越来越差。如果注册了多个回调,那更是一场噩梦,幸好从jQuery1.5开始出现了延迟对象(deferred),可以解决这个问题。
2.2、deferred.done
$.ajax()操作完成后,如果使用的是低于1.5.0版本的jQuery,返回的是XHR对象,你没法进行链式操作;如果高于1.5版,返回的是deferred对象,可以进行链式操作。
当延迟成功时调用一个函数或者数组函数,功能与原success类似。
语法:deferred.done(doneCallbacks[,doneCallbacks])
返回值:Deferred Object
该参数可以是一个函数或一个函数的数组。当延迟成功时,doneCallbacks被调用。回调执行是依照他们添加的顺序。一旦deferred.done()返回延迟对象,延迟对象的其它方法也可以链接到了这里,包括增加.done()方法。当延迟解决,doneCallbacks执行使用参数提供给 http://tomcat.apache.org/tomcat-7.0-doc/config/filter.html
依赖:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0modelVersion> <groupId>com.zhangguo.corsgroupId> <artifactId>TestCorsartifactId> <version>1.0-SNAPSHOTversion> <packaging>warpackaging> <name>TestCors Maven Webappname> <url>http://www.example.comurl> <properties> <project.build.sourceEncoding>UTF-8project.build.sourceEncoding> <maven.compiler.source>1.7maven.compiler.source> <maven.compiler.target>1.7maven.compiler.target> properties> <dependencies> <dependency> <groupId>junitgroupId> <artifactId>junitartifactId> <version>4.11version> <scope>testscope> dependency> <dependency> <groupId>javax.servletgroupId> <artifactId>jstlartifactId> <version>1.2version> dependency> <dependency> <groupId>javax.servletgroupId> <artifactId>javax.servlet-apiartifactId> <version>3.0.1version> <scope>providedscope> dependency> <dependency> <groupId>javax.servlet.jspgroupId> <artifactId>jsp-apiartifactId> <version>2.1version> <scope>providedscope> dependency><dependency> <groupId>com.fasterxml.jackson.coregroupId> <artifactId>jackson-coreartifactId> <version>2.5.2version> dependency> <dependency> <groupId>com.fasterxml.jackson.coregroupId> <artifactId>jackson-databindartifactId> <version>2.5.2version> dependency> dependencies> <build> <finalName>TestCorsfinalName> <pluginManagement> <plugins> <plugin> <artifactId>maven-clean-pluginartifactId> <version>3.0.0version> plugin> <plugin> <artifactId>maven-resources-pluginartifactId> <version>3.0.2version> plugin> <plugin> <artifactId>maven-compiler-pluginartifactId> <version>3.7.0version> plugin> <plugin> <artifactId>maven-surefire-pluginartifactId> <version>2.20.1version> plugin> <plugin> <artifactId>maven-war-pluginartifactId> <version>3.2.0version> plugin> <plugin> <artifactId>maven-install-pluginartifactId> <version>2.5.2version> plugin> <plugin> <artifactId>maven-deploy-pluginartifactId> <version>2.8.2version> plugin> plugins> pluginManagement> build> project> org.apache.tomcat tomcat-catalina 8.5.33
添加过滤器,尽量添加在最前面:
<filter> <filter-name>CorsFilterfilter-name> <filter-class>org.apache.catalina.filters.CorsFilterfilter-class> <init-param> <param-name>cors.allowed.originsparam-name> <param-value>https://www.apache.orgparam-value> init-param> <init-param> <param-name>cors.allowed.methodsparam-name> <param-value>GET,POST,HEAD,OPTIONS,PUTparam-value> init-param> <init-param> <param-name>cors.allowed.headersparam-name> <param-value>Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headersparam-value> init-param> <init-param> <param-name>cors.exposed.headersparam-name> <param-value>Access-Control-Allow-Origin,Access-Control-Allow-Credentialsparam-value> init-param> <init-param> <param-name>cors.support.credentialsparam-name> <param-value>trueparam-value> init-param> <init-param> <param-name>cors.preflight.maxageparam-name> <param-value>10param-value> init-param> filter> <filter-mapping> <filter-name>CorsFilterfilter-name> <url-pattern>/*url-pattern> filter-mapping>
如果使用了Spring MVC,请开启Spring对OPTIONS的支持:
<init-param> <param-name>dispatchOptionsRequestparam-name> <param-value>trueparam-value> init-param>
客户端:
DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>跨域title>
head>
<body>
<script src="../js/jquery-1.11.3.min.js" type="text/javascript" charset="utf-8">script>
<script type="text/javascript">
$.ajax({
type:"PUT",
url:"http://localhost:8080/mvc08/u",
contentType:"application/json;charset=utf-8",
dataType:"json",
success:function(data){
console.log(data);
}
});
script>
body>
html>
服务器:
package com.zhangguo.springmvc08.action; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Enumeration; @WebServlet(name = "UserServlet", value = "/u") public class UserServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.getWriter().write("{\"name\":\"Book\"}"); } @Override protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } @Override protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } @Override protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } }
结果:
web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <welcome-file-list> <welcome-file>users/tomwelcome-file> welcome-file-list> <servlet> <servlet-name>springMVCservlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class> <init-param> <param-name>contextConfigLocationparam-name> <param-value>classpath*:spring/spring-mvc.xmlparam-value> init-param> <init-param> <param-name>dispatchOptionsRequestparam-name> <param-value>trueparam-value> init-param> <load-on-startup>1load-on-startup> <async-supported>trueasync-supported> servlet> <servlet-mapping> <servlet-name>springMVCservlet-name> <url-pattern>/url-pattern> servlet-mapping> <filter> <filter-name>CORSfilter-name> <filter-class>com.thetransactioncompany.cors.CORSFilterfilter-class> <init-param> <param-name>cors.allowOriginparam-name> <param-value>http://127.0.0.1:8020param-value> init-param> <init-param> <param-name>cors.supportedMethodsparam-name> <param-value>POST,GET,OPTIONS,DELETE,PUTparam-value> init-param> <init-param> <param-name>cors.supportedHeadersparam-name> <param-value>Content-Type,Accept,Origin,XRequestedWith,ContentType,LastModifiedparam-value> init-param> <init-param> <param-name>cors.exposedHeadersparam-name> <param-value>SetCookieparam-value> init-param> <init-param> <param-name>cors.supportsCredentialsparam-name> <param-value>trueparam-value> init-param> filter> <filter-mapping> <filter-name>CORSfilter-name> <url-pattern>/*url-pattern> filter-mapping> <filter> <filter-name>encodingFilterfilter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class> <init-param> <param-name>encodingparam-name> <param-value>UTF-8param-value> init-param> <init-param> <param-name>forceEncodingparam-name> <param-value>trueparam-value> init-param> filter> <filter-mapping> <filter-name>encodingFilterfilter-name> <url-pattern>/*url-pattern> filter-mapping> web-app>
3.4.4、Servlet支持CORS
通过修改请求头部门信息可以实现Servlet完成复杂跨域功能,示例如下:
后台:
package com.zhangguo.springmvc08.action; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Enumeration; @WebServlet(name = "ProductServlet", value = "/pdt") public class ProductServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //获得所有头部信息 Enumerationitems=request.getHeaderNames(); String headers="Content-Type,Accept,Origin,XRequestedWith,ContentType,LastModified,Content-Type,ContentType,content-type"; while(items.hasMoreElements()){ headers+=","+items.nextElement(); } //设置允许CORS的域名,如果是所有则使用* response.addHeader("Access-Control-Allow-Origin", "http://127.0.0.1:8020"); response.addHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, TRACE, OPTIONS,PUT,DELETE"); response.addHeader("Access-Control-Request-Headers", "Origin,X-Requested-With,Content-Type,Accept"); response.addHeader("Access-Control-Allow-Credentials", "true"); response.getWriter().write("{\"name\":\"Book\"}"); } @Override protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.addHeader("Access-Control-Allow-Headers", "Content-type"); doGet(req, resp); } @Override protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } @Override protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } }
这里在实际使用中有遇到,所有支持的头部一时可能不能完全写出来,而又不想在这一层做过多的判断,没关系,事实上通过request的header可以直接取到Access-Control-Request-Headers,直接把对应的value设置到Access-Control-Allow-Headers即可
前台:
DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>跨域title>
head>
<body>
<script src="../js/jquery-1.11.3.min.js" type="text/javascript" charset="utf-8">script>
<script type="text/javascript">
$.ajax({
type:"PUT",
url:"http://localhost:8080/mvc08/pdt",
contentType:"application/json;charset=utf-8",
dataType:"json",
success:function(data){
console.log(data);
}
});
script>
body>
html>
结果:
3.4.5、Spring MVC4.2+ CORS注解
Spring MVC4.2 及以上增加了对CORS的支持
一个应用可能会有多个 CORS 配置,并且可以设置每个 CORS 配置针对一个接口或一系列接口或者对所有接口生效。
对第一种情况,如果想要对某一接口配置 CORS,可以在方法上添加 CrossOrigin 注解:
@CrossOrigin(origins = {"http://localhost:9000", "null"})
@RequestMapping(value = "/test", method = RequestMethod.GET)
public String greetings() {
return "{\"project\":\"just a test\"}";
}
第二种情况,如果想对一系列接口添加 CORS 配置,可以在类上添加注解,对该类声明所有接口都有效:
@CrossOrigin(origins = {"http://localhost:8080", "null"})
@RestController
public class HomeController{
}
第三种情况,添加全局配置,则需要添加一个配置类:
@Configuration public class WebConfig extends WebMvcConfigurerAdapter { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("http://localhost:9000", "null") .allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE") .maxAge(3600) .allowCredentials(true); } }
可以通过添加 Filter 的方式,配置 CORS 规则,并手动指定对哪些接口有效。
@Bean public FilterRegistrationBean corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); config.addAllowedOrigin("http://localhost:9000"); config.addAllowedOrigin("null"); config.addAllowedHeader("*"); config.addAllowedMethod("*"); source.registerCorsConfiguration("/**", config); // CORS 配置对所有接口都有效 FilterRegistrationBean bean = newFilterRegistrationBean(new CorsFilter(source)); bean.setOrder(0); return bean; }
也可以修改配置文件:
<mvc:cors> <mvc:mapping path="/**" allowed-origins="http://127.0.0.1:8020" allowed-methods="POST,GET, OPTIONS,DELETE,PUT" allowed-headers="Content-Type,ContentType,Access-Control-Allow-Headers, Authorization, X-Requested-With" allow-credentials="true"/> mvc:cors>
员工管理的跨域综合示例
后台REST服务:
package com.zhangguo.springmvc08.controller; import com.zhangguo.springmvc08.entity.User; import com.zhangguo.springmvc08.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.List; @RestController @RequestMapping(path = "/emps") public class EmpController extends BaseController { @Autowired UserService userService; @RequestMapping(path = "") public AjaxState getAllEmps(HttpServletRequest request, HttpServletResponse response) { Listusers=userService.queryAllUsers(); boolean result=users!=null; return new AjaxState(result?"success":"error",users,result?"获得数据成功!":"获得数据失败!"); } @RequestMapping(path = "/{id}", method = RequestMethod.GET) public AjaxState getEmpById(@PathVariable int id) { User user=userService.getUserById(id); boolean result=user!=null; return new AjaxState(result?"success":"error",user,result?"获得数据成功!":"获得数据失败!"); } @RequestMapping(path = "", method = RequestMethod.POST) public AjaxState addEmp(@RequestBody User user) { boolean result=userService.addUser(user); return new AjaxState(result?"success":"error",user,result?"添加成功!":"添加失败"); } @RequestMapping(path = "", method = RequestMethod.PUT) public AjaxState updateEmp(@RequestBody User user) { boolean result=userService.editUser(user); return new AjaxState(result?"success":"error",user,result?"修改成功!":"修改失败"); } @RequestMapping(path = "/{id}", method = RequestMethod.DELETE) public AjaxState deleteEmpById(@PathVariable int id) { Boolean result=userService.deleteUser(id); return new AjaxState(result?"success":"error",id,result?"删除成功!":"删除失败"); } } class AjaxState{ public String state; public Object data; public String message; public AjaxState(String state, Object data, String message) { this.state = state; this.data = data; this.message = message; } public AjaxState(){} }
Spring配置文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd"> <context:annotation-config/> <context:component-scan base-package="com.zhangguo.springmvc08"> context:component-scan> <mvc:default-servlet-handler/> <mvc:cors> <mvc:mapping path="/**" allowed-origins="http://127.0.0.1:8020" allowed-methods="POST,GET, OPTIONS,DELETE,PUT" allowed-headers="Content-Type,ContentType,Access-Control-Allow-Headers, Authorization, X-Requested-With" allow-credentials="true"/> mvc:cors> <mvc:annotation-driven> <mvc:message-converters register-defaults="true"> <bean class="org.springframework.http.converter.StringHttpMessageConverter"> <constructor-arg value="UTF-8"/> bean> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="objectMapper"> <bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"> <property name="failOnEmptyBeans" value="false"/> bean> property> bean> mvc:message-converters> mvc:annotation-driven> <bean id="defaultViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> <property name="prefix" value="/WEB-INF/views/"/> <property name="suffix" value=".jsp"/> bean> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="20971500"/> <property name="defaultEncoding" value="UTF-8"/> <property name="resolveLazily" value="true"/> bean> beans>
前端:
DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<title>员工管理title>
head>
<body>
<h2>员工管理h2>
<table border="1" width="100%" id="tabEmps">
<tr>
<th>编号th>
<th>姓名th>
<th>生日th>
<th>地址th>
<th>电话th>
<th>操作th>
tr>
table>
<p class="loading" style="display: none;">
<img src="img/loading.gif" align="absmiddle">努力加载中...
p>
<form id="formEmps">
<fieldset>
<legend>用户信息legend>
<p>
<label for="name">姓名:label>
<input name="name" id="name" type="text" required="required" maxlength="32"/>
p>
<p>
<label for="birthday">生日:label>
<input name="birthday" id="birthday" type="date" required="required" maxlength="8"/>
p>
<p>
<label for="address">地址:label>
<input name="address" id="address" type="text" required="required" maxlength="128"/>
p>
<p>
<label for="phone">电话:label>
<input name="phone" id="phone" type="text" required="required" maxlength="11"/>
p>
<p>
<input id="id" type="hidden" name="id" value=""/>
<button type="button" id="btnSubmit">保存button>
p>
fieldset>
form>
<p class="message">
p>
<script src="../js/jquery-1.11.3.min.js">script>
<script>
var app = {
url: "http://localhost:8080/mvc08/emps",
init: function () {
$("#btnSubmit").click(app.save);
$("#tabEmps").on("click", ".del", app.delete);
$("#tabEmps").on("click", ".edit", app.edit);
this.binddata();
},
ajax: function (actionType, callback, path, data) {
$.ajax({
url: app.url + (path || ""),
contentType: "application/json;charset=utf-8",
data: JSON.stringify(data) || "{}",
type: actionType || "get",
dataType: "json",
success: function (data) {
if (data && data.state == "success") {
app.info(data.message);
} else if (data && data.state == "error") {
app.info(data.message);
} else {
app.info(data);
}
if (callback) {
callback(data);
}
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
app.info(textStatus + errorThrown);
},
beforeSend: function () {
$(".loading").show(200);
}
,
complete: function () {
$(".loading").hide(200);
}
})
;
},
binddata: function () {
$("#tabEmps tr:gt(0)").remove();
this.ajax("get", function (data) {
$.each(data.data, function (index, emp) {
var tr = $("<tr/>").data("emp", emp).appendTo("#tabEmps");
$("<td/>").text(emp.id).appendTo(tr);
$("<td/>").text(emp.name).appendTo(tr);
$("<td/>").text(emp.birthday).appendTo(tr);
$("<td/>").text(emp.address).appendTo(tr);
$("<td/>").text(emp.phone).appendTo(tr);
$("<td/>").html("<a class='del' href='#'>删除a> | <a class='edit' href='#'>编辑a>").appendTo(tr);
});
});
},
getEmp: function () {
return {
"id": $("#id").val(),
"name": $("#name").val(),
"birthday": $("#birthday").val(),
"address": $("#address").val(),
"phone": $("#phone").val()
};
},
save: function () {
var emp = app.getEmp();
if (emp.id) {
$("#id").val("");
app.update(emp);
} else {
app.add(emp);
}
},
add: function (emp) {
app.ajax("POST", function (data) {
app.binddata();
}, "", emp);
},
update: function (emp) {
app.ajax("Put", function (data) {
app.binddata();
}, "", emp);
},
delete: function () {
if (confirm("删除吗?")) {
var tr = $(this).closest("tr");
var emp = tr.data("emp");
app.ajax("DELETE", function (data) {
tr.remove();
}, "/" + emp.id);
}
},
edit:function(){
var emp = $(this).closest("tr").data("emp");
$("#id").val(emp.id);
$("#name").val(emp.name);
$("#birthday").val(emp.birthday);
$("#address").val(emp.address);
$("#phone").val(emp.phone);
},
info: function (msg) {
$(".message")[0].innerHTML += msg + "<br/>";
}
};
app.init();
script>
body>
html>
运行结果:
其它跨域示例:
.Net服务器一般处理程序代码:
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace jQuery601_DotNet.Action { ////// FindUserById 的摘要说明 /// public class FindUserById : IHttpHandler { public void ProcessRequest(HttpContext context) { context.Response.ContentType = "text/plain"; context.Response.Headers.Add("Access-Control-Allow-Origin","*"); context.Response.Headers.Add("Access-Control-Allow-Methods", "GET,POST"); String name = ""; int id = Convert.ToInt32(context.Request.Params["id"]); if (id == 1001) { name = "Mark"; } else if (id == 1002) { name = "Jack"; } context.Response.Write(name); } public bool IsReusable { get { return false; } } } }
客户端脚本:
DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>6.4.9、跨域AJAX请求title>
head>
<body>
<h2>6.4.9、跨域AJAX请求h2>
<h2 id="message">h2>
<button type="button" id="btnAjax">ajax请求button>
<script type="text/javascript" src="js/jQuery/jquery.min.js">script>
<script type="text/javascript">
$("#btnAjax").click(function() {
$.get("http://localhost:12833/Action/FindUserById.ashx",{"id":1001},function(data){
log(data);
},"text");
});
function log(msg) {
$("#message")[0].innerHTML += msg + "
";
}
script>
body>
html>
运行结果:
从上图可以看到实现跨域且为异步请求。
Java Servlet后台脚本:
package com.gomall.action; import java.io.IOException; import java.io.PrintWriter; import java.util.Date; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.codehaus.jackson.map.ObjectMapper; import com.gomall.service.IProductService; import com.gomall.service.ProductService; @WebServlet("/Product") public class Product extends HttpServlet { private static final long serialVersionUID = 1L; public Product() { super(); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); response.setCharacterEncoding("utf-8"); String act = request.getParameter("act"); IProductService productService = new ProductService(); ObjectMapper mapper = new ObjectMapper(); PrintWriter out=response.getWriter(); if (act.equals("getAll")) { String json = mapper.writeValueAsString(productService.getAll()); out.append(json); } else if (act.equals("area")) { String callback=request.getParameter("callback"); out.append(callback+"('"+new Date()+"')"); } else if (act.equals("getJSONP")) { String callback=request.getParameter("callback"); String json = mapper.writeValueAsString(productService.getAll()); out.append(callback+"("+json+")"); } else if (act.equals("getAllCORS")) { /**向响应的头部中添加内容*/ response.addHeader("Access-Control-Allow-Origin", "*"); response.addHeader("Access-Control-Allow-Methods", "GET,POST"); String json = mapper.writeValueAsString(productService.getAll()); out.append(json); } } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
客户端代码:
DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>AJAXtitle>
head>
<body>
<script src="js/jQuery1.11.3/jquery-1.11.3.min.js" type="text/javascript" charset="utf-8">script>
<script type="text/javascript">
$.get("http://localhost:8087/JavaScript001/Product?act=getAllCORS",function(data){
alert(data);
});
script>
body>
html>
运行结果:
3.4.6、IE8实现CORS跨域的问题
a)、如果认为每次需要修改HTTP头部比较麻烦,在java中可以使用过滤器,.Net可以使用Module或HttpHandler全局注册(注册到Web.Config中,部署时还需要注意)。
b)、如果需要考虑IE8实现CORS则要插件支持,因为IE8并没有完全支持CORS。
插件名称:javascript-jquery-transport-xdr
github: https://github.com/gfdev/javascript-jquery-transport-xdr
示例代码:
DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>AJAXtitle>
head>
<body>
<script src="js/jQuery1.11.3/jquery-1.11.3.min.js" type="text/javascript" charset="utf-8">script>
<script type="text/javascript">
$.get("http://localhost:8087/JavaScript001/Product?act=getAllCORS&type=meat-and-filler&format=json",{},function(data){
alert(data);
},"json");
script>
body>
html>
运行结果:
3.4.7、.Net Core跨域
方法一、修改Web.config文件
<system.webServer> <httpProtocol> <customHeaders> <remove name="Access-Control-Allow-Origin" /> <remove name="Access-Control-Allow-Headers" /> <remove name="Access-Control-Allow-Methods" /> <add name="Access-Control-Allow-Origin" value="*" /> <add name="Access-Control-Allow-Headers" value="Content-Type" /> <add name="Access-Control-Allow-Methods" value="*" /> customHeaders> httpProtocol> system.webServer>
方法二、手动修改HttpResponse对象的头部信息,与Java一样
方法三、使用Microsoft.AspNetCore.Cors
#region 程序集 Microsoft.AspNetCore.Cors, Version=2.2.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60 // C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.aspnetcore.cors\2.2.0\lib\netstandard2.0\Microsoft.AspNetCore.Cors.dll #endregion using System; using Microsoft.AspNetCore.Cors.Infrastructure; namespace Microsoft.AspNetCore.Cors { // [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public class EnableCorsAttribute : Attribute, IEnableCorsAttribute { // // 摘要: // Creates a new instance of the Microsoft.AspNetCore.Cors.EnableCorsAttribute with // the default policy name defined by Microsoft.AspNetCore.Cors.Infrastructure.CorsOptions.DefaultPolicyName. public EnableCorsAttribute(); // // 摘要: // Creates a new instance of the Microsoft.AspNetCore.Cors.EnableCorsAttribute with // the supplied policy name. // // 参数: // policyName: // The name of the policy to be applied. public EnableCorsAttribute(string policyName); // public string PolicyName { get; set; } } }
1)、using Microsoft.AspNetCore.Cors;
2)、使用特性 [EnableCors]
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace WebApplication1.Controllers { [Route("api/[controller]")] [ApiController] [EnableCors] public class TaskController : ControllerBase {} }
配置Startup.cs文件:
public void ConfigureServices(IServiceCollection services) { services.AddCors(options => { options.AddDefaultPolicy( builder => { builder.WithOrigins("http://www.163.com", "http://www.cnblogs.com"); }); options.AddPolicy("AnotherPolicy", builder => { builder.WithOrigins("http://www.baidu.com") .AllowAnyHeader() .AllowAnyMethod(); }); }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); }
services.AddCors(p => p.AddDefaultPolicy( adp => adp.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin().AllowCredentials()) );
3.5、小结
当然除了兼容老浏览器的jsonp跨域与HTML5中的CORS跨域还有很多其它办法如利用iframe和location.hash、window.name实现的跨域数据传输、使用HTML5 postMessage、利用flash等办法。个人认为CORS应该才是未来主要的跨域选择,其它的方法都只是hack。
四、弹出层
前面AJAX示例中添加功能如果放在一个弹出层中布局会更加紧凑一些,像登录,提示信息经常会需要弹出层。
常见的弹出层有:FancyBox,LightBox,colorBox,artDialog,BlockUI,Layer等,这里介绍腾讯开源的artDialog,轻量,实用。
artDialog是一个设计得十分巧妙的对话框组件,小巧身材却拥有丰富的接口与漂亮的外观。
特点是自适应内容、优雅的接口、细致的体验、跨平台兼容、轻量实用。
项目源码: https://github.com/aui/artDialog
帮助信息: http://img0.zz91.com/huanbao/mblog/artDialog-5.0.4
文档与示例: http://aui.github.io/artDialog/doc/index.html
AngularJS 版本: https://github.com/aui/angular-popups
使用方法:
DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>artDialogtitle>
head>
<body>
<button onclick="btn_dialog()">
弹出框
button>
<button onclick="btn_loading()">
加载中
button>
<script src="js/jQuery1.11.3/jquery-1.11.3.js" type="text/javascript" charset="utf-8">script>
<script src="js/artDialog6/dialog-min.js" type="text/javascript" charset="utf-8">script>
<link rel="stylesheet" type="text/css" href="js/artDialog6/ui-dialog.css" />
<script type="text/javascript">
function btn_dialog() {
var d = dialog({
title: '消息',
content: '风吹起的青色衣衫,夕阳里的温暖容颜,你比以前更加美丽,像盛开的花
——许巍《难忘的一天》',
okValue: '确 定',
ok: function() {
var that = this;
setTimeout(function() {
that.title('提交中..');
}, 2000);
return false;
},
cancelValue: '取消',
cancel: function() {
alert('你点了取消按钮')
}
});
d.show();
}
function btn_loading(){
dialog({
modal:true
}).show();
}
script>
body>
html>
运行结果:
属性:
// 对齐方式 //align: 'bottom left', // 是否固定定位 //fixed: false, // 对话框叠加高度值(重要:此值不能超过浏览器最大限制) //zIndex: 1024, // 设置遮罩背景颜色 backdropBackground: '#000', // 设置遮罩透明度 backdropOpacity: 0.7, // 消息内容 content: 'Loading..', // 标题 title: '', // 对话框状态栏区域 HTML 代码 statusbar: '', // 自定义按钮 button: null, // 确定按钮回调函数 ok: null, // 取消按钮回调函数 cancel: null, // 确定按钮文本 okValue: 'ok', // 取消按钮文本 cancelValue: 'cancel', cancelDisplay: true, // 内容宽度 width: '', // 内容高度 height: '', // 内容与边界填充距离 padding: '', // 对话框自定义 className skin: '', // 是否支持快捷关闭(点击遮罩层自动关闭) quickClose: false, // css 文件路径,留空则不会使用 js 自动加载样式 // 注意:css 只允许加载一个 cssUri: '../css/ui-dialog.css',
事件:
/** * 显示对话框 * @name artDialog.prototype.show * @param {HTMLElement Object, Event Object} 指定位置(可选) */ /** * 显示对话框(模态) * @name artDialog.prototype.showModal * @param {HTMLElement Object, Event Object} 指定位置(可选) */ /** * 关闭对话框 * @name artDialog.prototype.close * @param {String, Number} 返回值,可被 onclose 事件收取(可选) */ /** * 销毁对话框 * @name artDialog.prototype.remove */ /** * 重置对话框位置 * @name artDialog.prototype.reset */ /** * 让对话框聚焦(同时置顶) * @name artDialog.prototype.focus */ /** * 让对话框失焦(同时置顶) * @name artDialog.prototype.blur */ /** * 添加事件 * @param {String} 事件类型 * @param {Function} 监听函数 * @name artDialog.prototype.addEventListener */ /** * 删除事件 * @param {String} 事件类型 * @param {Function} 监听函数 * @name artDialog.prototype.removeEventListener */ /** * 对话框显示事件,在 show()、showModal() 执行 * @name artDialog.prototype.onshow * @event */ /** * 关闭事件,在 close() 执行 * @name artDialog.prototype.onclose * @event */ /** * 销毁前事件,在 remove() 前执行 * @name artDialog.prototype.onbeforeremove * @event */ /** * 销毁事件,在 remove() 执行 * @name artDialog.prototype.onremove * @event */ /** * 重置事件,在 reset() 执行 * @name artDialog.prototype.onreset * @event */ /** * 焦点事件,在 foucs() 执行 * @name artDialog.prototype.onfocus * @event */ /** * 失焦事件,在 blur() 执行 * @name artDialog.prototype.onblur * @event */
该插件使用比较简单,可以看示例与源代码。
五、模板引擎
在AJAX示例中javascript中有大量的html字符串,html中有一些像onclick样的javascript,这样javascript中有html,html中有javascript,代码的偶合度很高,不便于修改与维护,使用模板引擎可以解决问题。
模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,用于网站的模板引擎就会生成一个标准的HTML文档。前后端都有模板引擎,比如T4、FreeMarker、Velocity,这里主要讲前端模板引擎:
上图是常见的一些前端模板引擎,速度相对快的是artTemplate,与artDialog是同一个作者,当然一个好的模板引擎不仅是速度还有很多方面都关键。
源码与帮助: https://github.com/aui/artTemplate
5.1、Hello World
示例代码:
DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>artTemplatetitle>
head>
<body>
<div id="result">
div>
<script src="js/artTemplate3/template.js" type="text/javascript" charset="utf-8">script>
<script type="text/html" id="template1">
{{if isShow}}
<h2>姓名:{{name}}</h2>
<ul>
{{each hobbies as hobby index}}
<li>
{{index+1}} {{hobby}}
</li>
{{/each}}
</ul>
{{/if}}
script>
<script type="text/javascript">
var data={
isShow:true,
name:"Tom",
hobbies:["看书","上网","运动","电影","购物"]
};
//用数据与模板渲染(render)出结果
var html=template("template1",data);
document.getElementById("result").innerHTML=html;
script>
body>
html>
运行结果:
生成的代码:
<h2>姓名:Tomh2> <ul> <li> 1 看书 li> <li> 2 上网 li> <li> 3 运动 li> <li> 4 电影 li> <li> 5 购物 li> ul>
5.2、方法
当前最新的版本是4.2与3.x版本有一些区别,4.x版本的方法与选项如下:
// 模板名 filename: null, // 模板语法规则列表 rules: [nativeRule, artRule], // 是否开启对模板输出语句自动编码功能。为 false 则关闭编码输出功能 // escape 可以防范 XSS 攻击 escape: true, // 启动模板引擎调试模式。如果为 true: {cache:false, minimize:false, compileDebug:true} debug: detectNode ? process.env.NODE_ENV !== 'production' : false, // bail 如果为 true,编译错误与运行时错误都会抛出异常 bail: true, // 是否开启缓存 cache: true, // 是否开启压缩。它会运行 htmlMinifier,将页面 HTML、CSS、CSS 进行压缩输出 // 如果模板包含没有闭合的 HTML 标签,请不要打开 minimize,否则可能被 htmlMinifier 修复或过滤 minimize: true, // 是否编译调试版 compileDebug: false, // 模板路径转换器 resolveFilename: resolveFilename, // 子模板编译适配器 include: include, // HTML 压缩器。仅在 NodeJS 环境下有效 htmlMinifier: htmlMinifier, // HTML 压缩器配置。参见 https://github.com/kangax/html-minifier htmlMinifierOptions: { collapseWhitespace: true, minifyCSS: true, minifyJS: true, // 运行时自动合并:rules.map(rule => rule.test) ignoreCustomFragments: [] }, // 错误事件。仅在 bail 为 false 时生效 onerror: onerror, // 模板文件加载器 loader: loader, // 缓存中心适配器(依赖 filename 字段) caches: caches, // 模板根目录。如果 filename 字段不是本地路径,则在 root 查找模板 root: '/', // 默认后缀名。如果没有后缀名,则会自动添加 extname extname: '.art', // 忽略的变量。被模板编译器忽略的模板变量列表 ignore: [], // 导入的模板变量 imports: runtime
方法:
与3.x版本类似,但config方法被取消,更多新查看如下链接
https://aui.github.io/art-template/zh-cn/docs/api.html
方法:
1)、template(id, data)
根据 id 渲染模板。内部会根据document.getElementById(id)查找模板。
如果没有 data 参数,那么将返回一渲染函数。
2)、template.compile(source, options)
将返回一个渲染函数。演示
3)、template.render(source, options)
将返回渲染结果。
4)、template.helper(name, callback)
添加辅助方法,让模板引擎调用自定义的javascript方法。
5)、template.config(name, value)
示例代码:
DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>artTemplatetitle>
head>
<body>
<div id="result">
div>
<script src="js/artTemplate3/template.js" type="text/javascript" charset="utf-8">script>
<script type="text/html" id="template1">
$$if isShow##
<h2>姓名:$$name##</h2>
<ul>
$$include "template2"##
</ul>
$$/if##
script>
<script type="text/html" id="template2">
$$each hobbies as hobby index##
<li>
$$index+1## $$#hobby##
</li>
$$/each##
script>
<script type="text/javascript">
var data={
isShow:true,
name:"Tom",
hobbies:["看书","上网","运动","电影","购物"]
};
//逻辑语法开始标签
template.config("openTag","$$");
//逻辑语法结束标签
template.config("closeTag","##");
//不转义
template.config("escape",false);
//用数据与模板渲染(render)出结果
var html=template("template1",data);
document.getElementById("result").innerHTML=html;
script>
body>
html>
运行结果:
4.x示例:
DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>模板引擎title>
head>
<body>
<div id="div1">
div>
<script src="../js/artTemplate4/lib/template-web.js" type="text/javascript" charset="utf-8">script>
<script type="text/html" id="t1">
<div>
{{if isShow}}
<h2>{{name}}的爱好列表</h2>
<table width="50%" border="1">
<tr>
<th>序号</th>
<th>名称</th>
<th>操作</th>
</tr>
{{each hobbies obj i}}
<tr>
<td>{{i+1}}</td>
<td>{{obj}}</td>
<td>
<a href="#" data-index="{{i}}" class="del">删除</a>
</td>
</tr>
{{/each}}
</table>
{{/if}}
</div>
script>
<script type="text/javascript">
var data = {
isShow: true,
name: "张学友",
hobbies: ["看书", "上网", "运动", "电影", "购物"]
};
var src = document.getElementById("t1");
var html = template.render(src.innerHTML, data, {
escape: false
});
console.log(html);
document.getElementById("div1").innerHTML = html;
script>
body>
html>
结果:
5.3、与AJAX结合应用
示例脚本:
DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>商品管理title>
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<style type="text/css">
@CHARSET "UTF-8";
* {
margin: 0;
padding: 0;
font-family: microsoft yahei;
font-size: 14px;
}
body {
padding-top: 20px;
}
.main {
width: 90%;
margin: 0 auto;
border: 1px solid #777;
padding: 20px;
}
.main .title {
font-size: 20px;
font-weight: normal;
border-bottom: 1px solid #ccc;
margin-bottom: 15px;
padding-bottom: 5px;
color: blue;
}
.main .title span {
display: inline-block;
font-size: 20px;
background: blue;
color: #fff;
padding: 0 8px;
background: blue;
}
a {
color: blue;
text-decoration: none;
}
a:hover {
color: orangered;
}
.tab td,
.tab,
.tab th {
border: 1px solid #777;
border-collapse: collapse;
}
.tab td,
.tab th {
line-height: 26px;
height: 26px;
padding-left: 5px;
}
.abtn {
display: inline-block;
height: 20px;
line-height: 20px;
background: blue;
color: #fff;
padding: 0 5px;
}
.btn {
height: 20px;
line-height: 20px;
background: blue;
color: #fff;
padding: 0 8px;
border: 0;
}
.abtn:hover,
.btn:hover {
background: orangered;
color: #fff;
}
p {
padding: 5px 0;
}
fieldset {
border: 1px solid #ccc;
padding: 5px 10px;
}
fieldset legend {
margin-left: 10px;
font-size: 16px;
}
.pic {
height: 30px;
width: auto;
}
#divFrom {
display: none;
}
style>
head>
<body>
<div class="main">
<h2 class="title"><span>商品管理span>h2>
<table border="1" width="100%" class="tab" id="tabGoods">
<tr>
<th>编号th>
<th>图片th>
<th>商品名th>
<th>价格th>
<th>详细th>
<th>操作th>
tr>
table>
<p style="color: red" id="message">p>
<p>
<a href="#" class="abtn" id="btnSave">添加a>
<input type="submit" value="删除选择项" class="btn" />
p>
<div id="divFrom">
<form id="formPdt">
<fieldset>
<legend>添加商品legend>
<p>
<label for="name">
名称:
label>
<input type="text" name="name" id="name" />
p>
<p>
<label for="price">
价格:
label>
<input type="text" name="price" id="price" />
p>
<p>
<label for="detail">
详细:
label>
<textarea id="detail" name="detail" cols="60">textarea>
p>
fieldset>
form>
div>
div>
<link rel="stylesheet" type="text/css" href="js/artDialog6/ui-dialog.css" />
<script src="js/artTemplate3/template.js" type="text/javascript" charset="utf-8">script>
<script src="js/jQuery1.11.3/jquery-1.11.3.min.js" type="text/javascript" charset="utf-8">script>
<script src="js/artDialog6/dialog-min.js" type="text/javascript" charset="utf-8">script>
<script type="text/html" id="tmpl">
{{each list as pdt}}
<tr>
<td>{{pdt.id}}</td>
<td><img src="http://localhost:8087/JavaScript001/images/{{pdt.picture}}" class="pic"></td>
<td>{{pdt.name}}</td>
<td>{{pdt.price | round:'¥'}}</td>
<td>{{pdt.detail}}</td>
<td>
<a href="#" class="abtn del" data-id={{pdt.id}}>删除</a>
</td>
</tr>
{{/each}}
script>
<script type="text/javascript">
var app = {
url: "http://localhost:8087/JavaScript001/", //提供服务的域名
add: function() {
var d = dialog({
title: '添加商品',
content: $('#divFrom').html(),
okValue: '添加',
modal: true,
backdropOpacity: 0.3,
ok: function() {
var that = this;
$.ajax({
type: "post",
data: $(".ui-dialog #formPdt").serialize() + "&act=add",
success: function(data) {
if(data) {
app.log("添加成功!");
app.loadAll();
that.close();
} else {
app.log("添加失败!");
}
}
});
return false;
},
cancelValue: '关闭',
cancel: function() {
alert('你点了取消按钮')
},
onclose: function() {
alert("关闭了");
}
});
d.show();
},
del: function() {
id = $(this).data("id");
var that = $(this);
$.ajax({
type: "get",
data: {
"id": id,
"act": "del"
},
success: function(data) {
if(data) {
that.closest("tr").remove();
app.log("删除成功!");
} else {
app.log("删除失败!");
}
}
});
},
loadAll: function() {
$.ajax({
type: "get",
data: {
"act": "getAllCORS"
},
success: function(data) {
$("#tabGoods tr:gt(0)").remove();
$("#tabGoods").append(template("tmpl",{list:data}));
}
});
},
init: function() {
/*动态绑定删除事件*/
$("#tabGoods").on("click", "a.del", {}, app.del);
/*绑定添加事件*/
$("#btnSave").click(app.add);
/*设置全局AJAX默认值*/
$.ajaxSetup({
dataType: "json",
url: app.url + "Product?type=meat-and-filler&format=json",
beforeSend: app.ajaxBefore,
complete: app.clearMsg,
error: function(xhr, textStatus, errorThrown) {
app.log("错误" + textStatus + errorThrown);
}
});
//为模板引擎定义辅助函数
template.helper("round",function(value,mark){
return (mark||"")+Math.round(value);
});
this.loadAll();
},
clearMsg: function() {
this.box.remove();
},
ajaxBefore: function() {
this.box = dialog({
modal: true
});
this.box.show();
},
log: function(msg) {
$("#message").html(msg);
}
};
app.init();
script>
body>
html>
运行结果:
六、示例下载
coding: https://coding.net/u/zhangguo5/p/javascript001/git
服务器: https://coding.net/u/zhangguo5/p/javascript001_java/git
github: https://github.com/zhangguo5/javascript01
https://git.dev.tencent.com/zhangguo5/javascriptpro.git
第二版:
前端:https://git.coding.net/zhangguo5/javascript_01.git
后台:https://git.coding.net/zhangguo5/SpringMVC08.git
七、视频
http://www.bilibili.com/video/av17173253/
https://www.bilibili.com/video/av16991874/
八、作业
8.1、请完成一个简单的分布式应用,使用Java作为服务器对外发布服务,PC客户端实现“品牌或商品”的管理,移动端实现如下列表:
- a)、分析出数据库的设计,建库、建表 (MySQL)
- b)、创建后台项目,实现5个服务,可以使用RETSFul (IDEA)
- c)、创建PC Web项目(HBuilder),使用AJAX消费后台提供的5个服务 ,完成增加、删除、修改、查询功能
- d)、创建App项目(HBuilder),先完成页面的静态布局,使用AJAX调用服务
- e)、注意跨域、可以选择三方的UI框架,但界面需完全一样
- f)、在PC Web中,添加,删除,编辑、详细功能请使用artDialog弹出层
- g)、在PC Web与App中请使用artTemplate渲染页面HTML
系统结构如下:
参考网站:https://m.dianping.com/