第十四章:表单脚本(选择框脚本、表单序列化、富文本编辑)

表单脚本

选择框脚本

  • 选择框是通过<select><option>元素创建的。<select>元素是在JS中是HTMLSelectElement 类型,提供了额外的属性和方法:
    1. add(newOption, relOption):向控件中插入新<option>元素,其位置在相关项(relOption)之前。可以通过new Option(text, value)创建HTMLOptionElement 类型的对象,此外用这个函数来调换两个option元素位置也挺好用的(类似于insertBefore)。
    2. multiple:布尔值,表示是否允许多项选择;等价于HTML 中的multiple 特性。
    3. options:控件中所有<option>元素的HTMLCollection。
    4. remove(index):移除给定位置的选项。如果不传参数,则会把整个select移除掉。如果传的不是Number类型的,会通过valueOf或者toString转成Number类型。没有重写的话会返回0?
    5. selectedIndex:基于0 的选中项的索引,如果没有选中项,则值为-1。对于支持多选的控件,只保存选中项中第一项的索引。改变这个值也可以设置选中项
    6. size:选择框中可见的行数;等价于HTML 中的size 特性。
    7. type:”select-one“或”select-multiple“,取决是是否有multiple 特性。
  • 选择框的value属性由选中项决定(一些自定义标签实现了设置value改变选中项的功能,但默认是没有这个功能的):
    1. 如果没有选中的项,则选择框的value保存空字符。
    2. 如果有选中项或多个选中项,取第一个选中项作为参考。如果第一个选中项的value有值,则select的value值将设置与其相同;如果无值,则设置为option的文本值。
  • 针对上面的特点,下面是两个例子:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="javascript:alert(1)">
    <select>
        <option>1</option>
        <option>2</option>
        <option>3</option>
        <option>4</option>
        <option>5</option>
        <option>6</option>
        <option>7</option>
        <option>8</option>
        <option>9</option>
        <option>10</option>
    </select>
</form>
</input>
<script>
    var select = document.forms[0].elements[0];
    console.log(select.type);//select-one
    var options = select.options;
    console.log(options.length);//10
    select.remove(0);
    select.remove({});//相当于remove(0)
    select.remove(9);
    select.remove(
        {
            valueOf:function(){
                return 7;
            }
        }
    );
    select.add(options[1], options[0]);//调换前两个选择的位置
</script>
</body>
</html>
  • 在做select的value值实验前,我们来了解一下HTMLOptionElement类型。每个option都是该类型,该类型有下列额外的属性:
    1. index:当前选项在options集合中的索引。
    2. label:等价于label特性。
    3. selected:布尔值,表示当前选项是否被选中。将这个属性设置为true 可以选中当前选项。
    4. text:选项的文本。
    5. value:选项的值(等价于HTML 中的value 特性)。在未指定value 特性的情况下,IE8 会返回空字符串,而IE9+、Safari、Firefox、Chrome 和Opera 则会返回与text 特性相同的值。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="javascript:alert(1)">
    <select name="location" id="selLocation">
        <option value="Sunnyvale, CA">Sunnyvale</option>
        <option value="Los Angeles, CA">Los Angeles</option>
        <option value="Mountain View, CA">Mountain View</option>
        <option value="">China</option>
        <option>Australia</option>
    </select>
</form>
</input>
<script>
    var select = document.forms[0].elements[0];
    select.addEventListener("change", function (e) {
        var select = e.target;
        console.log(select.value);
        console.log(select.options[select.selectedIndex].value 
                + "-----" + select.options[select.selectedIndex].text);
    });
    /*select.attachEvent("onchange", function (e) {
        var select = e.srcElement;
        console.log(select.value == "");
        console.log(select.options[select.selectedIndex].value
                + "-----" + select.options[select.selectedIndex].text);
    });*/ //IE代码
</script>
</body>
</html>
------------------------------
Los Angeles, CA
Los Angeles, CA-----Los Angeles
Sunnyvale, CA
Sunnyvale, CA-----Sunnyvale
Mountain View, CA
Mountain View, CA-----Mountain View

-----China    //主动设置为"",将返回""
Australia
Australia-----Australia  //不设置value特性,可以理解成设置value=text(但是没有改变HTML)
  • 针对上面最后一个特性,我想知道,这个是不是自动设置了value特性所造成的。于是我用getAttribute去获取option的value特性,结果发现是null。这说明了value属性与text相同,并不是自动设置了value特性。而应该是一种类似呼叫转移(这个词纯属胡诌)的行为。这个结果也给了我们一个启示:也就是前一篇文章开头提到的:对value 属性所做的修改,不一定会反映在DOM中。虽然value值与value特性息息相关,但是value值的变化并不都是反映了DOM中的value特性的变化,也可能是text的变化。所以要获取value值,千万不要通过getAttribute这种方式。
  • 通过常规DOM的方式去获取option元素的value与text的效率也会更低。所以我们在得到HTMLOptionElement对象后,要多去使用value和text属性而避免再通过getAttribute和innerHTML去获取value和text特性。

选择选项

  • 选中选项有两种方式,一个是设置select元素的selectedIndex,另外一种则是设置option元素的selected。接下来分单选多选框进行讨论。
  • 如果是单选框,那么selectedIndex的用法是没有疑问的,而selected就有一个问题:如果我设置了多个option的selected都为true,那会如何:
<select name="location" id="selLocation">
    <option value="Sunnyvale, CA" selected="true">Sunnyvale</option>
    <option value="Los Angeles, CA"selected="true">Los Angeles</option>
    <option value="Mountain View, CA"selected="true">Mountain View</option>
    <option value="">China</option>
    <option>Australia</option>
</select>
<script>
    var select = document.forms[0].elements[0];
    console.log(select.options[0].selected);//fasle
    console.log(select.options[1].selected);//false
    console.log(select.options[2].selected);//true
</script>
----------------------
无论在何种浏览器中都是选中了第三个,可见这种行为只认最后一个的设置。
如果我单纯通过脚本去设置:结果一模一样
<script>
    var select = document.forms[0].elements[0];
    select.options[0].selected = true;
    select.options[1].selected = true;
    select.options[2].selected = true;
    console.log(select.options[0].selected);//fasle
    console.log(select.options[1].selected);//false
    console.log(select.options[2].selected);//true
</script>
  • 如果是多选框,那selected可以不用讨论了,可以允许多个被选中,也就可以允许多个为true。但是selectedIndex的行为就很诡异。选中了多个选项,selectedIndex只会返回第一个被选中的选项的索引。如果通过脚本改变selectedIndex,则会只选中那一项,即使之前已经选中了很多项。例子就不举了。

添加选项

  • 可以使用JavaScript 动态创建选项,并将它们添加到选择框中。添加选项的方式有很多,第一种方式就是使用如下所示的DOM 方法。
var newOption = document.createElement("option");
newOption.appendChild(document.createTextNode("Option text"));
newOption.setAttribute("value", "Option value");
selectbox.appendChild(newOption);
  • 第二种就是之前提到过的使用new Option(“Option text”, “Option value”);创建一个HTMLOptionElement对象,然后通过appendChild或者select元素独有的add方法去添加到DOM中。
var newOption = new Option("Option text", "Option value");
selectbox.appendChild(newOption); //在IE8 及之前版本中有问题

//下面是使用add方法,兼容DOM的浏览器要求必须有第二个参数。
selectbox.add(newOption, undefined); //添加到末尾的最佳方案

移除选项

  • 除了最常规的DOM的removeChild()方法外,还可以使用select元素特有的remove()方法。这个方法传入一个索引即可使用,前面已经试验过了。还有一种遗留机制:
selectbox.options[0] = null; //移除第一个选项
  • 如果想要移除所有选项,可以遍历获取options的长度,然后执行长度次数的remove(0)即可(DOM都是动态的)。
for(var i=0, len=selectbox.options.length; i < len; i++){
    selectbox.remove(0);//千万不要以为是remove(i),否则你会发现只能移除索引为偶数的option
}

移动和重排选项

  • 在DOM 标准出现之前,将一个选择框中的选项移动到另一个选择框中是非常麻烦的。整个过程要涉及从第一个选择框中移除选项,然后以相同的文本和值创建新选项,最后再将新选项添加到第二个选择框中。而使用DOM 的appendChild()方法,就可以将第一个选择框中的选项直接移动到第二个选择框中。我们知道,如果为appendChild()方法insertBefore也可以)传入一个文档中已有的元素,那么就会先从该元素的父节点中移除它,再把它添加到指定的位置。
    var selectbox1 = document.getElementById("selLocations1");
    var selectbox2 = document.getElementById("selLocations2");
    selectbox2.appendChild(selectbox1.options[0]);
  • 而通过之前的add方法我们也可以移动位置
    var selectbox1 = document.getElementById("selLocation1");
    var selectbox2 = document.getElementById("selLocation2");
    selectbox2.add(selectbox1.options[0], undefined);

表单序列化

  • 随着Ajax 的出现,表单序列化已经成为一种常见需求(第21 章将讨论Ajax)。在JavaScript 中,可以利用表单字段的type 属性,连同name 和value 属性一起实现对表单的序列化。
    1. 对表单字段的名称和值进行URL 编码,使用和号(&)分隔。
    2. 不发送禁用的表单字段。
    3. 只发送勾选的复选框和单选按钮。
    4. 不发送type 为”reset”和”button”的按钮。
    5. 多选选择框中的每个选中的值单独一个条目。
    6. 在单击提交按钮提交表单的情况下,也会发送提交按钮;否则,不发送提交按钮。也包括type为”image”的<input>元素。
    7. <select>元素的值,就是选中的<option>元素的value 特性的值。如果<option>元素没有value 特性,则是<option>元素的文本值。
  • 下面是书中给的序列化的代码:
function serialize(form){        
        var parts = [],
            field = null,
            i,
            len,
            j,
            optLen,
            option,
            optValue;

        for (i=0, len=form.elements.length; i < len; i++){
            field = form.elements[i];

            switch(field.type){
                case "select-one":
                case "select-multiple":

                    if (field.name.length){
                        for (j=0, optLen = field.options.length; j < optLen; j++){
                            option = field.options[j];
                            if (option.selected){
                                optValue = "";
                                //在DOM 兼容的浏览器中需要使用hasAttribute()方法,而在IE 中需要使用特性的specified 属性。
                                if (option.hasAttribute){
                                    optValue = (option.hasAttribute("value") ? option.value : option.text);
                                } else {
                                    optValue = (option.attributes["value"].specified ? option.value : option.text);
                                }
                                parts.push(encodeURIComponent(field.name) + "=" + encodeURIComponent(optValue));
                            }
                        }
                    }
                    break;

                case undefined:     //fieldset
                case "file":        //file input
                case "submit":      //submit button
                case "reset":       //reset button
                case "button":      //custom button
                    break;

                case "radio":       //radio button
                case "checkbox":    //checkbox
                    if (!field.checked){
                        break;
                    }
                    /* falls through */

                default:
                    //don't include form fields without names
                    if (field.name.length){
                        parts.push(encodeURIComponent(field.name) + "=" + encodeURIComponent(field.value));
                    }
            }
        }        
        return parts.join("&");
    }

富文本编辑

  • 所谓富文本编辑就是我们可以在页面中建立一个文本编辑区,我们可以对其中的文本进行样式的设置,然后可以保持原样的将这段文本放置入页面的其他位置。故富文本编辑,又称为WYSIWYG(What You See Is What You Get,所见即所得)。就像博客下面的回复编辑区一样,我们可以改变字体大小或者颜色,提交后就可以保持原样地显示在回复区。
  • 这个功能可以说非常有用,那么该如何实现呢?这一技术的本质,就是在页面中嵌入一个包含空HTML 页面的iframe。通过设置designMode 属性,这个空白的HTML 页面可以被编辑,而编辑对象则是该页面<body>元素的HTML 代码。designMode 属性有两个可能的值:”off”(默认值)和”on”。在设置为”on”时(需在页面加载完成后设置,所以一般写在load事件处理程序内部),整个文档都会变得可以编辑(显示插入符号),然后就可以像使用字处理软件一样,通过键盘将文本内容加粗变成斜体,等等。
  • 此外还可以使用contenteditable属性,可以把contenteditable 属性应用给页面中的任何元素,然后用户立即就可以编辑该元素。还可以在JS脚本中设置contenteditable属性,contenteditable属性有三个可能的值:true”表示打开、“false”表示关闭,“inherit”表示从父元素那里继承(因为可以在contenteditable 元素中创建或删除元素)。但是我实验下来发现这个contenteditable属性好像不能用,所以这里也不再放例子了。
<div class="editable" id="richedit" contenteditable></div>

操作富文本

  • 与富文本编辑器交互的主要方式,就是使用document.execCommand()。这个方法可以对文档执行预定义的命令,而且可以应用大多数格式。可以为document.execCommand()方法传递3 个参数:要执行的命令名称表示浏览器是否应该为当前命令提供用户界面的一个布尔值(为了兼容,这个参数应该始终为false)和执行命令必须的一个值(如果不需要值,则传递null)。以下是命令表:
    这里写图片描述
    这里写图片描述
  • 下面是书中给的一个简单的富文本编辑例子:
<!DOCTYPE html>
<html>
<head>
    <title>Rich Text Editing Example</title>
</head>
<body>
    <form method="post" action="javascript:alert('Form submitted!')">
        <div id="divSimple">
            <input type="button" value="Bold">
            <input type="button" value="Italic">
            <input type="button" value="Underline">
            <input type="button" value="Indent">
            <input type="button" value="Outdent">
            <input type="button" value="Copy">
            <input type="button" value="Cut">
            <!--复制在IE中好用,在其他浏览器中可能被禁用了-->
            <input type="button" value="Paste">
        </div>
        <div id="divComplex">
            <input type="button" value="Create Link" id="btnCreateLink"> 
            <input type="button" value="Change Font Size" id="btnChangeFontSize">   
            <input type="button" value="Highlight Text" id="btnHighlight">       
            <input type="button" value="Get HTML" id="btnGetHtml">
            <input type="button" value="Get Selected Text" id="btnGetSelected">
        </div>
        <div id="divQuery">Is the current selection: 
            <input type="button" value="Bold"> 
            <input type="button" value="Italic">          
            <input type="button" value="Underline">
        </div>
        <iframe name="richedit" style="height: 500px; width: 1000px" src="blank.htm"></iframe>
        <input type="hidden" name="comments" value="">
        <input type="submit" value="Submit Form">
   </form>
    <script type="text/javascript">
        (function(){
            window.addEventListener("load", function(){
                frames["richedit"].document.designMode = "on";
            });
            var simple = document.getElementById("divSimple");
            var complex = document.getElementById("divComplex");
            var queryDiv = document.getElementById("divQuery");

            document.forms[0].addEventListener("submit", function(event){
                event.target.elements["comments"].value = frames["richedit"].document.body.innerHTML;
            });
            simple.addEventListener("click", function(event){
                var target = event.target;
                if (target.type == "button"){
                    frames["richedit"].document.execCommand(target.value.toLowerCase(), false, null);
                }

            });

            complex.addEventListener("click", function(event){
                var target = event.target;
                switch(target.id){
                    case "btnGetHtml":
                        alert(frames["richedit"].document.body.innerHTML);
                        break;
                    case "btnCreateLink":
                        var link = prompt("What link?", "http://www.wrox.com");
                        if (link){
                            frames["richedit"].document.execCommand("createlink", false, link);
                        }
                        break;
                    case "btnChangeFontSize":
                        var size = prompt("What size? (1-7)", "7");
                        if (size){
                            frames["richedit"].document.execCommand("fontsize", false, parseInt(size,10));
                        }
                        break;
                    case "btnGetSelected":
                        if (frames["richedit"].getSelection){
                            alert(frames["richedit"].getSelection().toString());
                        } else if (frames["richedit"].document.selection){
                            alert(frames["richedit"].document.selection.createRange().text);
                        }
                        break;
                    case "btnHighlight":
                        if (frames["richedit"].getSelection){
                            var selection = frames["richedit"].getSelection();
                            //get the range representing the selection
                            var range = selection.getRangeAt(0);
                            //highlight the selected text
                            var span = frames["richedit"].document.createElement("span");
                            span.style.backgroundColor = "yellow";
                            range.surroundContents(span);
                        } else if (frames["richedit"].document.selection){
                            var range = frames["richedit"].document.selection.createRange();
                            range.pasteHTML("<span style=\"background-color:yellow\">" + range.htmlText + "</span>");
                        }
                        break;
                }
            });

            queryDiv.addEventListener("click", function(event){
                var target = event.target;
                if (target.type == "button"){
                    alert(frames["richedit"].document.queryCommandState(target.value.toLowerCase(), false, null));
                }
            });
        })();
    </script>
</body>
</html>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值