JavaScript DOM高级程序设计 3.6 实例 将HTML代码转换成DOM代码,附源码--我要坚持到底!

作为一名Web开发者,最讨厌的事情就是重复性任务,摆脱乏味的日常重复性事物的一种方法,是借助可重用的对象或者说与你现在建立的ADS库类似的库,另外一种让事情变得有意思,且能够加速开发进程的方式是编写能够创建代码的代码。

本节讲的工具,就是它可以在快速生成要的DOM代码是用来取代使用innerHTML字符串

HTML代码:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>DOM Generation</title>
    <title>AdvancED DOM Scripting Sample Document</title>
    <!-- inclue some CSS style sheet to make everything look a little nicer -->
    <link rel="stylesheet" type="text/css" href="../../shared/source.css" />
    <link rel="stylesheet" type="text/css" href="../chapter.css" />
    <link rel="stylesheet" type="text/css" href="style.css" />

    <!-- Your ADS library with the common JavaScript objects -->
    <script type="text/javascript" src="../../ADS-final-verbose.js"></script>
    <!-- Log object from Chapter 2 -->
    <script type="text/javascript" src="../../chapter2/myLogger-final/myLogger.js"></script>
    <!-- The DOM generation file -->
    <script type="text/javascript" src="generateDOM.js"></script>
    <!-- The load script -->
    <script type="text/javascript" src="load.js"></script>
</head>
<body>
<h1>DOM Generation</h1>
<div >
<form  action="">
    <fieldset>
        <h2>Source</h2>
        <label for="source">Enter an HTML document fragment</label>
        <textarea  cols="30" rows="15">
</textarea>
        <input  type="button" value="↓ generate ↓" />
        <h2>DOM Code</h2>
        <label for="result">and voila! DOM goodness:</label>
        <textarea  cols="30" rows="15"></textarea>
    </fieldset>
</form>
</div>
</body>

</html>

页面包含一个generateDOM.js文件和一个调用generateDOM()方法,转换HTML代码的非常简单的load.js脚本

//向页面中添加载入事件,注册事件侦听器
ADS.addEvent(window,'load',function(){
    //在按钮上注册一个单机时间侦听器
    ADS.addEvent('generate','click',function(W3CEvent){
        //取得HTML源代码
        var source=ADS.$('source').value;
        //将HTML转换成DOM并放到#result文本区
        ADS.$('result').value=generateDOM(source);
    });
});

在构建generateDOM对象的框架之前,还需要想ADS.js添加几个方法

/*把对原型的修改放在ADS命名控件之外,是为了提醒你对内部String对象的prototype
的修改会影响到整个脚本中的每个字符串,而不仅仅是在ADS.generateDOM对象内部有影响
//重复一个字符串
if (!String.repeat)
{
    String.prototype.repeat=function(s)
    {
        return new Array(s+1).join(this);
    }
}
//var example='a'.repeat(5);
//example现在是aaaaa
//清除结尾 和开头处的空白符
if (!String.trim)
{
    String.prototype.trim=function(){
        retun this.replace(/^\s+|\s+$/g,'');
    }
}
*/

在ADS.js库中添加一下代码

//把word-word转换为wordWord
//用于处理嵌入式样式的属性。
function camelize(s)
{
    return s.replace(/-(\w/)/g,function(strMatch,p1){
        return p1.toUpperCase();
    });
}
window['ADS']['camelize']=camelize;

下面剩下唯一意见事就是在generateDOM.js文件中创建generateDOM对象了。框架以创建一个新的命名空间开始,人后包含了一些辅助方法和属性,最后是为window方法复制的代码:

//generateDOM对象的新命名空间
(function () {

    //保证字符串是一个安全的js字符串,因为转换工具生成的字符串会包含在单引号中
    //所以只需要转移反斜杠、单引号和换行符即可
    function encode(str) {
        if (!str) {
            return null;
        }
        str = str.replace(/\\/g, '\\\\');
        str = str.replace(/';/g, "\\'");

        str = str.replace(/\s+^/mg, "\\n");
        return str;
    }
    //查找所有节点中那些特殊的$var字符串。并对他们进行处理
    //检查是否存在美元符号,如果是,则返回一个带引号的字符串或者一个变量名称
    //而且还会把变量声明添加到requiredVariables字符串中。
    function checkForVariable(v) {
        if (v.indexOf('$') == -1) {
            v = '\'' + v + '\'';
        }
        else {
            //因MSIE会添加锚完整路径故需要取得该字符串从$到结尾出的子字符串
            v = v.substring(v.indexOf('$') + 1);
            requiredVariables += 'var' + v + ';\n'
        }
        return v;
    }

    var domCode = '';
    var nodeNameCounters = [];
    var requiredVariables = '';
    var newVariables = '';

    //借助如下代码,了解他内部工作过程
    function generate(strHTML, strRoot) {
        //将HTML代码添加到页面的主题中以便能遍历相应的DOM树
        var domRoot = document.createElement('DIV');
        //因为你可以控制在那个浏览器运行这个工具,所有innerHTML是可以使用的
        domRoot.innerHTML = strHTML;

        //重置变量
        domCode = '';
        nodeNameCounters = [];
        requireVariables = '';
        newVariables = '';

        //使用processNode()处理domRoot中的所有子节点
        var node = domRoot.firstChild;
        while (node) {
            ADS.walkTheDOMRecursive(processNode, node, 0, strRoot);
            node = node.nextSibling;
        }

        //输出生成的代码
        domCode =
            '/*requiredVariables in this code\n' + requiredVariables + '*/\n\n'
        + domCode + '\n\n' + '/* new objects in this code\n' + newVariables + '*/\n\n';
        return domCode;
    }

//循环遍历子节点
    function processNode(tabCount, refParent) {
        //根据树的深度级别重复制表符以便对每一行进行适当的缩进,代码更清晰,更容易理解
        var tabs = (tabCount ? '\t'.repeat(parseInt(tabCount)) : '');

        //确定节点类型并处理元素和文本节点
        switch (this.nodeType) {
            //处理元素节点 
            case ADS.node.ELEMENT_NODE:
                //计数器加1并创建一个使用标签和计数器的值表示的新变量,例如a1,a2,a3
                if (nodeNameCounters[this.nodeName]) {
                ++nodeNameCounters[this.nodeName];
                }
                else {
                    nodeNameCounters[this.nodeName]=1
                }

                var ref = this.nodeName.toLowerCase() + nodeNameCounters[this.nodeName];
                //添加创建这个元素的DOM代码航
                domCode += tabs + 'var ' + ref + ' =document.createElement(\''
                + this.nodeName + '\');\n';

                //将新变量添加到列表中以便在结束中报告他们
                newVariables += '' + ref + ';\n';

                //检测是否存在属性,如果是则循环遍历这些属性,并使用processAttribute()
                //遍历他们的DOM树
                if (this.attributes) {
                    for (var i = 0; i < this.attributes.length; i++) {
                        ADS.walkTheDOMRecursive(processAttribute, this.attributes[i], tabCount, ref);
                    }
                }
                break;
            //处理文本节点 
            case ADS.node.TEXT_NODE:
                //检测文本节点中除了空白符之外的值
                var value = (this.nodeValue ? encode(this.nodeValue.trim()) : '');
                if (value) {
                    //计数器加1并创建一个使用txt和计数器的值
                    //表示的新变量,例如txt1,txt2...
                    if (nodeNameCounters['txt']) {
                        ++nodeNameCounters['txt'];
                    }
                    else {
                        nodeNameCounters['txt'] = 1;
                    }
                    var ref = 'txt' + nodeNameCounters['txt'];

                    //检查是不是$var格式的值
                    value = checkForVariable(value);

                    //添加创建这个元素的DOM代码
                    domCode += tabs + 'var' + ref + ' =document.createTextNode(' + value + ');\n';
                    //将新变量添加到列表中以便在结果中报告它们
                    newVariables += '' + ref + ';\n';

                }
                else {
                    //如果不存在值(或者只是空白符)则返回
                    //即这个节点将不会被添加到父节点中
                    return;
                }
                break;
            default:
                //忽略其他情况
                break;
        }
        //添加将这个节点添加到父节点的代码
        if (refParent) {
            domCode += tabs + refParent + '.appendChild(' + ref + ');\n';
        }
        return ref;
    }

    function processAttribute(tabCount, refParent) {
        //跳过文本节点
        if (this.nodeType != ADS.node.ATTRIBUTE_NODE) {
            return;
        }
        //取得属性值
        var attrValue = (this.nodeValue ? encode(this.nodeValue.trim()) : '');
        if (this.nodeName == 'cssText') {
            alert('true');
        }
        //如果没有值返回
        if (!attrValue) {
            return;
        }
        //确定缩进级别
        var tabs = (tabCount ? '\t'.repeat(parseInt(tabCount)) : '');
        //根据nodeName进行判断,除了class和style需要特殊注意以外,所有类型都可以按常规来处理
        switch (this.nodeName) {
            default:
                if (this.nodeName.substring(0, 2) == 'on') {
                    //如果属性名称以‘on’开头,说明是一个嵌入事件属性,
                    //也就需要重新创建一个给该属性复制的函数
                    domCode += tabs + refParent + '.' + this.nodeName + '=function(){' + attrValue + '}\n';

                } else {
                    //对于其他情况则使用setAttribute
                    domCode += tabs + refParent + '.setAttribute(\'' + this.nodeName + '\'.' + checkForVariable(attrValue)
                        + ');\n';
                }
                break;
            case 'class':
                //使用className属性为class赋值
                domCode += tabs + refParent + '.className=' + checkForVariable(attrValue)
                    + ';\n';
                break;
            case 'style':
                //使用增则表达式基于;和临近的空格符来分割样式属性的值
                var style = attrValue.split(/\s*;\s*/);
                if (style) {
                    for (pair in style) {
                        if (!style[pair]) {
                            continue;
                        }
                        //使用增则表达式基于;和临近的空格符来分割样式属性的值
                        var prop = style[pair].split(/\s*:\s*/);
                        if (!prop[1]) {
                            continue;
                        }
                        //将css-property格式的css属性转换为cssProperty格式
                        prop[0] = ADS.camelize(prop[0]);
                        var propValue = checkForVariable(prop[1]);
                        if (prop[0] == 'float') {
                            //float是保留字,因此属特殊情况,cssFloat是标准属性
                            //styleFloat是ie使用的属性
                            domCode += tabs + refParent + '.style.cssFloat=' + propValue + ';\n';
                            domCode += tabs + refParent + '.style.styleFloat=' + propValue + ';\n';

                        }
                        else {
                            domCode += tabs + refParent + '.;\n';
                        }

                    }
                }
                break;
        }
    }

    
    window['generateDOM'] = generate;
})();

demo:http://vdisk.weibo.com/s/Da3Hr