自从本日起,开始写写关于 Edk 的文章。要开始理顺思路咯~彻彻底底弄好 Edk!
说明:
一、尽管框架这玩意不好做,用现成的库方便,但在编写框架时,还是可以学到许多一般应用无法学到的知识。
二、本代码可以免费使用、修改,希望我的程序能为您的工作带来方便,同时请保留这份请息。
三、在其他优秀库的面前,edk.js算很简单的东西,其实基本上都谈不上什么原创之类的。
/** * 检查对象的类型。支持 null/undefined/String/Array/Nubmer/Boolean/Date/RegExp。 * 推荐 Winter 文章:http://www.cnblogs.com/winter-cn/archive/2009/12/07/1618281.html * @param {Any} foo 要被检查类型的对象。 * @return {Funciton} */ $.type = function(foo){ if (foo === null) { return 'null'; }else if(typeof foo == 'undefined'){ return 'undefined'; } // constructor 判断比 instanceof 更精确,因为 instanceof 对父类亦有效。 switch (foo.constructor){ case Boolean : return Boolean; case Number : return Number; case String : return String; case Array : return Array; case Date : return Date; case RegExp : return RegExp; } if(foo instanceof Boolean){ return Number; }else if(foo instanceof Number){ return Number; }else if(foo instanceof String){ return String; }else if(foo instanceof Array){ return Array; }else if(foo instanceof Date){ return Date; }else if(foo instanceof RegExp){ return RegExp; } switch (typeof foo){ case 'undefined' : case 'unknown' : case 'function' : case 'regexp' : return 'null'; case 'boolean' : case 'number' : return Number; case 'date' : return Date; case 'string' : return String; case 'object' : return Object; case 'array' : return Array; } return null; // 什么类型都判断不了…… }
/** * * @param {Mixed} v * @return {Mixed} */ $.getPrimitives = function (v){ switch(v){ case 'null': return null; case 'true': return true; case 'false': return false; case String(Number(v)): return Number(v); case (new Date(v)).toString(): // v is a date but in Stirng Type return new Date(v); } return v; // 未转换。 }
封装一下客户端信息:
/** * 返回请求的路径(即当前页面的路径)。 * @return {String} url */ function getPath(){ return Request.ServerVariables('URL')(); } /** * 获取请求的方法。 * @return {String} */ function getHTTP_Method(){ return Request.ServerVariables('HTTP_METHOD')(); } /** * @return {String/Null} */ function getReferer(){ var arr = Request.ServerVariables('all_raw')().match(/referer:\s*(.*)/i); return arr == null ? null : arr[0]; }处理响应:
/** * 统一编码为UTF-8。 */ function setUTF8(){ Response.Charset = "utf-8"; Session.CodePage = 65001; Session.Timeout = 200; Server.ScriptTimeout = 100; } /** * 301 永久重定向。 * @param {String} url 要跳转的地址。 */ function urlForward(url){ Response.Status = "301 Moved Permanently"; Response.addHeader("Location", url); Response.End(); } /** * 强制不缓存。 */ function noCache(){ with(Response){ buffer = false; expires = -1; addHeader("Pragma", "no-cache"); addHeader("cache-ctrol","no-cache"); } }
获取来自表单的各个字段的数据。HTTP FORM 所提交的数据乃是 key/value 配对的结构。本函数是把这些 key/value 集合转化成为 JavaScript 对象。这是 post 包最核心的函数。对各个字段有 decodeURI()、unescape()、$$.getPerm() 自动类型转化的功能。@param {Boolean/String} isAllRequest true 表示为返回 QueryString 和 Form 的 Request 对象集合。如果指定 form 则返回表单的 hash 值,如果指定 QueryString 则返回 URL 上面的参数 hash。
/** * @return {Object} 客户端提交的数据或查询参数。 */ function every(collection){ var count = collection.count; var obj = { count : count // 字段总数 }; var emu = new Enumerator(collection); var key, v; while(count > 0 && !emu.atEnd()){ key = emu.item().toString(); v = collection(key)(); // MS ASP这里好奇怪,不加()不能返回字符串 obj[key] = $$.getPrimitives(v); // 进行自动类型转换。 emu.moveNext(); } return obj; }; this.formPost = every.delegate(Request.Form); this.queryString = every.delegate(Request.QueryString);
Raw POST 的解析器。Raw POST 也是标准 HTTP POST 方式的一种,但不像 key1=value2&key2=value2 形式提交的文本,而是一堆二进制的数据。浏览器提交的 POST 数据视作原始的数据,须经过 Adodb.Stream 二进制解码后才可以使用。注意:一旦使用过该方法。Request.Form('key') 就无法使用,但通过 URL 传递的 QueryString 仍可用。
/** * @return {Object} */ function getRawPost(){ with(new ActiveXObject('Adodb.Stream')){ open(); type = 2; // 2 = adTypeText writeText(Request.binaryRead(Request.totalBytes)); position = 0; charset = "us-ascii"; // gb2312? position = 2; eval('var requestObj = ' + readText());// 使用 eval()函数时,必须要一个变量来承载! close(); return requestObj; } } /** * 是否POST提交数据。 * @return {Boolean} */ function isPOST(){ return Request.ServerVariables('HTTP_METHOD')() == "POST"; } /** * 获取 HTTP 报文的 ContentType。可支持 GET 操作下的 ContentType。 * @return {String} */ function getContentType(){ return Request.ServerVariables('Content_Type')(); } /** * 返回是否Raw 的POST。这个方法在某种场合必须使用,例如Ext.Direct。 * 约定:凡application/json的提交均视作Raw POST。 * @return {Boolean} */ function isRawPOST(){ return isPOST() && (getContentType().indexOf('application/json')) != -1 }
Cross Browser XML Loader:
/** * 兼容浏览器与服务端的 XML 加载器。注意,当前模式下关闭了异步的通讯方式。 * create a document object * @param {String} xml XML 文档路径或者 XML 文档片段。 * @param {Boolean} isNode true 表示为送入的为 XML 文档片段。 * @return {Object} the document */ function loadXML(xml, isNode){ var doc; if(typeof ActiveXObject != 'undefined'){ doc = $$.xml.doc(); }else if(typeof document != 'undefined' && !isNode){ if(document.implementation && document.implementation.createDocument){ doc = document.implementation.createDocument("", "", null); } }else if(typeof DOMParser != 'undefined' && isNode){ doc = new DOMParser().parseFromString(xml, "text/xml"); // 加载XML片段(Moliza Firefox) return doc; } if(!doc){ throw '创建XML文档对象失败!'; } doc.async = false; // 关闭异步特性 if (xml && !isNode && (doc.load(xml) == false)){ // 加载一份完整的XML文档(Moliza Firefox 与 IE均如此) throw '加载XML文档资源失败!'; }else if(xml && isNode && (doc.loadXML(xml) == false)){ // 加载XML片段(IE) throw '加载XML片段失败!'; } return doc; } /** * 定位某一 XML 节点。如果找不到则返回一空数组。 * 用法: var doc = loadXML('edk.xml'); var xml = getNode.call(doc, '//head', false); * @param {String} xpathQuery 查询定位符。 * @param {Boolean} isSerialize 可选的,是否序列号查询结果为字符串,默认为true。 */ loadXML.getTpl = tpl.getTpl = function(xpathQuery, isSerialize){ var nodes = []; var msg = 'NO Exist Node! 不存在匹配的节点,请检查输入的条件再作查询。'; if(typeof isSerialize == 'undefined'){ isSerialize = true; } if(typeof this.selectNodes != 'undefined'){ nodes = this.selectSingleNode(xpathQuery); if(nodes == null){ throw msg; } return isSerialize ? nodes.xml :nodes; }else if(document.implementation.hasFeature('XPath', '3.0')){ // FF需要作进一步转换 var resolver = this.createNSResolver(this.documentElement); var items = this.evaluate(xpathQuery, this, resolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); for(var i = 0, j = items.snapshotLength; i < j; i++){ nodes[i] = items.snapshotItem(i); } if(!nodes.length){ throw msg; } return isSerialize ? new XMLSerializer().serializeToString(nodes[0]) : nodes; } return nodes; }
保存日志格式为 XML的:
function log(log){ var json, tmp; if(log.sql){ tmp = log.sql; delete log.sql; } json = $$.XML.json2xml(log); var logItem = $$.xml.doc(); if(!logItem.loadXML('<item>' + json + '</item>')){ throw "生成日志XML文档时错误"; } var value = logItem.createElement("value"); var cd = logItem.createCDATASection(tmp); value.appendChild(cd); logItem.firstChild.appendChild(value); // 插入新的XML节点 var xml = new ActiveXObject('Msxml2.DOMDocument.6.0'); if(!xml.load(Server.Mappath('/app/public/log.xml'))){ throw "保存日志XML文档时错误"; } xml.lastChild.appendChild(logItem.firstChild); $$.XML.saveXML(xml); logItem = xml = value = cd = null; return true; }
All XML Helper:
/** * @class * @singleton * 兼容浏览器与服务端的XML加载器。 */ $$.xml = $$.XML = new (function(){ /** * 把 XML 格式转换为 JSON 输出。MS ONLY 暂时。 * @param {IXMLDOMNode} n * @return {Object} JSON */ this.xml2json = function (node){ var obj = {}; var element = node.firstChild; while (element) { if (element.nodeType === 1) { var name = element.nodeName; var sub; sub = arguments.callee(element) sub.nodeValue = ""; sub.xml = element.xml; sub.toString = function() { return this.nodeValue; }; sub.toXMLString = function() { return this.xml; } // get attributes if (element.attributes) { for (var i = 0; i < element.attributes.length; i++) { var attribute = element.attributes[i]; sub[attribute.nodeName] = attribute.nodeValue; } } // get nodeValue if (element.firstChild) { var nodeType = element.firstChild.nodeType; if (nodeType === 3 || nodeType === 4) { sub.nodeValue = element.firstChild.nodeValue; } } // node already exists? if (obj[name]) { // need to create array? if (!obj[name].length) { var temp = obj[name]; obj[name] = []; obj[name].push(temp); } // append object to array obj[name].push(sub); } else { // create object obj[name] = sub; } } element = element.nextSibling; } return obj; } // @todo 需要吗? var Xml = { text: function(text) { return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">") }, attr: function(name, value) { return (name && value != null) ? (" " + name + "=\"" + this.text(value).replace(/"/g, /* "-->\x22 ? */ """) + "\"") : ""; }, cdata: function(text) { return (text) ? "<![CDATA[" + text.toString().replace("]>>", "]>>") + "]>>" : ""; } }; /** * json2xml */ this.json2xml = function(obj) { var str = ''; var tpl = '<{0} type="{1}">{2}</{0}>\n'; function serialize(obj) { var fn = arguments.callee; var xml = []; var typ = typeof obj; switch (typ) { case "object" : { if (obj === null) { xml.push("<" + i + " />\n"); // 空的闭合标签 } else if (typeof obj.getTime === "function") { xml.push(tpl.format(i, 'date', obj.toUTCString())); } else if (typeof obj.join === "function") { for (var j = 0; j < obj.length; j++) { xml.push(fn(obj[j])); } } else { xml.push(tpl.format(i, typ, fn(obj))); } break; } case "string" : obj = obj.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); case "date" : case "boolean" : case "number" : xml.push(tpl.format(i ,typ, obj)); } return xml.join(""); } for (var i in obj) { str += serialize(obj[i]); } return str; } /** * 兼容浏览器与服务端的XML加载器。注意,当前模式下关闭异步的通讯方式。 * create a document object * @param {String} xml XML文档路径或者XML文档片段。 * @param {Boolean} isNode true表示为送入的为XML文档片段。 * @return {Object} the document */ this.loadXML = function(xml, isNode){ var doc; if(typeof ActiveXObject != 'undefined'){ doc = $$.xml.doc(); }else if(typeof document != 'undefined' && !isNode){ if(document.implementation && document.implementation.createDocument){ doc = document.implementation.createDocument("", "", null); } }else if(typeof DOMParser != 'undefined' && isNode){ doc = new DOMParser().parseFromString(xml, "text/xml"); // 加载XML片段(Moliza Firefox) return doc; } if(!doc){ throw '创建XML文档对象失败!'; } doc.async = false; // 关闭异步特性 if (xml && !isNode && (doc.load(xml) == false)){ // 加载一份完整的XML文档(Moliza Firefox 与 IE均如此) throw '加载XML文档资源失败!'; }else if(xml && isNode && (doc.loadXML(xml) == false)){ // 加载XML片段(IE) throw '加载XML片段失败!'; } return doc; } /** * 定位某一XML节点。如果找不到则返回一空数组。 * 用法: var doc = loadXML('edk.xml'); var xml = getNode.call(doc, '//head', false); * @param {String} xpathQuery 查询定位符。 * @param {Boolean} isSerialize 可选的,是否序列号查询结果为字符串,默认为true。 */ this.getNode = function(xpathQuery, isSerialize){ var nodes = []; var msg = 'NO Exist Node! 不存在匹配的节点,请检查输入的条件再作查询。'; if(typeof isSerialize == 'undefined'){ isSerialize = true; } if(typeof this.selectNodes != 'undefined'){ nodes = this.selectSingleNode(xpathQuery); if(nodes == null){ throw msg; } return isSerialize ? nodes.xml :nodes; }else if(document.implementation.hasFeature('XPath', '3.0')){ // FF需要作进一步转换 var resolver = this.createNSResolver(this.documentElement); var items = this.evaluate(xpathQuery, this, resolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); for(var i = 0, j = items.snapshotLength; i < j; i++){ nodes[i] = items.snapshotItem(i); } if(!nodes.length){ throw msg; } return isSerialize ? new XMLSerializer().serializeToString(nodes[0]) : nodes; } return nodes; } /** * perform an XLS transformation on an XML document and store the results in an element * @param {Element} target the element to populate with the results of the transformation */ this.transform = function (xsltDoc, target){ if(XSLTProcessor){ var processor = new XSLTProcessor(); processor.importStylesheet(xsltDoc); var doc = processor.transformToFragment(this, document); target.appendChild(doc); }else{ target.innerHTML = this.transformNode(xsltDoc); } } /** * 依据数据值是什么(在 data 里查找匹配的),决定选中哪一个 Option 控件。 * @param {IXMLDOMNode} n XML节点对象。 * @param {Object} data 数据对象。 * @return {IXMLDOMNode} XML节点对象。 */ this.setSelectByNode = function(n, data){ var i, k; var selectEl; for(i in data){ k = ".//select[@name='{0}']".format(i); selectEl = n.selectSingleNode(k); if(selectEl){ // 选中value一样的节点 for(var z = 0; z < 2; z++){ if( selectEl.childNodes(z).attributes(0).value == data[i]){ selectEl.childNodes(z).setAttribute('selected', 'true'); // 设置为选中! } } } } return n; } /** * 依据数据值是什么(在 data 里查找匹配的),决定选中哪一个Radio控件。 * @param {IXMLDOMNode} n XML节点对象。 * @param {Object} data 数据对象。 * @return {IXMLDOMNode} XML节点对象。 */ this.setRadioByNode = function(n, data){ var k ,selectEl; for(var i in data){ k = ".//input[@name='{0}']".format(i); selectEl = n.selectNodes(k); if(selectEl && selectEl.length > 0){ // 选中value一样的节点 for(var z = 0; z < selectEl.length; z++){ // 默认attributes(3)是name属性 if( selectEl(z).attributes(1).value == data[i]){ selectEl(z).setAttribute('checked', 'true'); // 设置为选中! } } } } return n; } /** * 从XML文档中选择指定的模板文件,将其数据绑定 data 输出。 * 有取消 CData 作转意之功能。 * @param {String} xmlFile XML 片段或者是 XML 文件,需要完全的文件路径地址,一般需要 Server.Mappath() 获取真实地址后才输入到这里。 * @param {String} xPath XPath路径。 * @param {Object} data (可选的)数据实体,通常是 JSON 实体或者是配置文件 $$.cfg。 * @return {String} 携带数据的HTML。 */ this.from_XML_Tpl = function(xmlFile, xPath, data){ var xml = new ActiveXObject('Msxml2.DOMDocument.6.0') ,node ,tpl ,html; // 自动判别是否可使用服务端的方法 if(typeof Server != 'undefiend'){ if(xml.load(Server.Mappath(xmlFile)) != true){ throw '加载' + xmlFile + '模板文件失败'; }; }else{ if(xml.loadXML(xmlFile) != true){ throw '加载' + xmlFile + '片段失败'; }; } node = xml.selectSingleNode(xPath); if(!node){ throw '没有模板节点'; } // Option的Selected属性等的居然没用完整XML陈述形式!! if(data){ $$.XML.setSelectByNode(node, data); $$.XML.setRadioByNode(node, data); } tpl = node.xml ,tpl = tpl.replace('&', '&') // 规避XML标准的转义 ,xml = null ,html = data ? new Edk.Template(tpl).applyTemplate(data) : tpl; // 由于有些地步不合XML WellForm标准,故用CData豁免之,先还原。 if(html.indexOf('<![CDATA[')){ html = html.replace('<![CDATA[', '').replace(']]>', ''); } return html; } this.saveCDATA = function (){ var post = $$.form.post(); var filePath = Server.Mappath($$.cfg.edk_root + '/app/form/staticPage.xml'); var xml = new ActiveXObject('Msxml2.DOMDocument.6.0'); if(!xml.load(filePath)){ throw "打开模板文件错误"; } var parentNode = xml.selectSingleNode('//' + page.split('.').pop()); var CDataNode = xml.createCDATASection(post['Content']); parentNode.replaceChild(CDataNode, parentNode.childNodes(0)); $$.XML.saveXML(xml, filePath); Response.Write('写入CData数据成功!'); return true; } function setXML_cData(data, wrapTag, isCDATA){ this.contentType = 'text/xml'; if(typeof data == 'string'){ if(wrapTag && !isCDATA){ data = '<{0}>{1}</{0}>'.format(wrapTag, data); }else if(wrapTag && isCDATA){ data = '<{0}>' + ("<![CD" + "ATA[") + "{1}" + ("]>" + ">") + '</{0}>'.format(wrapTag, data.replace(("]>" +">"), "]>>")); } return data; }else{ data = $$.XML.json2xml(data); } return datal } });
XML Class
$$.xml = { doc : function(){ var doc; /** * ActiveX Objects for importing XML (IE only) * @type Array */ var MSXML = [ "Msxml2.DOMDocument.6.0", "Msxml2.DOMDocument.5.0", "Msxml2.DOMDocument.4.0", "Msxml2.DOMDocument.3.0", "MSXML2.DOMDocument", "Microsoft.XMLDOM" ]; for(var i = 0, j = MSXML.length; i < j; i++){ try{ doc = new ActiveXObject(MSXML[i]); break; }catch(e){} } return doc; } /** * 保存 XML 对象为 XML 文本文件。 * 注意:Server Side Only * @param {XMLDocument} xmlDoc XML 文档对象本身。 * @param {String} xmlFilePath 可选的。XML 文档的真实磁盘路径。 * @return {Boolean} 是否操作成功。 */ ,save : function(xmlDoc, xmlFilePath){ xmlFilePath = xmlFilePath || xmlDoc.url.replace(/file:\/\/\//,''); // make a clone var saver = this.doc(); saver.loadXML(xmlDoc.xml); if( saver.readyState == 4 && saver.parsed){ saver.save(xmlFilePath); } return true; } /** * @param {String} node * @param {String} str */ ,saveCDATA : function (node, str){ var parentNode = this.selectSingleNode(node); var CDataNode = this.createCDATASection(str); parentNode.replaceChild(CDataNode, parentNode.childNodes(0)); return true;// 写入CData数据成功! } };
Model指的是一份XML文件,里面的某一段节点。这一段节点定义了域对象包含了哪几种字段。Model的定义是任意可设计的,依据业务对象的性质去安排。当前我们只读取input节点的定义,以别于其他无用的(相对的)HTML Markup。所以暂时借用了$$.vaildator表单验证器,但可以考虑在未来的版本中提供更全面的支持。该方法读取XML模型文件,获取模型。建议只读取一次,成为固定的属性,因为加载XML文件消耗资源。
启动该BO的特定方法。我们采用OO的方法论来规划后台的接口,也就是确定了业务对象是那个实体,然后执行对应的业务方法即可。 上述只是一个简单的步骤,细分之下,其中必然涉及复杂的浏览,这里就大致的讲一讲。 一般来说,每一个HTTP请求,服务端接纳后并执行的方法,就是这个方法。 每一个BO相当于是一个Action,也可以视作一个工作流的开始,就好像一个这样的流程如下: Action: Get HTTP post-->isSafe-->vaild-->permission-->do the job-->-->Response-->logging 除了do the job属于具体的业务方法外,其他走的都是一般的Web提交信息的后台所执行的工序。 大体设计上,该方法的作用有以下几点: 一、在此方法中完成了居于流程中前端的Get HTTP POST的工作。 getMethod的作用是依据请求,分拣出应该执行那个方法,因此返回的类型是Function。 换言之,具体执行的哪个方法,访问getMethod就可以知道。从一个侧面讲,getMethod具有明显的函数变量的特点,反映了当前方法的动态性。 this.actions是该BO的动作列表,须要传入到getMethod中,让getMethod从中选择。 getMethod()可支持经典的Web请求或者Ext.Diirect通讯模式。 有关$$.Entity.getMethod()的详细资讯,请参阅其文档。 二、开辟this管道。虽然getMethod()返还方法,但方法链已经不能支持args.callee, 只能除参数外选择其他方法实现信息通道,渗透控制工作流上每一个函数。好在JS有一个this管道,估计初衷是结合OO的遗留物, 现在FP的理念没想到有很好的作用。可以开辟参数以外的又一理想“寄存器”。不过推荐读取内容仍须经参数送入,写入的就可以这个this中进行,分工更加清晰。 三、此时马上就发挥this的作用。通过call(obj)实质就是决定了工作流内部每一个方法的this指针都是指向obj参数的。 (该参数为一“匿名对象”)。考虑到某些横向的对象引用,以此方便提取信息,当前提供user、domain等的管道在内。 该方法最终返还字符串,进而执行(在函数体外)Response.Write()输出到客户端输出内容或反馈信息。
* 结论1:Select * 语句对速度影响很大。 select * from Adab 用时:35940 (共57个字段) select xhjm,xm,xjztdm,bdm,nj,dwdm from Adab 用时 4186 select xhjm,xm from Adab 用时1626 select xm from Adab 用时830 *可以看得出每增加一个字段,时间会增加几乎是一倍。 * 另外,返回的数据的长度也会影响时间:如 select sfzh from Adab 用时1580 几乎与select xhjm,xm from Adab相同。 因此,返回的数据量是影响速度最关键的因素。网络上的数据传送成了执行SQL最大的性能问题。 执行一个selec dwdm,count(xhjm) from Adab的语句,用时106 要汇总计算,但传送数据少,因此速度很快