')
// S.node.create('
')
// S.node.create('
') //=> 创建 DIV 节点
// ```
// ```
// S.node.create('
', {
// text: 'ok',
// css : {color: 'red'}
// }); //=> 创建 DIV 节点,内容为'ok',颜色为红色
// ```
create: function(html, props) {
var key,
ret = [],
tag,
container;
if (!html || !isString(html)) {
return ret;
}
if (RE_SINGLE_TAG.test(html)) {
ret = $(doc.createElement(RegExp.$1));
} else {
html = html.replace(RE_XHTML_TAG, '<$1>$2>');
tag = RE_TAG.test(html) && RegExp.$1;
container = containers[tag] || containers['*'];
container.innerHTML = html;
ret = each(slice.call(container.childNodes), function(el) {
container.removeChild(el);
});
}
if (isPlainObject(props)) {
for (key in props) {
ret.attr(key, props[key]);
}
}
return ret;
},
// ** .html() **
//
// * .html()
//
// 获取符合选择器的第一个元素的 innerHTML
//
// * .html(html[, loadScripts])
//
// 给符合选择器的所有元素设置 innerHTML 值
//
// loadScripts 表示是否执行 html 中的内嵌脚本,默认 false
//
// ```
// var el = S.node.create('
');
// var html = [
// '
This is the added part
',
// ''
// ].join('');
// el.html(html);
// //=> 不会 alert(1)
// el.html();
// //=>
This is the added part
// el.html(html, true);
// //=> alert(1)
// ```
html: function(html, loadScripts) {
return html === undefined ?
this.length ? this[0].innerHTML : null
:
each(this, function(el) {
$(el).empty().append(html, loadScripts);
});
},
// ** .remove() **
//
// * .remove()
//
// 将符合选择器的所有元素从 DOM 中移除
remove: function() {
var self = this;
// 移除所有事件绑定
self.detach && self.detach();
return each(self, function(el) {
el.parentNode && el.parentNode.removeChild(el)
});
},
// ** .empty() **
//
// * .empty()
//
// 清除节点的所有子孙节点
empty: function() {
return each(this, function(el) {
el.innerHTML = '';
});
},
// ** .clone() **
//
// * .clone([deep])
//
// 获取符合选择器的第一个元素的克隆元素
//
// deep 表示是否深度克隆(克隆节点的子孙节点),默认 false
clone: function(deep) {
return $(
map(this, function(el) {
return el.cloneNode(!!deep);
})
);
}
});
/**
* @ignore
* @file ie
* @author 莫争
*/
// IE10 及以下浏览器不支持 `__proto__` 继承,需重写 `.node()` 和 `.isNode()` 方法来兼容
if (!('__proto__' in {})) {
mix($, {
node: function(els) {
els = els || [];
mix(els, node);
els.__node = true;
return els;
},
isNode: function(obj) {
return isArray(obj) && '__node' in obj;
}
});
}
/**
* @ignore
* @file output
* @author 莫争
*/
// ** Node 模块提供的快捷方式 **
//
// ```
// mix(S, {
// node : node, // 参元类
// Node : $, // 构造器
// NodeList: $, // 构造器
// one : $.one, // 获取 / 创建一个 Node 对象
// all : $.all // 获取 / 创建一批 Node 对象
// });
// ```
mix(S, {
node : node,
Node : $,
NodeList: $,
one : $.one,
all : $.all
});
S.add && S.add('node', function (S) {
return $;
});
}(this, KISSY));
// ## Event 模块
//
// **Event 用法:**
//
// 1.直接使用
//
// ```
// var $ = KISSY.Node.all;
// $('body').on('click', function(ev){
// console.log(ev)
// });
// ```
//
// 2.普通对象的自定义事件
//
// ```
// var a = {}, S = KISSY;
// S.mix(a, S.Event.Target);
// a.on('my_event', function(ev){
// console.log(ev)
// });
// a.fire('my_event', {"data1": 1, "data2": 2});
// ```
// **未列出的Event API與KISSY保持用法一致**
//
//| API | KISSY | KISSY-MINI |
//| -------------------- |:--------------------:|:--------------------:|
//| Event.Object | YES | NO |
//| Event.Target.publish | YES | NO |
//| Event.Target.addTarget | YES | NO |
//| Event.Target.removeTarget| YES | NO |
//| mouseenter | YES | NO |
//| mouseleave | YES | NO |
//| mousewheel | YES | NO |
//| gestures | YES | `Import touch.js*` |
//| | | |
//
// **与 zeptojs 对比,有以下差异:**
//
// 1. 去除对鼠标兼容事件的支持,包括 mouseenter/mouseleave;
// 2. 提供对普通对象的自定义事件支持,需提前混入 S.Event.Target
//
// **与 KISSY 对比,有以下差异:**
//
// 1. 仅支持链式调用,不支持 Event.on 语法;
// 2. 自定义事件不支持冒泡等属性和方法;
// 3. 触控事件需额外引入 touch.js;
// 4. 回调返回的 event 对象是兼容处理后的原生事件对象,不再提供 ev.originalEvent
(function(S){
S.Event || (S.Event = {});
var $ = S.all,
Node = S.node,
_eid = 1,
isFunction = function(obj){
return typeof obj == 'function';
},
/* 简化 S.mix */
mix = function(target, source) {
for (var key in source) {
target[key] = source[key];
}
},
/* 简化 S.each */
each = function(obj, iterator, context) {
Object.keys(obj).map(function(name){
iterator.call(context, obj[name], name, obj);
});
},
slice = [].slice,
handlers = [],
focusinSupported = 'onfocusin' in window,
/* 焦点事件代理 */
focusEvent = {
focus: 'focusin',
blur: 'focusout'
},
specialEvents = {
"click": "MouseEvent"
},
eventMethods = {
preventDefault: 'isDefaultPrevented',
stopImmediatePropagation: 'isImmediatePropagationStopped',
stopPropagation: 'isPropagationStopped'
};
/**
* 生成返回布尔值函数的方法
* @param {[type]} trueOrFalse [description]
* @return {[type]} [description]
*/
// returnBool(trueOrFalse)
//
// 内部方法,生成返回布尔值函数的方法
function returnBool(trueOrFalse) {
return function(){ return trueOrFalse; };
}
/**
* 生成和 DOM 绑定的唯一 id
* @param {[type]} element [description]
* @return {[type]} [description]
*/
// eid(element)
//
// 内部方法,生成和 DOM 绑定的唯一 id
function eid(element) {
return element._eid || (element._eid = _eid++);
}
/**
* 解析事件字符串
* @param {String} event 原始的事件类型字符串
* @return {Object} 解析后得到的事件类型对象
*/
// parse(event)
//
// 内部方法,解析事件字符串
function parse(event) {
var parts = event.split('.');
return {
e : parts[0],
ns: parts.slice(1).join(' ')
};
}
/**
* 根据事件类型 ns 生成匹配正则,用于判断是否在同一个分组
* @param {String} ns [description]
* @return {RegExp} [description]
*/
// matcherFor(ns)
//
// 内部方法,根据事件类型 ns 生成匹配正则,用于判断是否在同一个分组
function matcherFor(ns) {
return new RegExp('(?:^| )' + ns.replace(' ', ' .* ?') + '(?: |S)');
}
/**
* 获得指定的 Handler
* @param {[type]} element [description]
* @param {[type]} event [description]
* @param {Function} fn [description]
* @param {[type]} selector [description]
* @return {[type]} [description]
*/
// findHandlers(el,event,fn)
//
// 内部方法,获得指定的 Handler
function findHandlers(element, event, fn, selector, scope) {
var evt = parse(event);
if (evt.ns) var matcher = matcherFor(event.ns);
return (handlers[eid(element)] || []).filter(function(handler) {
return handler &&
(!evt.e || handler.e == evt.e) &&
(!evt.ns || matcher.test(handler.ns)) &&
(!fn || handler.fn === fn) &&
(!selector || handler.sel == selector) &&
(!scope || handler.scope === scope);
});
}
/**
* 获得是否捕获事件状态,焦点事件一律捕获
* @param {[type]} handler [description]
* @param {[type]} captureSetting [description]
* @return {Boolean} [description]
*/
// isCapture(handler,capture)
//
// 内部方法,获得是否捕获事件状态,焦点事件一律捕获
function isCapture(handler, capture) {
return handler.del &&
(!focusinSupported && (handler.e in focusEvent)) || !!capture;
}
/**
* 将焦点事件统一为真实事件,但 firefox 因为不支持 focusinout 所以不会被转换
* @param {[type]} type [description]
* @return {[type]} [description]
*/
// eventCvt(type)
//
// 内部方法,将焦点事件统一为真实事件,但 firefox 因为不支持 focusinout 所以不会被转换
function eventCvt(type) {
return (focusinSupported && focusEvnet[type]) || type;
}
/**
* 复制原事件对象,并作为原事件对象的代理
* @param {[type]} event [description]
* @return {[type]} [description]
*/
// createProxy(event)
//
// 内部方法,复制原事件对象,并作为原事件对象的代理
function createProxy(event) {
var key, proxy = {
originalEvent: event
};
for (key in event)
if (event[key] !== undefined) proxy[key] = event[key];
return compatible(proxy, event);
}
S.Event.createProxy = createProxy;
/**
* 针对三个事件属性做兼容
* @param {[type]} event [description]
* @param {[type]} source [description]
* @return {[type]} [description]
*/
// compatible(event,source)
//
// 内部方法,针对三个事件属性做兼容
function compatible(event, source) {
if (source || !event.isDefaultPrevented) {
source || (source = event);
each(eventMethods, function(predicate,name) {
var sourceMethod = source[name];
event[name] = function() {
this[predicate] = returnBool(true);
return sourceMethod && sourceMethod.apply(source, arguments);
};
event[predicate] = returnBool(false);
});
event.halt = function(){
this.preventDefault();
this.stopPropagation();
};
if (source.defaultPrevented !== undefined ? source.defaultPrevented :
'returnValue' in source ? source.returnValue === false :
source.getPreventDefault && source.getPreventDefault())
event.isDefaultPrevented = returnBool(true);
}
return event;
}
/**
* 生成原生事件对象
* @param {[type]} type [description]
* @param {[type]} props [description]
* @return {[type]} [description]
*/
// createEvent(type,props)
//
// 内部方法,生成原生事件对象
function createEvent(type, props) {
var event = document.createEvent(specialEvents[type] || 'Events'),
bubbles = true;
if (props) {
for (var name in props) {
name == 'bubbles' ? (bubbles = !!props[name]) : (event[name] = props[name]);
}
}
event.initEvent(type, bubbles, true);
return compatible(event);
}
/**
* 添加事件绑定的主函数
* @param {[type]} element [description]
* @param {[type]} events [description]
* @param {Function} fn [description]
* @param {[type]} data [description]
* @param {[type]} selector [description]
* @param {[type]} delegator [description]
* @param {[type]} capture [description]
*/
// add(el,event,fn)
//
// 内部方法,添加事件绑定的主函数
function add(element, events, fn, selector, delegator, scope) {
var id = eid(element),
set = (handlers[id] || (handlers[id] = []));
if (events == 'ready') return S.ready(fn);
events.split(/\s/).map(function(event) {
var handler = parse(event);
handler.fn = fn;
handler.sel = selector;
handler.del = delegator;
handler.scope = scope;
var callback = delegator || fn;
handler.proxy = function(e) {
e = compatible(e);
if (e.isImmediatePropagationStopped()) return;
var result = callback.apply(scope || element, e._args == undefined ? [e] : [e].concat(e._args));
if (result === false) {
e.preventDefault();
e.stopPropagation();
}
return result;
};
handler.i = set.length;
set.push(handler);
element.addEventListener(eventCvt(handler.e), handler.proxy, isCapture(handler));
/* 自定义 DOM 事件处理,初始化*/
if(typeof event !== 'undefined' && event in S.Event.Special){
S.Event.Special[event].setup.apply(S.one(element,[handler.scope]));
}
});
}
/**
* 移除事件绑定的主函数
* @param {[type]} element [description]
* @param {[type]} events [description]
* @param {Function} fn [description]
* @param {[type]} selector [description]
* @param {[Object]} scope [description]
* @return {[type]} [description]
*/
// remove(el,event,fn)
//
// 内部方法,移除事件绑定的主函数
function remove(element, events, fn, selector, scope) {
var id = eid(element),
removeHandlers = function(set) {
set.map(function(handler){
delete handlers[id][handler.i];
element.removeEventListener(eventCvt(handler.e), handler.proxy, isCapture(handler));
/* 自定义 DOM 事件处理,销毁*/
if(typeof event !== 'undefined' && event in S.Event.Special){
S.Event.Special[event].teardown.apply(S.one(element));
}
});
};
if(events) {
events.split(/\s/).map(function(event) {
removeHandlers(findHandlers(element, event, fn, selector, scope));
});
}
else removeHandlers(handlers[id] || []);
}
/**
* 主要绑定函数,包括 delegate 的处理方法
* @param {[type]} event [description]
* @param {[type]} selector [description]
* @param {Function} callback [description]
* @param {[type]} scope [description]
* @return {[type]} [description]
*/
// **S.Node.on(event,selector,callback,[scope])**
//
// 事件绑定
//
// ```
// S.Event.on('click','div',function(e){...})
// ```
//
// 可以使用`els.on('click',callback)`
//
// **el.on(eventType,callback)**
//
// 在元素上进行事件绑定,el也可以是Node列表,比如
//
// ```
// S.one('div').on('click',function(){
// alert('ok');
// });
// ```
Node.on = function(event, selector, callback, scope) {
var delegator, _this = this;
/* selector 为空的情况,即非 delegator */
if (isFunction(selector)) {
scope = callback;
callback = selector;
selector = undefined;
}
/* 阻止默认事件,kissy 不支持此方式 */
if (callback === false) callback = returnFalse;
_this.each(function(element) {
/* delegate 处理逻辑 */
if (selector) delegator = function(e) {
var evt, match, matches = element.all(selector);
if(!matches || !matches.length) return;
match = matches.filter(function(el){
return (el == e.target) || ($(el).contains(e.target));
})[0];
if (match && match !== element[0]) {
evt = createProxy(e);
evt.currentTarget = match;
evt.liveFired = element[0];
return callback.apply(scope || match, [evt].concat(slice.call(arguments, 1)));
}
};
add(element[0], event, callback, selector, delegator, scope);
});
return _this;
};
/**
* 取消事件绑定的主函数
* @param {[type]} event [description]
* @param {[type]} selector [description]
* @param {Function} callback [description]
* @param {[type]} scope [description]
* @return {[type]} [description]
*/
// **S.Node.detach(event,selector,callback,[scope])**
//
// 取消事件绑定,推荐直接调用**els.detach('click',callback)**
//
// **el.detach(eventType,callback)**
//
// 取消元素事件,el也可以是Node列表。
Node.detach = function(event, selector, callback, scope) {
var _this = this;
if (isFunction(selector)) {
scope = callback;
callback = selector;
selector = undefined;
}
_this.each(function(element) {
remove(element[0], event, callback, selector, scope);
});
return _this;
};
/**
* delegate 主函数,只是 Node.on 的别名
* @param {[type]} event [description]
* @param {[type]} selector [description]
* @param {Function} callback [description]
* @param {[type]} scope [description]
* @return {[type]} [description]
*/
// **S.Node.delegate(event,selector,function(){...},[scope])**
//
// 事件委托,推荐直接调用**el.delegate('event',selector,callback,scop)**
//
// **el.delegate(eventType,callback,scope)**
//
// 针对当前节点执行事件委托,scope 为委托的节点或选择器
Node.delegate = function(event, selector, callback, scope) {
return this.on(event, selector, callback, scope);
};
/**
* undelegate 主函数,只是 Node.detach 的别名
* @param {[type]} event [description]
* @param {[type]} selector [description]
* @param {Function} callback [description]
* @param {[type]} scope [description]
* @return {[type]} [description]
*/
// **S.Node.undelegate(event,selector,function(){...},[scope])**
//
// 解除事件委托,是`Node.detach`的别名,推荐直接调用**el.undelegate()**
//
// **el.undelegate(eventType,selector,callback,scope)**
//
// 针对当前节点执行解除事件委托,scope 为委托的节点或选择器
Node.undelegate = function(event, selector, callback, scope) {
return this.detach(event, selector, callback, scope);
};
/**
* 执行符合匹配的 dom 节点的相应事件的事件处理器
* @param {String} events [description]
* @param {Object} props 模拟处理原生事件的一些信息
* @return {[type]} [description]
*/
// **S.Node.fire(event,props)**
//
// 执行符合匹配的 dom 节点的相应事件的事件处理器,推荐直接调用
//
// ```
// el.fire('click')
// ```
//
// **el.fire(eventType,props)**
//
// 触发节点元素的`eventType`事件,el.fire 函数继承自 `S.Node.fire(event,props)`
// - eventType: 事件类型
// - props:触发事件的时候传入的回传参数
//
// ```
// S.one('div').on('click',function(e){
// alert(e.a);
// });
// S.one('div').fire('click',{
// a:1
// });
// // => 弹出框,值为1
// ```
Node.fire = function(events, props) {
var _this = this;
events.split(/\s/).map(function(event){
event = createEvent(event, props);
_this.each(function(element) {
if ('dispatchEvent' in element[0]) element[0].dispatchEvent(event);
else element.fireHandler(events, props);
});
});
return _this;
};
/**
* 执行符合匹配的 dom 节点的相应事件的事件处理器,不会冒泡
* @param {[type]} event [description]
* @param {[type]} props [description]
* @return {[type]} [description]
*/
// **S.Node.fireHandler(event,props)**
//
// 执行符合匹配的 dom 节点的相应事件的事件处理器,不会冒泡
//
// 推荐直接执行
//
// ```
// el.fireHandler('click',{...})
// ```
//
// **el.fireHandler(eventType,props)**
//
// 以非冒泡形式触发回调,由`el.fire()`函数调用,在单纯希望执行事件绑定函数时使用此方法
Node.fireHandler = function(events, props) {
var e, result, _this = this;
events.split(/\s/).map(function(event){
_this.each(function(element) {
e = createEvent(event);
e.target = element[0];
if(e.target === null){
e = getCustomDOMEvent(e);
}
mix(e,props);
findHandlers(element[0], event).map(function(handler, i) {
result = handler.proxy(e);
if (e.isImmediatePropagationStopped()) return false;
});
});
});
return _this;
};
function getCustomDOMEvent(e){
var eProxy = {};
mix(eProxy,e);
eProxy.__proto__ = e.__proto__;
return eProxy;
}
S.Event || (S.Event = {});
/**
* 将普通对象混入 Event.Target 后,即能拥有简单的自定义事件特性。
* @type {Object}
*/
// **S.Event.Target**
//
// 简单自定义事件对象,将普通对象混入 `Event.Target` 后,即能拥有简单的自定义事件特性。
//
// 事件本身是一个抽象概念,和平台无关、和设备无关、更和浏览器无关,浏览器只是使用“事件”的方法来触发特定的行为,进而触发某段网页逻辑。而常见的DOM事件诸如click,dbclick是浏览器帮我们实现的“特定行为”。而这里的“特定行为”就是触发事件的时机,是可以被重新定义的,原理上,事件都是需要精确的定义的,比如下面这个例子,我们定义了一个新事件:“初始化1秒后”
//
// ```
// var EventFactory = function(){
// var that = this;
// setTimeout(function(){
// that.fire('afterOneSecond');
// },1000);
// };
// S.augment(EventFactory,S.Event.Target);
// var a = new EventFactory();
// a.on('afterOneSecond',function(){
// alert('1秒后');
// });
// // 1秒后弹框
// ```
//
// 这是一个很纯粹的自定义事件,它有事件名称`afterOneSecond`,有事件的触发条件`self.fire('afterOneSecond')`,有事件的绑定,`a.on('afterOneSecond')`。这样这个事件就能顺利的发生,并被成功监听。在代码组织层面,一般工厂类中实现了事件命名、定义和实现,属于内聚的功能实现。而绑定事件时可以是工厂类这段代码外的用户,他不会去关心事件的具体实现,只要关心工厂类"暴露了什么事件可以让我绑定"就可以了,这就是KISSY中使用自定义事件的用法。
//
S.Event.Target = {
/**
* 用于存放绑定的事件信息
* @type {Object}
*/
_L: {
/*
"click": [
{
E: "click touchstart",
F: fn1,
S: scope1
},
{
E: "click",
F: fn2,
S: scope2
}
]
*/
},
/**
* 绑定事件
* @param {String} eventType 必选,绑定的事件类型,以空格分隔
* @param {Function} fn 必选,触发事件后的回调方法
* @param {[type]} scope 回调方法的 this 指针
* @return {[type]} 返回对象本身
*/
on: function(eventType, fn, scope) {
var eventArr = s2a(eventType), T = this;
eventArr.map(function(ev){
var evt = ev in T._L ? T._L[ev] : (T._L[ev] = []);
evt.push({
E: eventType,
F: fn,
S: scope
});
});
return T;
},
/**
* 触发事件
* @param {String} eventType 必选,绑定的事件类型,以空格分隔
* @param {[type]} data 触发事件时传递给回调事件对象的信息,而 data 后面的参数会原封不动地传过去
* @return {[type]} 返回对象本身
*/
// on()
//
// Event.Target 的参元方法,绑定自定义事件
//
// fire(event,data)
//
// Event.Target 的参元方法,触发事件
fire: function(eventType, data) {
var eventArr = s2a(eventType), T = this;
eventArr.map(function(ev){
var evt = T._L[ev],
returnEv = S.mix(data || {}, {target: T, currentTarget: T});
if(!evt) return;
evt.map(function(group){
group.F.apply(group.S || T, [returnEv].concat([].slice.call(arguments, 2)));
});
});
return T;
},
/**
* 解除绑定事件
* @param {String} eventType 必选,绑定的事件类型,以空格分隔
* @param {Function} fn 如果需要指定解除某个回调,需要填写
* @param {[type]} scope 同上,可以进一步区分某个回调
* @return {[type]} 返回对象本身
*/
// detach(event,fn)
//
// Event.Target 的参元方法,解除绑定事件
detach: function(eventType, fn, scope) {
var eventArr = s2a(eventType), T = this;
eventArr.map(function(ev){
/* 如果遇到相同事件,优先取消最新绑定的 */
var evt = T._L[ev];
if(!evt) return;
if(!fn && (T._L[ev] = [])) return;
for(var key=0; key < evT._Length; key++) {
if(group.F == fn && group.S == scope) {
evt.split(key, 1);
continue;
}
else if(group.F == fn) {
evt.split(key, 1);
continue;
}
}
});
return T;
}
};
S.Event.Special = {
/*
'myEvent':{
setup:function(){
},
teardown:function(){
}
}
*/
};
/**
* 把 event 字符串格式化为数组
*/
// s2a(str)
//
// 内部方法,把 event 字符串格式化为数组
function s2a(str) {
return str.split(' ');
}
S.add('event',function(S){
return S.Event;
});
})(KISSY);
// ## IO 模块
//
// **IO的配置項说明:**
//
// timeout 值的單位為秒,與KISSY保持一致。
//
// contentType配置,若未配置值,且滿足以下條件
// 1. data不為空
// 2. type不為get
//
// 此時默認
//
// ```
// Content-Type=application/x-www-form-urlencoded
// ```
//
// **KISSY MINI 删除的 API**
//
//| API | KISSY | KISSY-MINI |
//| -------------------- |:--------------------:|:--------------------:|
//| setupConfig | YES | NO |
//| upload | YES | NO |
//| serialize | YES | NO |
//| getResponseHeader | YES | NO |
//| Promise API | YES | NO |
//| | | |
//
// 配置项:
//
//| Setting | KISSY | KISSY-MINI |
//| -------------------- |:--------------------:|:--------------------:|
//| cfg.crossDomain | YES | NO |
//| cfg.mimeType | YES | NO |
//| cfg.password | YES | NO |
//| cfg.username | YES | NO |
//| cfg.xdr | YES | NO |
//| cfg.xhrFields | YES | NO |
//| cfg.form | YES | NO |
//| | | |
//
//
// **KISSY VS KISSY-MINI,Ajax实现上的差异**
//
//| KISSY | KISSY-MINI | Note |
//|:-------------------- |:--------------------:|:--------------------:|
//| 回調函數的第二個參數支持更多的狀態 | 目前只支持 success/error/timeout/abort/parseerror | 更多的錯誤狀態可以通過getNativeXhr()得到原生的xhr對象來獲取。 |
//| jsonp返回多個數據源時,success回調得到的數據是一個包含所有數據源的數組 | 目前只取第一個數據源 | - |
//| IO的別名有S.Ajax/S.io/S.IO | 只有S.IO | - |
//| jsonpCallback支持函數返回全局函數名 | jsonpCallback只支持字符串 | - |
//| 對於url上的參數,會與data參數重新組合 | data附加在url上 | - |
//| cache增加的時間戳KISSY和KISSY MINI是不一致的 | - | - |
//| | | |
//
// 实例代码
//
// ```
// S.IO({
// type: 'get',
// url: 'http://www.taobao.com',
// data: {...},
// success: function(responseData,statusText,xhr){
// //...
// },
// dataType:'json' // 可取值为'json'/'jsonp'
// });
// ```
//
// 快捷调用方法
// - S.IO.get(url,fn)
// - S.IO.post(url,fn)
// - S.IO.jsonp(url,fn)
// - S.IO.getJSON(url,fn)
// - S.IO.getScript(url,fn) 等同于 S.getScript(url,fn)
// - S.IO.jsonp(url,fn) 等同于 S.jsonp(url,fn)
//
// 具体用法参照[KISSY1.4.0 Ajax文档](http://docs.kissyui.com/1.4/docs/html/guideline/io.html)
;(function(global, S) {
var doc = global.document,
location = global.location;
function mix(target, source) {
var k, key;
for (key in source) {
if((k = source[key]) !== undefined) {
target[key] = k;
}
}
return target;
}
function merge(d, n) {
return mix(mix({}, d), n);
}
function isType(type) {
return function(obj) {
return {}.toString.call(obj) == '[object ' + type + ']';
}
}
var isObject = isType('Object'),
isArray = Array.isArray || isType('Array'),
isFunction = isType('Function');
function each(obj, iterator, context) {
var keys = Object.keys(obj), i, len;
for (i = 0, len = keys.length; i < len; i++) {
if (iterator.call(context, obj[keys[i]], keys[i], obj) === false) {
return;
}
}
}
var jsonpID = 1,
TRUE = !0,
FALSE = !TRUE,
NULL = null,
ABORT = "abort",
SUCCESS = "success",
ERROR = "error",
EMPTY = "",
getScript = S.getScript,
noop = function() {};
var transports = {},
def = {
type: 'GET',
async: TRUE,
serializeArray: TRUE,
/* whether data will be serialized as String */
processData: TRUE,
/* contentType: "application/x-www-form-urlencoded; charset=UTF-8" */
/* Callback that is executed before request */
beforeSend: noop,
/* Callback that is executed if the request succeeds */
success: noop,
/* Callback that is executed the the server drops error */
error: noop,
/* Callback that is executed on request complete (both: error and success) */
complete: noop,
context: NULL,
/* MIME types mapping */
accepts: {
script: 'text/javascript,application/javascript',
json: "application/json,text/json",
xml: 'application/xml,text/xml',
html: "text/html",
text: 'text/plain'
},
/* Default timeout */
timeout: 0,
cache: TRUE
};
function presetConfig(cfg) {
if(!cfg.url) {
cfg.url = location.toString();
}
/* 序列化data參數 */
if (cfg.processData && isObject(cfg.data)) {
cfg.data = param(cfg.data, cfg.serializeArray)
}
cfg.type = cfg.type.toUpperCase();
if (cfg.data && cfg.type == 'GET') {
cfg.url = appendURL(cfg.url, cfg.data)
}
if (cfg.cache === FALSE) {
cfg.url = appendURL(cfg.url, 't=' + Date.now());
}
var testURL = /^([\w-]+:)?\/\/([^\/]+)/.test(cfg.url),
protocol = testURL ? RegExp.$1 : location.protocol;
cfg.local = protocol == 'file:';
/* KISSY默認的上下文是config而不是io實例*/
cfg.context || (cfg.context = cfg);
return cfg;
}
function fireEvent(type, io) {
IO.fire(type, {io: io});
}
/**
* IO異步請求對象
* @param config
* @returns IO instance
* @constructor
*/
function IO(config) {
var self = this;
if (!(self instanceof IO)) {
return new IO(config);
}
/* 所有的io類型都先進行數據預處理。 */
var cfg = presetConfig(merge(def, config)),
timeout = cfg.timeout;
self.cfg = cfg;
fireEvent('start', self);
/* 根據dataType獲取對應的transport對象。 */
/* 每個transport實現對應的send、abort方法。 */
var dataType = cfg.dataType,
Transport = transports[dataType] || transports[EMPTY];
var transport = new Transport(self);
self.transport = transport;
/* beforeSend回調可以阻止異步請求的發送。*/
var fnBeforeSend = cfg.beforeSend;
if(fnBeforeSend && fnBeforeSend.call(cfg.context, self, cfg) === false) {
self.abort();
return self;
}
fireEvent('send', self);
if(timeout > 0) {
self._timer = setTimeout(function() {
self.abort("timeout");
}, timeout * 1000);
}
try {
transport.send();
}catch(ex) {
self._complete(FALSE, ex.message);
}
return self;
}
mix(IO, S.Event.Target);
mix(IO.prototype, {
abort: function(s) {
this.transport.abort(s);
},
/* 一個IO請求,必然要調用success或者error方法中的一個。*/
/* 最終都需要調用complete回調方法,在這裡統一控制。*/
_complete: function(status, statusText) {
var self = this,
cfg = self.cfg,
context = cfg.context,
param = [self.responseData, statusText, self],
TYPE = status ? SUCCESS : ERROR,
COMPLETE = "complete";
/* IO對象不允許重複執行。*/
if(self._end) return;
self._end = TRUE;
clearTimeout(self._timer);
cfg[TYPE].apply(context, param);
fireEvent(TYPE, self);
cfg[COMPLETE].apply(context, param);
fireEvent(COMPLETE, self);
}
});
function setTransport(name, fn) {
transports[name] = fn;
}
function appendURL(url, query) {
return (url + '&' + query).replace(/[&?]{1,2}/, '?');
}
var encode = encodeURIComponent;
function param(o, arr) {
var rt = [];
_serialize(rt, o, arr);
return rt.join("&");
}
function _serialize(rt, o, arr, k) {
var symbol = arr === true ? encode("[]") : EMPTY;
each(o, function(val, key) {
if(k) {
key = k + symbol;
}
if(isArray(val)) {
_serialize(rt, val, arr, key);
}else{
rt.push(key + "=" + encode(val));
}
});
}
var XHRNAME = "XMLHttpRequest",
reBlank = /^\s*$/;
/* 標準的XMLHttpRequest對象 */
function createXHR() {
return new global[XHRNAME]();
}
/**
* 基於XMLHttpRequest對象的異步請求處理。
* @constructor
*/
function xhrTransport(io) {
this.io = io;
}
mix(xhrTransport.prototype, {
_init: function() {
var self = this,
io = self.io,
cfg = io.cfg,
dataType = cfg.dataType,
mime = cfg.accepts[dataType],
baseHeaders = {},
xhr = createXHR();
/* io.xhr = xhr; */
io.getNativeXhr = function() {
return xhr;
};
/* 依照大部分庫的做法。 */
if (!cfg.crossDomain) {
baseHeaders['X-Requested-With'] = XHRNAME;
}
if (mime) {
baseHeaders['Accept'] = mime;
if(xhr.overrideMimeType) {
if (mime.indexOf(',') > -1) {
mime = mime.split(',', 2)[0]
}
xhr.overrideMimeType(mime)
}
}
/* 附加Content-Type */
if (cfg.contentType || (cfg.data && cfg.type != 'GET')) {
baseHeaders['Content-Type'] = cfg.contentType ||
'application/x-www-form-urlencoded';
}
cfg.headers = merge(baseHeaders, cfg.headers || {})
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
var result, error = FALSE;
if ((xhr.status >= 200 &&
xhr.status < 300) ||
xhr.status == 304 ||
(xhr.status == 0 && cfg.local)) {
/* 若dataType未設置,則取得結果的時候根據mime信息推斷dataType值,並進行對應的數據處理。*/
dataType = dataType || mimeToDataType(xhr.getResponseHeader('Content-Type'));
/* 利用xhr對象來獲取數據。*/
result = io.responseText = xhr.responseText;
io.responseXML = xhr.responseXML;
try {
if (dataType == 'script') {
(1,eval)(result);
}else if(dataType == 'xml') {
result = xhr.responseXML;
}else if (dataType == 'json') {
result = reBlank.test(result) ? NULL : parseJSON(result);
}
} catch (e) { error = e }
io.responseData = result;
if (error) {
io._complete(FALSE, 'parsererror')
}else {
io._complete(TRUE, SUCCESS);
}
} else {
io._complete(FALSE, ERROR)
}
}
};
xhr.open(cfg.type, cfg.url, cfg.async);
each(cfg.headers, function(v, k) {
xhr.setRequestHeader(k, v);
});
xhr.send(cfg.data ? cfg.data : NULL);
},
abort: function(statusText) {
var self = this,
xhr = self.xhr,
io = self.io;
if(xhr) {
xhr.onreadystatechange = noop;
xhr.abort();
}
io._complete(FALSE, statusText || ABORT);
},
send: function() {
this._init();
}
});
setTransport(EMPTY, xhrTransport);
var regMimeType = /^(?:text|application)\/(json|javascript|xml|html)/i;
function mimeToDataType(mime) {
var result = mime && regMimeType.test(mime),
type = result ? RegExp.$1 : "text";
return type === "javascript" ? "script" : type;
/*
return mime && ( mime == htmlType ? 'html' :
reJsonType.test(mime) ? 'json' :
reScriptType.test(mime) ? 'script' :
reXMLType.test(mime) && 'xml' ) || 'text';
*/
}
function parseJSON(text) {
return JSON.parse(text);
}
/**
* 基於script節點的異步請求處理,主要針對jsonp的場景
* @param io io實例
* @constructor
*/
function ScriptTransport(io) {
this.io = io;
}
mix(ScriptTransport.prototype, {
abort: function(statusText) {
this._end(FALSE, statusText || ABORT)
},
/**
* 完成請求以後的清理工作。
* @param status
* @param statusText
* @private
*/
_end: function(status, statusText) {
var self = this,
script = self.script,
io = self.io,
gvar = self._globalVar;
/* 不直接刪除,避免有請求返回以後調用導致的報錯。 */
global[gvar] = function() {
delete global[gvar];
};
if(script) {
script.src = NULL;
script.onload = script.onerror = noop;
script.parentNode.removeChild(script);
}
/* 調用io實例的方法,完成io請求狀態 */
io._complete(status, statusText);
},
send: function() {
var self = this,
io = self.io,
cfg = io.cfg,
callbackName = cfg.jsonp || "callback",
methodName = cfg.jsonpCallback || "jsonp"+jsonpID ++;
/* methodName = (S.isFunction(methodName) ? methodName() : methodName) ||
"jsonp"+jsonpID ++; */
self._globalVar = methodName;
/* 添加jsonp的callback參數。 */
var url = appendURL(cfg.url, callbackName + "=" + methodName);
global[methodName] = function(data){
/*
r = data;
*/
/* 如果是多個數據的情況下,返回的數據是數組。*/
/* 跟kissy保持一致。 */
/*
if(arguments.length >1) {
r = makeArray(arguments);
}
*/
io.responseData = data;
self._end(TRUE, SUCCESS);
};
/* KISSY.getScript方法支持傳入指定的script節點元素。*/
self.script = getScript(url, {
charset: cfg.scriptCharset,
error: function() {
self._end(FALSE, ERROR);
}
});
}
});
setTransport("jsonp", ScriptTransport);
function factory(t, dt) {
return function(url, data, callback, dataType, type) {
/* data 参数可省略 */
if (isFunction(data)) {
dataType = callback;
callback = data;
data = NULL;
}
return IO({
type: t || type,
url: url,
data: data,
success: callback,
dataType: dt || dataType
});
};
}
/* 定義快捷方法 */
// Ajax API
//
// **S.IO.get(url,callback)**
//
// **S.IO.post(url,callback)**
//
// **S.IO.jsonp(url,callback)**
//
// **S.IO.getJSON(url,callback)**
//
// **S.IO.getScript(url,callback)** 同 **S.getScript(url,callback)**
mix(IO, {
get: factory("get"),
post: factory("post"),
jsonp: factory(NULL, "jsonp"),
getJSON: factory(NULL, "json"),
getScript: getScript
});
// **S.IO.jsonp(url,callback)** 同 **S.jsonp()**
//
// **S.getScript (url , config)**
//
// 动态加载目标地址的资源文件,第二个参数可以是配置对象,也可以是回调函数
//
// 如果是配置对象,参数可以是:
// - charset:编码类型
// - success:成功的回调函数
// - error:失败的回调函数
//
// ```
// S.getScript(url , { success : success , charset : charset });
// S.getScript(url, function(){...});
//
// ```
mix(S, {
IO: IO,
jsonp: IO.jsonp
});
/* KMD封裝 */
S.add('io', function() {
return IO;
});
})(this, KISSY);
//