一、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 { /**获得所有*/ List<Product> getAll(); /**添加 * @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 ArrayList<Product> products; 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<Product> 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>AJAX</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/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> <!--[if (IE 8)|(IE 9)]> <script src="js/jquery.transport.xdr.min.js" type="text/javascript" charset="utf-8"></script> <![endif]--> <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 = $("<tr/>"); //行 $("<td/>").html(obj.id).appendTo(tr); //编号 var imgTd = $("<td/>"); $("<img/>", { "src": app.url + "images/" + obj.picture, "class": "pic" }).appendTo(imgTd); //图片 imgTd.appendTo(tr); $("<td/>").html(obj.name).appendTo(tr); $("<td/>").html(Math.ceil(obj.price)).appendTo(tr); $("<td/>").html(obj.detail).appendTo(tr); $("<td/>").html("<a href='#' class='abtn del'>删除</a>").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执行使用参数提供给 resolve或 resolveWith方法依照添加的顺序调用。
示例代码:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>延迟对象(deferred)</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"> console.log("使用方法一"); $.get("student.json", "json").done(function(stu) { console.log(stu.id); }).done(function(stu) { console.log(stu.name); }); console.log("使用方法二"); $.get("student.json", "json").done(function(stu) { console.log(stu.id); }, function(stu) { console.log(stu.name); }); </script> </body> </html>
运行结果:
2.3、deferred.fail
语法:deferred.fail(failCallbacks[,failCallbacks])
返回值:Deferred Object
当延迟失败时调用一个函数或者数组函数,功能与原回调方法error类似。
该参数可以是一个函数或一个函数的数组。当延迟失败时,doneCallbacks被调用。回调执行是依照他们添加的顺序。一旦deferred.fail()返回延迟对象,延迟对象的其它方法也可以链接到了这里,包括增加.done()方法。当延迟解决,doneCallbacks执行使用参数提供给 resolve或 resolveWith方法依照添加的顺序调用。
示例:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>延迟对象(deferred)</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"> $.get("stu.json", "json").done(function(stu) { console.log(stu.name); }).fail(function(xhr, status, errorThrown){ alert("xhr:"+xhr+",status:"+status+",errorThrown:"+errorThrown); }); </script> </body> </html>
运行结果:
2.4、deferred.always
语法:deferred.always(alwaysCallbacks,[alwaysCallbacks])
返回值:Deferred Object
当递延对象是解决(成功, resolved)或拒绝(失败,rejected)时被调用添加处理程序,与回调方法complete类似。
示例:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>延迟对象(deferred)</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"> $.get("student.j", "json").done(function(stu) { console.log(stu.name); }).fail(function(data, status, errorThrown){ console.log("data:"+data+",status:"+status+",errorThrown:"+errorThrown); }).always(function(data, textStatus){ console.log("ajax执行完成,完成状态:"+textStatus); }); </script> </body> </html>
运行结果
成功时:
失败时:
2.5、deferred.then
deferred.then(doneFilter [, failFilter ] [, progressFilter ])
添加处理程序被调用时,递延对象得到解决或者拒绝,一次指定多个事件。
所有三个参数(包括progressCallbacks ,在jQuery的1.7 )可以是一个单独的函数或一个函数的数组。 其中一个参数,也可以为空,如果没有该类型的回调是需要的。或者,使用.done()或.fail()仅设置doneCallbacks或failCallbacks。当递延解决,doneCallbacks被调用。若递延代替拒绝,failCallbacks被调用。回调按他们添加的顺序执行。一旦deferred.then返回延迟对象,延迟对象的其它方法也可以链接到了这里,包括增加.then()方法。
示例:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>延迟对象(deferred)</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"> $.get("student.jsonx", "json").then(function(stu) { console.log(stu.name); }, function(data, status, errorThrown) { console.log("data:" + data + ",status:" + status + ",errorThrown:" + errorThrown); }); </script> </body> </html>
结果:
2.6、应用延迟对象
前面的示例中我们都是使用jQuery ajax返回的deferred对象,其实我们也可以在自定义的代码中使用deferred对象,恰当的使用deferred对象或以优雅的解决不少问题。
示例:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>延迟对象(deferred)</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 myTask=function(){ //通过jQuery创建一个延迟对象 var def=$.Deferred(); setTimeout(function(){ //随机产生一个整数,如果是偶数 var n=Math.round(Math.random()*100); if(n%2==0) { console.log("一个耗时的操作终于完成了,n="+n); def.resolve(); //任务成功完成 }else{ console.log("一个耗时的操作失败,n="+n); def.reject(); //拒绝,失败 } },3000); //返回延迟对象,防止中间修改 return def.promise(); } /*当方法myTask执行完成后,添加回调方法*/ $.when(myTask()).done(function(){ console.log("执行成功"); }).fail(function(){ console.log("执行失败"); }); </script> </body> </html>
失败时:
成功时:
promise()在原来的deferred对象上返回另一个deferred对象,后者只开放与改变执行状态无关的方法(比如done()方法和fail()方法),屏蔽与改变执行状态有关的方法(比如resolve()方法和reject()方法),从而使得执行状态不能被改变。
示例2:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Deferred</title> </head> <body> <script src="../js/jquery-1.11.3.min.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> var task = function() { //创建延迟对象 var def = $.Deferred(); //随机产生一个数 var n = Math.round(Math.random() * 10000); setTimeout(function() { if(n % 2 == 0) { console.log("成功,耗时的任务完成了"); def.resolve(n); //通知成功 } else { console.log("失败,耗时的任务完成了"); def.reject(n); //通知失败 } }, n); return def.promise(); }; $.when(task()).done(function(data) { console.log(data); }).fail(function(data) { console.log(data); }); </script> </body> </html>
结果2:
2.7、总结
(1) $.Deferred() 生成一个deferred对象。
(2) deferred.done() 指定操作成功时的回调函数
(3) deferred.fail() 指定操作失败时的回调函数
(4) deferred.promise() 没有参数时,返回一个新的deferred对象,该对象的运行状态无法被改变;接受参数时,作用为在参数对象上部署deferred接口。
(5) deferred.resolve() 手动改变deferred对象的运行状态为"已完成",从而立即触发done()方法。
(6)deferred.reject() 这个方法与deferred.resolve()正好相反,调用后将deferred对象的运行状态变为"已失败",从而立即触发fail()方法。
(7) $.when() 为多个操作指定回调函数。
(8)deferred.then() 有时为了省事,可以把done()和fail()合在一起写,这就是then()方法。如果then()有两个参数,那么第一个参数是done()方法的回调函数,第二个参数是fail()方法的回调方法。如果then()只有一个参数,那么等同于done()。
(9)deferred.always() 这个方法也是用来指定回调函数的,它的作用是,不管调用的是deferred.resolve()还是deferred.reject(),最后总是执行。
三、跨域
互联网上的主机由IP来标识,为了方便记忆,创建了域名系统.域名与IP对应,域名的作用是不用让你记复杂的IP地址,能唯一定位资源,URL的格式是协议://主机名.公司名称.机构类型.地域类型:端口/路径,如http://www.zhangguo.com.cn:8080/products/list.do?id=1#a1
3.1、什么是跨域
JavaScript同源策略的限制,A域名下的JavaScript无法操作B或是C域名下的对象,如下所示:
假设页面:http://store.company.com/index.html
客户端代码d05.html,http://localhost:8087/jQuery601_JAVA/d05.html
<!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 + "<br/>"; } </script> </body> </html>
另一个域下面一般处理程序,http://localhost:12833/Action/FindUserById.ashx:
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace jQuery601_DotNet.Action { /// <summary> /// 根据用户编号获得用户 /// </summary> public class FindUserById : IHttpHandler { public void ProcessRequest(HttpContext context) { context.Response.ContentType = "text/plain"; 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; } } } }
运行结果:
3.2、JSONP跨域
JSONP跨域是利用script脚本允许引用不同域下的js实现的,将回调方法带入服务器,返回结果时回调。
2.1、JSONP跨域原理
客户端:
<body> <script type="text/javascript"> /*回调方法*/ function show(data) { alert(data); } </script> <script src="http://localhost:8087/JavaScript001/Product?act=area&callback=show" type="text/javascript" charset="utf-8"></script> </body>
服务器:
String callback=request.getParameter("callback"); out.append(callback+"('"+new Date()+"')");
结果:
服务器返回一段javascript,通过指定的方法名调用。从图中可以看出,使用JSONP的形式调用已经不再是通过XMLHTTPRequest对象,而是同步调用。
3.3、jQuery使用JSONP跨域
在jQuery中内置了实现JSONP跨域的功能,如果指定为json类型,则会把获取到的数据作为一个JavaScript对象来解析,并且把构建好的对象作为结果返回。为了实现这个目的,他首先尝试使用JSON.parse()。如果浏览器不支持,则使用一个函数来构建。JSON数据是一种能很方便通过JavaScript解析的结构化数据。如果获取的数据文件存放在远程服务器上(域名不同,也就是跨域获取数据),则需要使用jsonp类型。使用这种类型的话,会创建一个查询字符串参数 callback=? ,这个参数会加在请求的URL后面。服务器端应当在JSON数据前加上回调函数名,以便完成一个有效的JSONP请求。如果要指定回调函数的参数名来取代默认的callback,可以通过设置$.ajax()的jsonp参数。
页面脚本:
<!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?callback=?",{"id":1001},function(data){ log(data); },"jsonp"); }); function log(msg) { $("#message")[0].innerHTML += msg + "<br/>"; } </script> </body> </html>
服务器一般处理程序:
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace jQuery601_DotNet.Action { /// <summary> /// FindUserById 的摘要说明 /// </summary> public class FindUserById : IHttpHandler { public void ProcessRequest(HttpContext context) { context.Response.ContentType = "text/plain"; String name = ""; int id = Convert.ToInt32(context.Request.Params["id"]); if (id == 1001) { name = "Mark"; } else if (id == 1002) { name = "Jack"; } String callback = context.Request["callback"]; context.Response.Write(callback+"('"+name+"')"); } public bool IsReusable { get { return false; } } } }
运行结果:
服务器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+")"); } } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
客户端:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>AJAX</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"> $.get("http://localhost:8087/JavaScript001/Product?callback=?",{act:"getJSONP"},function(data){ $.each(data,function(index,obj){ $("<p/>").html(obj.name).appendTo("body"); }); },"jsonp"); </script> </body> </html>
运行结果:
在jQuery中如果使用JSONP只需要将返回数据类型设置为jsonp就可以了,但是这种方法只支持get请求,不支持post请求;请求是同步的;服务器返回数据要处理,要添加回调函数,麻烦。
3.4、跨域资源共享(CORS)
同源策略(same origin policy)的限制下非同源的网站之间不能发送 ajax 请求的。
w3c 提出了跨源资源共享CORS即Cross Origin Resource Sharing(跨域源资源共享),就是我们所熟知的跨域请求。
跨域资源共享(CORS)是一种网络浏览器的技术规范,它为Web服务器定义了一种方式,允许网页从不同的域访问其资源。
CORS与JSONP相比:
1、 JSONP只能实现GET请求,而CORS支持所有类型的HTTP请求。
2、 使用CORS,开发者可以使用普通的XMLHttpRequest发起请求和获得数据,比起JSONP有更好的错误处理。
3、 JSONP主要被老的浏览器支持,它们往往不支持CORS,而绝大多数现代浏览器都已经支持了CORS。
CORS 将请求分为两类:简单请求和非简单请求:
3.4.1.简单请求
支持get/post/put/delete请求,例如返回Access-Control-Allow-Origin:*,但是不允许自定义header且会忽略cookies,且post数据格式有限制,只支持‘text/plain','application/x-www-urlencoded'and'multipart/form-data',其中’text/plain'默认支持,后面两种需要下面的预检请求和服务器协商。
简单请求对应该规则,因此对简单请求的定义为:
(1) 请求方法是以下三种方法之一:
HEAD
GET
POST
(2)HTTP的头信息不超出以下几种字段:
Accept Accept-Language Content-Language Last-Event-ID Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
简单请求的部分响应头及解释如下:
Access-Control-Allow-Origin(必含)- 不可省略,否则请求按失败处理。该项控制数据的可见范围,如果希望数据对任何人都可见,可以填写"*"。
Access-Control-Allow-Credentials(可选) – 该项标志着请求当中是否包含cookies信息,只有一个可选值:true(必为小写)。如果不包含cookies,请略去该项,而不是填写false。这一项与XmlHttpRequest2对象当中的withCredentials属性应保持一致,即withCredentials为true时该项也为true;withCredentials为false时,省略该项不写。反之则导致请求失败。
Access-Control-Expose-Headers(可选) – 该项确定XmlHttpRequest2对象当中getResponseHeader()方法所能获得的额外信息。通常情况下,getResponseHeader()方法只能获得如下的信息:
Cache-Control Content-Language Content-Type Expires Last-Modified Pragma
当你需要访问额外的信息时,就需要在这一项当中填写并以逗号进行分隔
示例:
服务器端:
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; @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 { //设置允许CORS的域名,如果是所有则使用* response.addHeader("Access-Control-Allow-Origin","*"); response.getWriter().write("{"name":"Book"}"); } }
客户端:
<!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"> //跨域Get请求 $.get("http://localhost:8080/mvc08/pdt", function(data) { console.log("Get:"+data.name); }, "json"); //跨域Post请求 $.post("http://localhost:8080/mvc08/pdt", function(data) { console.log("Post:"+data.name); }, "json"); </script> </body> </html>
结果:
3.4.2.复杂请求
如说你需要发送PUT、DELETE等HTTP动作,或者发送Content-Type: application/json的内容就需要使用复杂请求了。
最先发送的是一种"预请求",此时作为服务端,也需要返回"预回应"作为响应。预请求实际上是对服务端的一种权限请求,只有当预请求成功返回,实际请求才开始执行。预请求以OPTIONS形式发送,当中同样包含域,并且还包含了两项CORS特有的内容
代码:
<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>
结果:
Access-Control-Allow-Origin(必含) – 和简单请求一样的,必须包含一个域,不能是*号。
Access-Control-Allow-Methods(必含) – 这是对预请求当中Access-Control-Request-Method的回复,这一回复将是一个以逗号分隔的列表。尽管客户端或许只请求某一方法,但服务端仍然可以返回所有允许的方法,以便客户端将其缓存。
Access-Control-Allow-Headers(当预请求中包含Access-Control-Request-Headers时必须包含) – 这是对预请求当中Access-Control-Request-Headers的回复,和上面一样是以逗号分隔的列表,可以返回所有支持的头部。
Access-Control-Allow-Credentials(可选) – 和简单请求当中作用相同。
Access-Control-Max-Age(可选) – 以秒为单位的缓存时间,允许时应当尽可能缓存。
3.4.3、CORSFilter
CORSFilter是Apache官方提供一个支持CORS跨域的过滤器,详细说明: 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.0</modelVersion> <groupId>com.zhangguo.cors</groupId> <artifactId>TestCors</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <name>TestCors Maven Webapp</name> <!-- FIXME change it to the project's website --> <url>http://www.example.com</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <!-- JSTL --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- Servlet核心包 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.0.1</version> <scope>provided</scope> </dependency> <!--JSP --> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-catalina</artifactId> <version>8.5.33</version> </dependency> <!-- jackson --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.5.2</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.5.2</version> </dependency> </dependencies> <build> <finalName>TestCors</finalName> <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) --> <plugins> <plugin> <artifactId>maven-clean-plugin</artifactId> <version>3.0.0</version> </plugin> <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging --> <plugin> <artifactId>maven-resources-plugin</artifactId> <version>3.0.2</version> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.7.0</version> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.20.1</version> </plugin> <plugin> <artifactId>maven-war-plugin</artifactId> <version>3.2.0</version> </plugin> <plugin> <artifactId>maven-install-plugin</artifactId> <version>2.5.2</version> </plugin> <plugin> <artifactId>maven-deploy-plugin</artifactId> <version>2.8.2</version> </plugin> </plugins> </pluginManagement> </build> </project>
添加过滤器,尽量添加在最前面:
<filter> <filter-name>CorsFilter</filter-name> <filter-class>org.apache.catalina.filters.CorsFilter</filter-class> <init-param> <param-name>cors.allowed.origins</param-name> <param-value>https://www.apache.org</param-value> </init-param> <init-param> <param-name>cors.allowed.methods</param-name> <param-value>GET,POST,HEAD,OPTIONS,PUT</param-value> </init-param> <init-param> <param-name>cors.allowed.headers</param-name> <param-value>Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers</param-value> </init-param> <init-param> <param-name>cors.exposed.headers</param-name> <param-value>Access-Control-Allow-Origin,Access-Control-Allow-Credentials</param-value> </init-param> <init-param> <param-name>cors.support.credentials</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>cors.preflight.maxage</param-name> <param-value>10</param-value> </init-param> </filter> <filter-mapping> <filter-name>CorsFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
如果使用了Spring MVC,请开启Spring对OPTIONS的支持:
<init-param> <param-name>dispatchOptionsRequest</param-name> <param-value>true</param-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 pages--> <welcome-file-list> <welcome-file>users/tom</welcome-file> </welcome-file-list> <!--配置springmvc DispatcherServlet(中心控制器)--> <servlet> <servlet-name>springMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <!--Sources标注的文件夹下需要新建一个spring文件夹--> <param-name>contextConfigLocation</param-name> <param-value>classpath*:spring/spring-mvc.xml</param-value> </init-param> <init-param> <param-name>dispatchOptionsRequest</param-name> <param-value>true</param-value> </init-param> <load-on-startup>1</load-on-startup> <async-supported>true</async-supported> </servlet> <servlet-mapping> <servlet-name>springMVC</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <filter> <filter-name>CORS</filter-name> <filter-class>com.thetransactioncompany.cors.CORSFilter</filter-class> <init-param> <param-name>cors.allowOrigin</param-name> <param-value>http://127.0.0.1:8020</param-value> </init-param> <init-param> <param-name>cors.supportedMethods</param-name> <param-value>POST,GET,OPTIONS,DELETE,PUT</param-value> </init-param> <init-param> <param-name>cors.supportedHeaders</param-name> <param-value>Content-Type,Accept,Origin,XRequestedWith,ContentType,LastModified</param-value> </init-param> <init-param> <param-name>cors.exposedHeaders</param-name> <param-value>SetCookie</param-value> </init-param> <init-param> <param-name>cors.supportsCredentials</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>CORS</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-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 { //获得所有头部信息 Enumeration<String> items=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) { List<User> users=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"> <!--启用spring的一些annotation --> <context:annotation-config/> <!-- 自动扫描该包,使SpringMVC认为包下用了@controller注解的类是控制器 --> <context:component-scan base-package="com.zhangguo.springmvc08"> </context:component-scan> <!--HandlerMapping 无需配置,springmvc可以默认启动--> <!--静态资源映射--> <mvc:default-servlet-handler/> <!--如果发现是静态资源的请求,就将该请求转由Web应用服务器默认的Servlet处理,如果不是静态资源的请求,才由DispatcherServlet继续处--> <!--本项目把静态资源放在了WEB-INF的statics目录下,资源映射如下--> <!--<mvc:resources mapping="/css/**" location="/WEB-INF/statics/css/"/>--> <!--<mvc:resources mapping="/js/**" location="/WEB-INF/statics/js/"/>--> <!--<mvc:resources mapping="/image/**" location="/WEB-INF/statics/image/"/>--> <!--但是项目部署到linux下发现WEB-INF的静态资源会出现无法解析的情况,但是本地tomcat访问正常,因此建议还是直接把静态资源放在webapp的statics下,映射配置如下--> <!--<mvc:resources mapping="/css/**" location="/statics/css/"/>--> <!--<mvc:resources mapping="/js/**" location="/statics/js/"/>--> <!--<mvc:resources mapping="/image/**" location="/statics/images/"/>--> <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> <!-- 配置注解驱动 可以将request参数与绑定到controller参数上 --> <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> <!-- 对模型视图名称的解析,即在模型视图名称添加前后缀(如果最后一个还是表示文件夹,则最后的斜杠不要漏了) 使用JSP--> <!-- 默认的视图解析器 在上边的解析错误时使用 (默认使用html)- --> <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/"/><!--设置JSP文件的目录位置--> <property name="suffix" value=".jsp"/> </bean> <!-- springmvc文件上传需要配置的节点--> <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 { /// <summary> /// FindUserById 的摘要说明 /// </summary> 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 + "<br/>"; } </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>AJAX</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"> $.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>AJAX</title> </head> <body> <script src="js/jQuery1.11.3/jquery-1.11.3.min.js" type="text/javascript" charset="utf-8"></script> <!--[if (IE 8)|(IE 9)]> <script src="js/jquery.transport.xdr.min.js" type="text/javascript" charset="utf-8"></script> <![endif]--> <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 FilesdotnetsdkNuGetFallbackFoldermicrosoft.aspnetcore.cors2.2.0lib etstandard2.0Microsoft.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>artDialog</title> </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: '风吹起的青色衣衫,夕阳里的温暖容颜,你比以前更加美丽,像盛开的花<br>——许巍《难忘的一天》', 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: '<span class="ui-dialog-loading">Loading..</span>', // 标题 title: '', // 对话框状态栏区域 HTML 代码 statusbar: '', // 自定义按钮 button: null, // 确定按钮回调函数 ok: null, // 取消按钮回调函数 cancel: null, // 确定按钮文本 okValue: 'ok', // 取消按钮文本 cancelValue: 'cancel', cancelDisplay: true, // 内容宽度 '', // 内容高度 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>artTemplate</title> </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>姓名:Tom</h2> <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>artTemplate</title> </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"## <!--包含模板2--> </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:["看书","上网","运动","<b>电影</b>","<i>购物</i>"] }; //逻辑语法开始标签 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: ["看书", "<b>上网</b>", "运动", "电影", "购物"] }; 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> <!--[if (IE 8)|(IE 9)]> <script src="js/jquery.transport.xdr.min.js" type="text/javascript" charset="utf-8"></script> <![endif]--> <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
系统结构如下: