(function(){
/**
* 拷贝数组,不拷贝undefined元素
*/
function cloneArray(source) {
var prop = null,
i = 0,
len = source.length,
target = [];
for(; i < len; i++) {
prop = source[i];
if(typeof prop === "undefined") {
continue;
}
if(YAHOO.lang.isObject(prop)) {
target[target.length] = clone(prop);
} else if(YAHOO.lang.isArray(prop)) {
target[target.length] = cloneArray(prop);
} else {
target[target.length] = source[i];
}
}
return target;
}
/**
* 复制对象的内容
* @returns {Object} 返回新对象
*/
function clone(source) {
var prop = null,
target = {},
arr = [];
if(YAHOO.lang.isArray(source)) {
return cloneArray(source);
}
for(var i in source) {
if(source.hasOwnProperty(i)) {
prop = source[i];
if(typeof prop === "undefined") {
continue;
}
if(YAHOO.lang.isObject(prop)) {
target[i] = clone(prop);
} else if(YAHOO.lang.isArray(prop)) {
target[i] = cloneArray(prop);
} else {
target[i] = source[i];
}
}
}
return target;
}
function isEmpty(obj) {
return (L.isNull(obj) || L.isUndefined(obj));
}
/**
* 前端MVC的数据层,通过对象路径操作对象。
* 这个组件一般用于处理复杂的表单,将表单项与后台对象的路径一一对应起来,直接将前台的表单数据转化为对象。
* @author cwb0525@gmail.com
* @example
* var json = {a: 1, b: 'test', c: [1, 3]};
* var store = new PathStore(json)
* store.put("/a", 2);
* store.put("/c/0", 33);
* console.log("%o", store);
*/
var PathStore = function(root) {
if(root) {
this.root = root;
}
};
PathStore.prototype = {
root: {},
initialize: function(root){
if(root) {
this.root = root;
}
},
/**
* @param {String} path /a/b/c
* @param {Object} value
*/
insert: function(path, value, force){
var parts = path.split("/"),
space = parts.shift(),
i = 0,
part = null,
len = parts.length,
curr = this.root,
maxIndex = len - 1,
idx = 0;
if(force) {
this.put(path, value);
}
do{
part = parts[i];
if(isEmpty(curr)) {
throw "不存在的路径:" + path;
}
//对应的情况:/a/b/c/
if(maxIndex === i && part.length === 0) {
if(!YAHOO.lang.isArray(curr)) {
throw path + "不是一个数组";
}
idx = curr.length;
curr[idx] = value;
return idx;
}
if(maxIndex === i && part.length > 0) {
curr[part] = value;
}
i += 1;
curr = curr[part];
} while(i < len);
},
/**
* 强制更新,路径不存在则创建
*/
put: function(path, value) {
console.log("put --- " + path + ":" + value);
var parts = path.split("/"),
space = parts.shift(),
i = 0,
part = null,
len = parts.length,
curr = this.root,
parent = null,
idx = 0,
regNum = /^[1-9]\d*|0$/;
for(var i = 0; i < len - 1; i++) {
part = parts[i];
if(isEmpty(curr[part])) {
//下一个键值名一个整数
if(regNum.test(parts[i + 1])) {
curr[part] = [];
} else {
curr[part] = {};
}
}
curr = curr[part];
}
part = parts[i];
if(L.isArray(curr)) {
idx = parseInt(part);
if(idx !== NaN) {
curr[idx] = value;
} else {
throw "路径无效:" + path;
}
} else {
curr[part] = value;
}
},
update: function(path, value){
var parts = path.split("/"),
space = parts.shift(),
i = 0,
part = null,
len = parts.length,
curr = this.root,
maxIndex = len - 1,
parent = null,
idx = 0;
do{
part = parts[i];
//对应的情况:/a/b/c/
if(maxIndex === i && part.length === 0) {
if(!YAHOO.lang.isArray(parent)) {
console.log(path + "不是一个数组");
return false;
}
idx = parseInt(part);
if(idx === NaN) {
console.log("非法的数组路径:" + path);
return false;
}
parent[idx] = value;
}
if(maxIndex === i && part.length > 0) {
curr[part] = value;
return true;
}
i += 1;
parent = curr;
curr = curr[part];
if(isEmpty(curr)) {
console.log("不存在的路径:" + path);
return false;
}
} while(i < len);
},
remove: function(path) {
var parts = path.split("/"),
space = parts.shift(),
i = 0,
part = null,
len = parts.length,
curr = this.root,
maxIndex = len - 1,
parent = null;
do {
part = parts[i];
//对应的情况:/a/b/c/
if(maxIndex === i && part.length === 0) {
if(!YAHOO.lang.isArray(curr)) {
console.log("尝试删除非法路径:" + path);
return false;
}
while(curr.length) {
curr.pop();
}
break;
}
if(maxIndex === i && part.length > 0) {
if(YAHOO.lang.isArray(curr)) {
part = parseInt(part);
}
if(typeof curr[part] === "undefined") {
console.log("尝试删除不存在的路径:" + path);
return false;
}
delete curr[part];
return true;
}
i += 1;
curr = curr[part];
parent = curr;
if(isEmpty(curr)) {
console.log("尝试删除不存在的路径:" + path);
return false;
}
} while(i < len);
},
get: function(path) {
var parts = path.split("/"),
space = parts.shift(),
i = 0,
part = null,
len = parts.length,
curr = this.root,
maxIndex = len - 1;
do {
part = parts[i];
//对应的情况:/a/b/c/
if(maxIndex === i && part.length === 0) {
if(!YAHOO.lang.isArray(curr)) {
return null;
}
return curr;
}
if(isEmpty(curr) || isEmpty(curr[part])) {
console.log("get --- 不存在的路径: " + path);
return null;
}
if(maxIndex === i && part.length > 0) {
if(YAHOO.lang.isArray(curr)) {
part = parseInt(part);
}
return curr[part];
}
i += 1;
curr = curr[part];
} while(i < len);
return null;
},
/**
* @param {Object} handlers 特殊域的处理函数集合
*/
updateFromForm: function(form, handlers) {
var elems = form.elements || [],
value = null,
handler = null,
names = [],
name = "",
oldValue = null,
handlers = handlers || {};
for(var i = 0, len = elems.length; i < len; i++) {
elem = elems[i];
name = elem.getAttribute("name");
if(name && names.indexOf(name) < 0) {
names.push(name);
}
}
for(var i = 0, len = names.length; i < len; i++) {
name = names[i];
path = name;
if(!path || path.charAt(0) !== "/") {
continue;
}
//兼容IE6/7
if(Y.env.ua.ie <= 7) {
elem = D.query("[name='" + name + "']", form);
if(elem.length === 1) {
elem = elem[0];
}
} else {
elem = form[name];
}
//要求被忽略的元素
if(D.getAncestorByClassName(elem, "store-ignored")) continue;
handler = handlers[path];
//有些表单元素需要经过特殊的处理,才能获取到正确的值
if(YAHOO.lang.isFunction(handler)) {
value = handler.call(this, elem);
this.put(path, value);
} else if(!elem.tagName && elem.length){ //判断NodeList,处理多个radio同名的情况
var elt = null;
var checked = false;
var isCheckOrRadio = false;
for(var j = 0, jlen = elem.length; j < jlen; j++) {
elt = elem[j];
if(elt.type == "radio" || elt.type == "checkbox") {
if(elt.checked) {
value = elt.value;
this.put(path, value);
checked = true;
}
isCheckOrRadio = true;
}
if(elt.type === "text" || elt.type === "hidden"){//页面上也有可能多个input重复
if(!D.getAncestorByClassName(elt, "store-ignored")) {//未必忽略
value = elt.value;
this.put(path, value);
}
}
}
if(isCheckOrRadio && checked === false) {
this.remove(path);
}
} else if(elem.type == "radio" || elem.type == "checkbox") {//处理checkbox和radio
if(elem.checked) {
value = elem.value;
this.put(path, value);
} else {
this.remove(path);
}
} else { //处理input和select
value = elem.value;
this.put(path, value);
}
}
},
toString: function(){
var data = this.cloneRoot();
return JSON.stringify(data);
},
getRoot: function() {
return this.root;
},
cloneRoot: function() {
return clone(this.root);
},
clone: function() {
return new PathStore(this.cloneRoot());
}
};
PathStore.cloneObject = clone;
window.PathStore = PathStore;
})()
适应于前端表单元素和后端JAVA对象成员一一对应的情景,一般应用于复杂表单数据的传输。