自从本日起,开始写写关于 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; // 未转换。
}
明显,这里的 Date 不是原语类型,不应加入到 getPrimitives()中去,但是某些时候恰恰就是需要判定日期类型的,于是得加上。
对象的继承
对象的复制
对象的序列化
客户端信息
封装一下客户端信息:
/**
* 返回请求的路径(即当前页面的路径)。
* @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");
}
}
序列化 Request 对象
获取来自表单的各个字段的数据。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);
getRawPost()
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
}
XML处理
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 要汇总计算,但传送数据少,因此速度很快