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 { /**获得所有*/
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);
}
}
运行结果:
删除:
二、延迟对象(Deferred)
deferred对象就是jQuery1.5版以后新增加的回调函数解决方案。
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.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() 为多个操作指定回调函数。
除了这些方法以外,deferred对象还有二个重要方法,上面的教程中没有涉及到。
(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 == )
{
name = "Mark";
}
else if (id == )
{
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 == )
{
name = "Mark";
}
else if (id == )
{
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)
跨域资源共享(CORS)是一种网络浏览器的技术规范,它为Web服务器定义了一种方式,允许网页从不同的域访问其资源。
CORS与JSONP相比:
1、 JSONP只能实现GET请求,而CORS支持所有类型的HTTP请求。
2、 使用CORS,开发者可以使用普通的XMLHttpRequest发起请求和获得数据,比起JSONP有更好的错误处理。
3、 JSONP主要被老的浏览器支持,它们往往不支持CORS,而绝大多数现代浏览器都已经支持了CORS。
CORS有两种模型可以实现:
1.简单模型
支持get/post/put/delete请求,例如返回Access-Control-Allow-Origin:*,但是不允许自定义header且会忽略cookies,且post数据格式有限制,只支持‘text/plain','application/x-www-urlencoded'and'multipart/form-data',其中’text/plain'默认支持,后面两种需要下面的预检请求和服务器协商。
2.协商模型/预检请求(Preflighted Request)
举例:浏览器发出PUT请求,OPTION请求返回Access-Control-Allow-Origin:*,Access-Control-Allow-Methods:’PUT’,服务器同意所有域的PUT请求,浏览器收到并继续发出真正的PUT请求,服务器响应并再次返回Access-Control-Allow-Origin:*,允许浏览器的脚本执行服务器返回的数据。
response.addHeader("Access-Control-Allow-Origin","http://www.company.com");
Access-Control-Allow-Origin:允许跨域访问的域名,如果是公共的则返回*即可
response.addHeader("Access-Control-Allow-Methods","GET,POST,OPTIONS");
Access-Control-Allow-Methods:允许跨域访问的谓词(方法),如GET,POST,DELETE,PUT(REST)
.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 == )
{
name = "Mark";
}
else if (id == )
{
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>
运行结果:
2.4、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>
运行结果:
c)、Apache官方提供一个支持CORS跨域的过滤器,详细说明: http://tomcat.apache.org/tomcat-7.0-doc/config/filter.html
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, // 内容宽度
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>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、方法
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>
运行结果:
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
参照:http://www.cnblogs.com/best/
JavaScript学习笔记(一)——延迟对象、跨域、模板引擎、弹出层、AJAX示例的更多相关文章
- JavaScript:学习笔记(9)——Promise对象
JavaScript:学习笔记(9)——Promise对象 引入Promise Primose是异步编程的一种解决方案,比传统的解决方案回调函数和事件更加合理和强大.如下面为基于回调函数的Ajax操作 ...
- JavaScript:学习笔记(10)——XMLHttpRequest对象
JavaScript:学习笔记(10)——XMLHttpRequest对象 XHR对象 使用XMLHttpRequest (XHR)对象可以与服务器交互.您可以从URL获取数据,而无需让整个的页面刷新 ...
- JavaScript学习总结(一)——延迟对象、跨域、模板引擎、弹出层、AJAX示例
一.AJAX示例 AJAX全称为“Asynchronous JavaScript And XML”(异步JavaScript和XML) 是指一种创建交互式网页应用的开发技术.改善用户体验,实现无刷新效 ...
- JavaScript学习总结(二)——延迟对象、跨域、模板引擎、弹出层、AJAX示例
一.AJAX示例 AJAX全称为“Asynchronous JavaScript And XML”(异步JavaScript和XML) 是指一种创建交互式网页应用的开发技术.改善用户体验,实现无刷新效 ...
- Javascript学习笔记——操作浏览器对象
Javascript学习笔记 目前尝试利用javascript去对于一个浏览器对象完成一系列的访问及修改, 浏览器是网页显示.运行的平台,常用的浏览器有IE.火狐(Firefox).谷歌(Chrome ...
- Vue学习笔记-django-cors-headers安装解决跨域问题
一 使用环境: windows 7 64位操作系统 二 jango-cors-headers安装解决跨域问题(后端解决方案) 跨域,指的是浏览器不能执行其他网站的脚本.它是由浏览器的同源策略造成的 ...
- javascript学习笔记02--面向对象学习
js面向对象编程 1. javascript 是一种基于对象的编程 object-based(基于对象):遇到的所有对象都是对象2.javascript没有类class,但是有新的原型对象,习 ...
- Vue学习笔记-chrome84版本浏览器跨域设置
一 使用环境: windows 7 64位操作系统 二 chrome84版本浏览器跨域设置 报错问题:Indicate whether to send a cookie in a cross- ...
- Bootstrap学习5--bootstrap中的模态框(modal,弹出层)
bootstrap中的模态框(modal),不同于Tooltips,模态框以弹出对话框的形式出现,具有最小和最实用的功能集. 务必将模态框的 HTML 代码放在文档的最高层级内(也就是说,尽量作为 b ...
随机推荐
- 微信企业号 获取AccessToken
目录 1. AccessToken介绍 2. 示例代码 1. AccessToken介绍 1.1 什么是AccessToken AccessToken即访问凭证,业务服务器每次主动调用企业号接口时需要 ...
- 动画requestAnimationFrame
前言 在研究canvas的2D pixi.js库的时候,其动画的刷新都用requestAnimationFrame替代了setTimeout 或 setInterval 但是jQuery中还是采用了s ...
- 2.WindowsServer2012R2装完的一些友好化设置
网站部署之~Windows Server | 本地部署 http://www.cnblogs.com/dunitian/p/4822808.html#iis 1.桌面图标(控制面板里面屏蔽了,得自己输 ...
- 0.Win8.1,Win10,Windows Server 2012 安装 Net Framework 3.5
后期会在博客首发更新:http://dnt.dkill.net 网站部署之~Windows Server | 本地部署:http://www.cnblogs.com/dunitian/p/482280 ...
- pt-pmp
pt-pmp有两方面的作用:一是获取进程的堆栈信息,二是对这些堆栈信息进行汇总. 进程的堆栈信息是利用gdb获取的,所以在获取的过程中,会对mysql服务端的性能有一定的影响. 用官方的话说: Thi ...
- PayPal高级工程总监:读完这100篇论文 就能成大数据高手(附论文下载)
100 open source Big Data architecture papers for data professionals. 读完这100篇论文 就能成大数据高手 作者 白宁超 2016年 ...
- Windos环境用Nginx配置反向代理和负载均衡
Windos环境用Nginx配置反向代理和负载均衡 引言:在前后端分离架构下,难免会遇到跨域问题.目前的解决方案大致有JSONP,反向代理,CORS这三种方式.JSONP兼容性良好,最大的缺点是只支持 ...
- BPM与 SAP & Oracle EBS集成解决方案分享
一.需求分析 SAP和Oracle EBS都是作为全球顶级的的ERP产 品,得到了众多客户的青睐.然而由于系统庞大.价格昂贵以及定位不同,客户在实施过程中经常会面临以下困惑: 1.SAP如何实现&qu ...
- BPM配置故事之案例13-触发消息通知
老李:小明! 小明:--见你就没好事,又要我干嘛? 老李:额,小事小事,最近很多部门都觉得Boss的审批速度太慢了,能不能以后给审批人一个消息提醒? 小明:--有一种不太好的预感 老李:怎么,很困难么 ...
- iOS开源项目周报1229
由OpenDigg 出品的iOS开源项目周报第三期来啦.我们的iOS开源周报集合了OpenDigg一周来新收录的优质的iOS开发方面的开源项目,方便iOS开发人员便捷的找到自己需要的项目工具等. Ma ...