*
*【结构简叙】(function(){ ……框架主体……; window.H=window.Heng=H;//向外部引用基础类 })(); H(sel).Method()或Heng.(sel).Method();//外部实例化
*内部已做了Heng基础类实例化处理new Heng(sel);实际使用时H(sel)即为创建对象,若sel为函数内嵌代码在形成完整DOM树时立即执行。
H(fn)实现了jQuery库中的$(fn)===$(document).ready(fn)形成完整DOM树即可执行。
var H=function (sel){ if(typeof sel=="function"){ H.ready(sel) } else{ return new Heng(sel); } }; H(function(){ H(sel).Method();});//DOM树加载完毕H(fn)等价于jQuery库中的$(fn)===$(document).ready(fn)*构造函数中实现“css选择器”获取DOM节点的功能并将节点存储到nodes属性上,遵循“公共数据属性化”的规则。
function Heng(sel) { //【原选择器理】:选择器拆分数组,从document开始上级get后代作为下级祖先 this.sel = sel; this.nodes = []; //选择器匹配的节点数组 if (typeof sel == 'string') { if (sel.indexOf(' ') != -1) { var nodes = sel.split(' '); var childElements = []; var node = []; //实时祖先节点数组 for (var i = 0; i < nodes.length; i++) { if (node.length == 0) node.push(document); switch (nodes[i].charAt(0)) { case '#': childElements = []; //清除上一组值再更新 childElements.push(this.getId(nodes[i].substring(1))); node = childElements; break; case '.': childElements = []; for (var j = 0; j < node.length; j++) { var temps = this.getClass(nodes[i].substring(1), node[j]); for (var k = 0; k < temps.length; k++) { childElements.push(temps[k]); } } node = childElements; break; default: childElements = []; for (var j = 0; j < node.length; j++) { var temps = this.getTagName(nodes[i], node[j]); for (var k = 0; k < temps.length; k++) { childElements.push(temps[k]); } } node = childElements; } } this.nodes = childElements; } else { //sel为无空格选择器字符串 switch (sel.charAt(0)) { case '#': this.nodes.push(this.getId(sel.substring(1))); break; case '.': this.nodes = this.getClass(sel.substring(1)); break; default: this.nodes = this.getTagName(sel); } } } else if (typeof sel == 'object') { //sel为dom节点 if (sel != undefined) { this.nodes[0] = sel; } } }*静态方法不需实例化,用于框架内部进行数据逻辑和DOM预处理或其它全局非DOM操作;实例方法结合DOM节点完成需求,如果sel匹配组件入口为一个集合,则所有节点都可以实现方法的逻辑与功能。
//静态方法 H.Method() = function (arg) { …code… }; //实例方法 Heng.prototype.Method = function (opts) { …… var nodes = this.nodes[i]; //sel匹配的的节点数组 for (var i = 0; i < this.nodes.length; i++) {//内部循环 var oDiv = this.nodes[i]; var aUl = oDiv.getElementsByTagName('ul'); …… } }*面向对象编程不可避免会出现this指向混乱的情况,所有类似问题通过闭包在函数外部声明var This=this;修正this指向。
Heng.prototype.slide = function (opts) { …… var This = this; node = function (opts) { //node为dom节点 //原型上的方法getClass只能通过指向实例的this去引用,此时的this为node节点 This.getClass(classString, parrent); /*this.getClass(classString,parrent)错误*/ } }*每一个实例方法都可以通过修改参数对象otps实现不同的需求,未指定的数据会使用默认值。
//模态框 Heng.prototype.dialog = function (opts) { //opts={"animate":是否开启动画,"enterDir":进入方向,"maskopa":遮罩透明度,"warncount":警告闪烁次数,"content":弹框内容} var def = { "animate" : false, "enterDir" : "top", "maskbg" : "#000000", "maskopa" : 0.5, "warncount" : 5, "content" : "<h1>Hello World</h1>" };//def为默认值 opts = H.extend(def, opts);//数据合并 …… }
*
*【方法案例】
H("form").formCheck();一行代码完成页面所有表单验证,实现功能如下:
*对H("form")匹配到的所有表单内部任意类型的表单元素都会进行校验。
*每个表单元素在失去焦点时会各自进行独立验证。
*表单提交时内部所有元素进行统一验证,分为“逐一校验(遇到验证未通过的元素则中断后续检查)”和“一次校验(一次性检查所有元素)”两种模式,通过配置opts["submitCheck"]=true/false切换两种模式。
*无论验证通过与否,都会有相应验证类型成功或失败的提示信息,可以配置opts参数自定义提示的内容和样式信息。
*内置常用数据类型的验证,亦可通过opts["customType"]、opts["customReg"]扩展验证类型,极大的提高了表单验证的灵活性。
Heng.prototype.formCheck = function (opts) { //opts={"customType":"无formCheck的class值","customReg":"必须的自定义类型的正则文本","customTip":"必须的自定义类型的错误提示"} //自定义校验类型用"formCheck-"+opts["customType"]作为class值 var def = { "user" : "*请输入3-16位字母数字", "password" : "*请输入5-17位以字母开头的字符数字组合", "email" : "*请输入正确邮箱地址", "Mobilephone" : "*请输入正确手机号", "radioBox" : "请选择", "ch" : "请输入中文字符", "wrongStyle" : "font-size:12px; color:#F00;", "passContent" : "成功", "passStyle" : "font-size:12px; color:#0C0;", "submitCheck" : false //提交时逐条验证还是一次验证显示所有错误信息。默认一次校验 }; opts = H.extend(def, opts); //dataType绑定到具体表单元素class值 var dataType = ["formCheck-user", "formCheck-password", "formCheck-email", "formCheck-mobilePhone", "formCheck-ch", "formCheck-radioBox"]; var Reg = { "user" : "^[a-zA-Z0-9_]{3,16}$", "password" : "^[a-zA-Z]+\w{5,17}$", "email" : "^([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$", "mobilePhone" : "^(13+\d{9})|(159+\d{8})|(153+\d{8})$", "ch" : "[\u4e00-\u9fa5]" }; if (opts["customType"]) { Reg[opts["customType"]] = opts["customReg"]; opts[opts["customType"]] = opts["customTip"]; //需要放在formCheck-radioBox之前 dataType.splice((dataType.length - 2), 1, ("formCheck-" + opts["customType"])); } var This = this; for (var i = 0; i < this.nodes.length; i++) { var form = this.nodes[i]; form.nodes = []; //dataType匹配的表单元素集合的超集,保存在每个form下 for (var j = 0; j < dataType.length; j++) { //表单元素超集_二维数组; var resultArr = this.getClass(dataType[j], form); if (resultArr.length != 0) { resultArr.dataClass = dataType[j]; //将对应class值绑定在子数组上 form.nodes.push(resultArr); } } for (var k = 0; k < form.nodes.length; k++) { //绑定blur事件 (function () { if (form.nodes[k].dataClass != "formCheck-radioBox") { var regoptsKEY = form.nodes[k].dataClass.slice(form.nodes[k].dataClass.indexOf("-") + 1); var regTest = new RegExp(Reg[regoptsKEY]); for (var l = 0; l < form.nodes[k].length; l++) { form.nodes[k][l].onblur = function () { var wrongSpan = This.getClass("formCheck-wrong", this.parentNode)[0]; if (!regTest.test(this.value)) { wrongSpan.innerHTML = opts[regoptsKEY]; wrongSpan.style.cssText = def["wrongStyle"]; } else { wrongSpan.innerHTML = def["passContent"]; wrongSpan.style.cssText = def["passStyle"]; } } } } else { if (form.nodes[k].dataClass == "formCheck-radioBox") { for (var m = 0; m < form.nodes[k].length; m++) { var RBA = form.nodes[k][m]; (function (RBA) { //每组radio或checkBox的input集合,form.nodes[k][m].radioBoxArr三维数组 form.nodes[k][m].radioBoxArr = form.nodes[k][m].parentNode.getElementsByTagName("input"); for (var n = 0; n < form.nodes[k][m].radioBoxArr.length; n++) { if (form.nodes[k][m].radioBoxArr[n].checked) { var statePre = true; break; } else { var statePre = false; } form.nodes[k][m].state = statePre; form.nodes[k][m].radioBoxArr[n].onclick = function () { for (var n = 0; n < RBA.radioBoxArr.length; n++) { if (RBA.radioBoxArr[n].checked) { var statePre = true; break; } else { var statePre = false; } } RBA.state = statePre; var wrongSpan = This.getClass("formCheck-wrong", this.parentNode)[0]; if (RBA.state) { wrongSpan.innerHTML = opts["passContent"]; wrongSpan.style.cssText = opts["passStyle"]; } else { wrongSpan.innerHTML = opts["radioBox"]; wrongSpan.style.cssText = opts["wrongStyle"]; } } } })(RBA) } } } })() } (function (form) { form.onsubmit = function (e) { var e = e || window.event; for (var k = 0; k < form.nodes.length; k++) { var regoptsKEY = form.nodes[k].dataClass.slice(form.nodes[k].dataClass.indexOf("-") + 1); var regTest = new RegExp(Reg[regoptsKEY]); for (var l = 0; l < form.nodes[k].length; l++) { if (def["submitCheck"]) { if (form.nodes[k].dataClass != "formCheck-radioBox") { var wrongSpan = This.getClass("formCheck-wrong", form.nodes[k][l].parentNode)[0]; if (!regTest.test(form.nodes[k][l].value)) { wrongSpan.innerHTML = opts[regoptsKEY]; wrongSpan.style.cssText = def["wrongStyle"]; return false; //停止执行中断循环,阻止默认 } else { wrongSpan.innerHTML = def["passContent"]; wrongSpan.style.cssText = def["passStyle"]; } } else if (form.nodes[k].dataClass == "formCheck-radioBox") { var wrongSpan = This.getClass("formCheck-wrong", form.nodes[k][l].parentNode)[0]; if (form.nodes[k][l].state) { wrongSpan.innerHTML = opts["passContent"]; wrongSpan.style.cssText = opts["passStyle"]; } else { wrongSpan.innerHTML = opts["radioBox"]; wrongSpan.style.cssText = opts["wrongStyle"]; return false; } } } else { if (form.nodes[k].dataClass != "formCheck-radioBox") { var wrongSpan = This.getClass("formCheck-wrong", form.nodes[k][l].parentNode)[0]; if (!regTest.test(form.nodes[k][l].value)) { wrongSpan.innerHTML = opts[regoptsKEY]; wrongSpan.style.cssText = opts["wrongStyle"]; e.preventDefault(); e.returnValue = false; } else { wrongSpan.innerHTML = opts["passContent"]; wrongSpan.style.cssText = opts["passStyle"]; } } else if (form.nodes[k].dataClass == "formCheck-radioBox") { var wrongSpan = This.getClass("formCheck-wrong", form.nodes[k][l].parentNode)[0]; if (form.nodes[k][l].state) { wrongSpan.innerHTML = opts["passContent"]; wrongSpan.style.cssText = opts["passStyle"]; } else { wrongSpan.innerHTML = opts["radioBox"]; wrongSpan.style.cssText = opts["wrongStyle"]; e.preventDefault(); e.returnValue = false; } } } } } } })(form) } }
H("div").on ("Tap",fn/{"Tap":fn,"Hold":fn});自定义事件处理程序实现触控手势操作:
*触控手势不属于系统事件,需一套在数据层管理事件及函数添加、销毁、遍历执行的机制,模拟系统原生add/removeEventListener方法的功能。
*触控手势事件的触发依赖系统touchstart/touchmove/touchend,触点ID号identifier跟踪同一手指。通过e.data报告手势状态。
*Tap:屏幕停留时间小于250ms,因不可避免手指抖动、力度不均影响触点状态,手指离屏时坐标偏移需小于阀值(水平:10px,垂直:5px)。
*Pinch:手指移动中水平或垂直方向坐标相对进屏时偏移大于10px时可触发,两根手指水平垂直偏移量4个值中的最大者作为缩放系数e.data.k。
*Hold:手指在屏幕停留时间大于500ms,如果提前离开或者手指偏移量大于阀值(水平:10px,垂直:5px)则停止定时器不触发。
*swipe:手指需在1000ms内连续(中途不反向)移动至少30px,对比进出屏触点坐标水平垂直偏移量,取较大者来确定e.data报告滑动方向和距离。
/*自定义事件处理程序*/ H.addEvent = function (type, handler) { this.handlers = {}; //{type1:[fn],type2:[fn]} if (typeof this.handlers[type] == "undefined") { this.handlers[type] = []; } this.handlers[type].push(handler); }; H.fireEvent = function (type, data) {//调用对应类型函数触发事件 if (this.handlers[type]instanceof Array) { var arrayEvent = this.handlers[type]; for (var i = 0, len = arrayEvent.length; i < len; i++) { if (typeof arrayEvent[i] === "function") { arrayEvent[i](data); } } } }; H.removeEvent = function (type, handler) { if (this.handlers[type]instanceof Array) { var arrayEvent = this.handlers[type]; for (var i = 0, len = arrayEvent.length; i < len; i++) { if (arrayEvent[i] === handler) { arrayEvent.splice(i, 1); break; } } } } /*触控手势实现和事件绑定*/ Heng.prototype.on = function (handle, fn) { //调用方式(type,fn) 或 ({type1:fn1,type2:fn2}) for (var i = 0; i < this.nodes.length; i++) { var node = this.nodes[i]; var iStouch = false; var hasTap = false, hasPinch = false, hasHold = false, hasSwipeLeft = false, hasSwipeRight = false; left = true, right = true, up = true, down = true; var inTime, outTime, touchID, touchID1, inX, inY, inX1, inY1, outX, outY, moveX, moveY, moveX1, moveY1, disX, disY, disX1, disY1, t; if (arguments.length == 1) { for (var k in handle) { if ((k === "Tap") || (k === "Pinch") || (k === "Hold") || (k === "Swipe")) { iStouch = true; //触控和鼠标事件不和共用 this.iStouch = iStouch; checkType(k); } } } else { iStouch = (handle === "Tap") || (handle === "Pinch") || (handle === "Hold") || (handle === "Swipe"); this.iStouch = iStouch; checkType(handle); } if (iStouch) { H.bind(node, "touchstart", tsFn); H.bind(node, "touchend", teFn); if (!hasTap) { H.bind(node, "touchmove", tmFn); } if (arguments.length == 1) { for (var j in handle) { H.addEvent(j, handle[j]); } } else { H.addEvent(handle, fn); } } else { if (arguments.length == 1) { for (var j in handle) { H.bind(node, j, handle[j]); } } else { H.bind(node, handle, fn) } } } function checkType(x) { switch (x) { case "Tap": hasTap = true; break; case "Pinch": hasPinch = true; break; case "Hold": hasHold = true; break; case "swipeLeft": hasSwipe = true; break; } } function tsFn(e) { touchID = e.changedTouches[0].identifier; inTime = new Date().getTime(); inX = e.changedTouches[0].clientX; inY = e.changedTouches[0].clientY; if (e.changedTouches[1]) { touchID1 = e.changedTouches[1].identifier; inX1 = e.changedTouches[1].clientX; inY1 = e.changedTouches[1].clientY; } if (hasHold) { if (e.targetTouches.length === 1 && e.changedTouches[0].identifier === touchID) { t = window.setTimeout(function () { H.fireEvent("Hold", e); }, 500) } } } function tmFn(e) { if (hasHold) { if ([Math.abs(moveY - inY) >= 5] && [Math.abs(moveX - inX) >= 10]) { window.clearTimeout(t); } } else if (hasPinch && e.targetTouches.length === 2 && e.changedTouches[1].identifier === touchID1 && e.changedTouches[0].identifier === touchID) { e.preventDefault(); disX = Math.abs(inX1 - inX); disY = Math.abs(inY1 - inY); disX1 = Math.abs(moveX1 - moveX); disY1 = Math.abs(moveY1 - moveY); if ((Math.abs(disX - disX1) >= 10) || (Math.abs(disY - disY1) >= 10)) { e.data.k = (Math.abs(disX - disX1) > Math.abs(disY - disY1)) ? (disX1 / disX) : (disY1 / disY); //缩放因子 H.fireEvent("Pinch", e); } } else if (hasSwipe && e.targetTouches.length === 2) { if (e.changedTouches[0].clientX >= moveX) { left = true } //对比相邻两次的移动判断是否中途反向 else { right = false } if (e.changedTouches[0].clientY >= moveY) { up = true } else { down = false } e.preventDefault(); } moveX = e.changedTouches[0].clientX; moveY = e.changedTouches[0].clientY; if (e.changedTouches[1]) { moveX1 = e.changedTouches[1].clientX; moveY1 = e.changedTouches[1].clientY; } } function teFn(e) { outTime = new Date().getTime(); outX = e.changedTouches[0].clientX; outY = e.changedTouches[0].clientY; if (hasTap && e.targetTouches.length === 1 && e.changedTouches[0].identifier === touchID) { if ([(outTime - inTime) <= 250] && [Math.abs(outY - inY) <= 5] && [Math.abs(outX - inX) <= 10]) { H.fireEvent("Tap", e); } } else if (hasHold && (outTime - inTime) <= 500) { window.clearTimeout(t); } else if (hasSwipe && e.targetTouches.length === 1 && e.changedTouches[0].identifier === touchID) { if (Math.abs(outX - inX) >= Math.abs(outY - inY)) { e.data.dis = Math.abs(outX - inX); if (outX >= inX) { e.data.direction = "right" } else { e.data.direction = "left" } } else { e.data.dis = Math.abs(outY - inY); if (outY >= inY) { e.data.direction = "down" } else { e.data.direction = "up" } } if ((outTime - inTime) <= 1000 && (Math.abs(outY - inY) >= 30 || Math.abs(outX - inX) >= 30)) { if ((left && right) || (up && down)) { //保证中途连续不反向 H.fireEvent("swipe", e); } } } } return this; };
H("ul").tree ()无极树形渲染,数据源分析:
*扁平json:[ {"id":节点描述,"text":节点文本},{节点},{节点}]。一维数据无法展示嵌套关系,只需一次循环即可遍历到所有数据,但是必须以id或其它属性描述不同数据间的结构和嵌套关系才能还原html视图。
*标准json:[ {"text":节点文本,"children":[{子节点},{子节点}]},{节点} ],不固定的无限层级嵌套子节点。数据嵌套关系直观表达了即将生成的html结构,传统循环无法解决数据层级不固定的遍历需求,必须通过函数递归将当前数据作为下一级子数据遍历的入口,才能层层深入查找多维数据。
function render(jsonData, parentNode) { for (var j = 0; j < jsonData.length; j++) { var oli = document.createElement("li"); oli.innerHTML = jsonData[j]["text"]; parentNode.appendChild(oli); if (jsonData[j]["children"]) { var oUl = document.createElement("ul"); oli.appendChild(oUl); render(jsonData[j]["children"], oUl);//将当前数据作为下一级子数据遍历的入口 } } }
* Author: wang-heng
* Date: 2015-07-16
* Copyright @ wh412057730原创
*/
点击这里查看框架源代码