原文地址:http://www.onlamp.com/pub/a/onlamp/2007/11/20/advanced-javascript-iii.html

JavaScript高级编程I:http://blog.csdn.net/mydeman/archive/2007/08/20/1751479.aspx
    JavaScript高级编程II:http://blog.csdn.net/mydeman/archive/2007/08/23/1755760.aspx

在本系列的终结篇中,我们继续学习另外一个有用的JavaScript案例,主要是通过DOM随意操作和重写HTML页面。和以前一样,本文中使用JavaScript应该不经修改就可以在当前所有主要的浏览器上使用。
        使用你的鼠标进行魔术变换(Magic Mutating with Your Mouse)
         在创建表单时,你有时可能希望根据某些次级条件改变输入域的类型。例如,假设你正在编写一个图书馆的搜索界面。如果通过作者搜索,你可能想使用一个标准文本框。如果通过图书类型(比方说精装本或普通),你可能想提供一个下拉列表;以及如果使用图书内容搜索,就提供一个大文本域。试试下面的,你就会比较清楚了: <script language=javascript> // Copies attributes from one node (src) to another (dest). Modify this to suit // your needs. For example, you may not want to retain the value when a new // node is generated. function CopyAttributes(src, dest) { var i; dest.id = src.id; dest.name = src.name; } // Performs the mutation on the node with id given by the argument searchbox. // opt specifies the type of mutation to make. function UpdateSearchField(opt, searchbox) { var inputbox = document.getElementById(searchbox); // Based on search type, choose which element to create and // set its attributes accordingly. if (opt == "content") { // make a textarea var el = document.createElement("TEXTAREA"); CopyAttributes(inputbox, el); el.cols = 40; el.rows = 4; } else if (opt == "author") { // make a standard text box var el = document.createElement("INPUT"); CopyAttributes(inputbox, el); el.type = "text"; el.size = 20; } else if (opt == "binding") { // make a drop-down list var el = document.createElement("SELECT"); CopyAttributes(inputbox, el); el.size = 1; // str stores the text for the drop-down selector. Different // drop-down lists could be provided by simply switching in // different arrays for str. var i; var str = new Array("Hardcover", "Paperback", "Magazine"); for (i = 0; i < str.length; i++) { var opt = document.createElement("OPTION"); opt.appendChild(document.createTextNode(str[i])); opt.setAttribute("value", str[i]); if (inputbox.value == str[i]) // check for selected item opt.setAttribute("selected", "selected"); el.appendChild(opt); } } // Use the DOM function replaceChild to put in the newly created // node. inputbox.parentNode.replaceChild(el, inputbox); } </script>

Search for: AuthorBindingContent
      JavaScript通过一个回调函数实现这个功能,只要改变搜索类型的值,回调函数就会被触发。

// Copies attributes from one node (src) to another (dest).  Modify this to suit
// your needs.  For example, you may not want to retain the value when a new
// node is generated.
function CopyAttributes(src, dest)
{
var i;
dest.id = src.id;
dest.name = src.name;
}
// Performs the mutation on the node with id given by the argument searchbox.
// opt specifies the type of mutation to make.
function UpdateSearchField(opt, searchbox)
{
var inputbox = document.getElementById(searchbox);
// Based on search type, choose which element to create and
// set its attributes accordingly.
if (opt == 'content') { // make a textarea
var el = document.createElement("TEXTAREA");
CopyAttributes(inputbox, el);
el.cols = 40;
el.rows = 4;
}
else if (opt == 'author') { // make a standard text box
var el = document.createElement("INPUT");
CopyAttributes(inputbox, el);
el.type = 'text';
el.size = 20;
}
else if (opt == 'binding') { // make a drop-down list
var el = document.createElement("SELECT");
CopyAttributes(inputbox, el);
el.size = 1;
// str stores the text for the drop-down selector.  Different
// drop-down lists could be provided by simply switching in
// different arrays for str.
var i;
var str = new Array('Hardcover', 'Paperback', 'Magazine');
for (i = 0; i < str.length; i++) {
var opt = document.createElement("OPTION");
opt.appendChild(document.createTextNode(str[i]));
opt.setAttribute('value', str[i]);
if (inputbox.value == str[i]) // check for selected item
opt.setAttribute('selected', 'selected');
el.appendChild(opt);
}
}
// Use the DOM function replaceChild to put in the newly created
// node.
inputbox.parentNode.replaceChild(el, inputbox);
}

表单的布局代码为:

<form οnsubmit="return false;" action="">
Search for:
<select οnchange="UpdateSearchField(this.value, 'searchbox');" name="searchtype">
<option value="author" selected="selected">Author</option>
<option value="binding">Binding</option>
<option value="content">Content</option>
</select>
<input id="searchbox" type="text" value="" name="searchbox" size="20" />
<input type="submit" value="Go!" name="submit" />
</form>

这里使用技术其实很简单。无论何时用户改变 searchtype的值(通过从下拉列表中选择), onchange属性就会调用我们的JavaScript函数, UpdateSearchField,将刚刚选择的值和表单中搜索框的 id作为参数。该函数创建一段HTML代码,然后使用新代码替换当前的搜索框。很显著,我们正在随意重写网页的部分代码。
        假设读者已经熟悉HTML DOM。如果不熟悉,可以参考 官方文档。 UpdateSearchField首先观察选中域的值,然后创建一个新的合适类型的DOM元素节点。通常情况下, 你可能使用节点类型作为输入参数,或者在表中查询,但是为了简单其间,我们基于传递搜索域的值。现在解释一下 createElement。该函数在特定的document( document,在这里指当前document)的DOM树中创建一个新的空节点。例如,创建一个INPUT节点等同于HTML的 <INPUT></INPUT>。注意当创建时,它还并没有放在文档的任何地方。可以想象为,它被存储屏幕外的任何地方,在浏览器窗口之外,在它被实际添加到当前文档的某个地方之前,允许我们根据需要创建和修改它。
        然后,我们复制input(它的 id已经作为传入参数--该节点最终会被我们的新节点替代)的部分或者所有属性到新节点中,此时用到了助手函数 CopyAttributes。你可能需要改变这个函数以适应你的需求。例如,你可能想要复制对象类或者对象值。复制以后,节点的属性就设置好了。
       对于 SELECT元素,我们就需要多一点工作了,需要为它创建 OPTION。这个在一个循环中完成,和创建其他元素的方式一样,使用 createElement和 createTextNode。后面的工作和前面一样,只不过不是创建HTML标签,而是创建一个包含纯文本的DOM节点。它是包含在HTML的OPTION标签之间的文本。然后,使用DOM函数 appendChild将这些option插入到SELECT标签中间。如果一步步地看for循环中的过程,这个过程可能更加容易理解。

循环次数 生成的HTML
0 <SELECT>
</SELECT>
1 <SELECT>
<OPTION value="HardCover">HardCover</OPTION>
</SELECT>
2 <SELECT>
<OPTION value="HardCover">HardCover</OPTION>
<OPTION value="Paperback">Paperback</OPTION>
</SELECT>
3 <SELECT>
<OPTION value="HardCover">HardCover</OPTION>
<OPTION value="Paperback">Paperback</OPTION>
<OPTION value="Magazine">Magazine</OPTION>
</SELECT>

最后,我们新的searchbox节点终于创建完毕,我们需要将它放在页面的某个位置。同时需要删除现在的searchbox。很方便地,replaceChild函数就是用来满足我们需要的,使用我们创建的新节点替换已存在的节点(以及它的子节点,如果有的话)。这样,我们创建的新HTML就替换了已有的idsearchbox的HTML代码块。至此,大功告成。
      当然,这种技巧并不局限于搜索表单中的INPUT框,实际上通过页面上某些事件的触发,你可以将任何元素变为任何其他的元素。增加一点想象力,许多有趣和令人印象深刻效果就可以通过该技术实现。

动态表格(Dynamic Tables)

在用户界面上经常期望用户可以输入不确定函数的数据。例如,当填写订单时,用户会一行输入一条记录。有了JavaScript,我们再也不用担心是否为订单预留了足够的控件,让用户根据需要添加和删除行就可以了。下面是一个即时的演示,在上个例子的基础上更近一步。

<script language=javascript> // Reads the quantity and price columns in the form and computes the // totals and grand total, filling these in. function UpdateTotals(table_id) { var numrows = document.getElementById(table_id).rows.length - 1; // don't count the header row! var i, totalcost = 0.00; for (i = 1; i <= numrows; i++) { // Compute total for each row var q = parseInt(document.getElementById('quant_' + i).value); var price = parseFloat(document.getElementById('price_' + i).value); var cost; if (!q || !price) cost = 0.00; else cost = q * price; var total = document.getElementById('total_' + i); total.value = '$' + cost; totalcost = totalcost + cost; // Keep running grand total } var total = document.getElementById('total'); total.value = '$' + totalcost; } // Appends a row to the given table, at the bottom of the table. function AppendRow(table_id) { var row = document.getElementById(table_id).rows.item(1); // 1st row var newid = row.parentNode.rows.length; // Since this includes the header row, we don't need to add one var newrow = row.cloneNode(true); rowrenumber(newrow, newid); row.parentNode.appendChild(newrow); // Attach to table // Clear out data from new row. var curnode = document.getElementById('catno_' + newid); curnode.value = ""; curnode.tabIndex = newid; curnode = document.getElementById('descr_' + newid); curnode.value = ""; curnode = document.getElementById('quant_' + newid); curnode.value = ""; curnode = document.getElementById('price_' + newid); curnode.value = ""; curnode = document.getElementById('total_' + newid); curnode.value = ""; curnode = document.getElementById('delete_' + newid); curnode.innerHTML = "X"; curnode = document.getElementById('delete_1'); // Really only need this when newid = 2 curnode.innerHTML = "X"; } // Given a tr node and row number (newid), this iterates over the row in the // DOM tree, changing the id attribute to refer to the new row number. function rowrenumber(newrow, newid) { var curnode = newrow.firstChild; // td node while (curnode) { var curitem = curnode.firstChild; // input node (or whatever) while (curitem) { if (curitem.id) { // replace row number in id var idx = 0; var spl = curitem.id.split('_'); var baseid = spl[0]; curitem.id = baseid + '_' + newid; if (curitem.name) curitem.name = baseid + '_' + newid; if (baseid == 'catno') curitem.tabIndex = newid; } curitem = curitem.nextSibling; } curnode = curnode.nextSibling; } } // Give a node within a row of the table (one level down from the td node), // this deletes that row, renumbers the other rows accordingly, updates // the Grand Total, and hides the delete button if there is only one row // left. function DeleteRow(el) { var row = el.parentNode.parentNode; // tr node var rownum = row.rowIndex; // row to delete var tbody = row.parentNode; // tbody node var numrows = tbody.rows.length - 1; // don't count header row! if (numrows == 1) // can't delete when only one row left return false; var node = row; tbody.removeChild(node); var newid = -1; // Loop through tr nodes and renumber - only rows numbered // higher than the row we just deleted need renumbering. row = tbody.firstChild; while (row) { if (row.tagName == 'TR') { newid++; if (newid >= rownum) rowrenumber(row, newid); } row = row.nextSibling; } if (numrows == 2) { // 2 rows before deleting - only 1 left now, so 'hide' delete button var delbutton = document.getElementById('delete_1'); delbutton.innerHTML = ' '; } UpdateTotals(tbody.parentNode.id); // Grand Total may change after a delete, so update it } </script>

Widgets "R" Us Order Form

Catalog # Description Quantity Unit Price Total  
Grand Total:  

要在最后增加一行,只需要Append Row按钮。要删除一行,就点击行尾的红色“X”。当只剩下最后一行时就不能删除了。另外注意,只要你输入了数量和价格或者删除了一行,TotalGrand Total的值就会更新。这些是如何实现的呢?首先,我们使用HTML创建一个初始的表格。注意,初始时必须至少有一行。

<form action="" method="post">
<table>
<tr>
<td colspan="3">
<table id="catalog" border="1">
<tr>
<th>Catalog #</th>
<th>Description</th>
<th>Quantity</th>
<th>Unit Price</th>
<th>Total</th>
<th> </th>
</tr>
<tr>
<td>
<input id="catno_1" name="catno_1" tabindex="1" type="text" value="" />
</td>
<td>
<input id="descr_1" name="descr_1" type="text" value="" />
</td>
<td>
<input id="quant_1" name="quant_1" type="text" value="" οnkeyup="UpdateTotals('catalog');" />
</td>
<td>
<input id="price_1" name="price_1" type="text" value="" οnkeyup="UpdateTotals('catalog');" />
</td>
<td>
<input id="total_1" name="total_1" type="text" value="" disabled="disabled" />
</td>
<td>
<span id="delete_1" style="color:red; cursor: pointer;" οnclick="DeleteRow(this);"> </span>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>
<input type="submit" name="submit" value="Append Row" οnclick="AppendRow('catalog'); return false;"/>
</td>
<td align="right">
<b>Grand Total:</b>
<input id="total" name="total" type="text" value="" disabled="disabled" />
</td>
<td>
&nbsp;
</td>
</tr>
</table>
</form>

这只是一个由表单包围的标准HTML表格,其每一个单元格都包含一个供用户输入数据的input标签。注意,每一个input标签的nameid值的都是以“_1”结尾,删除按钮的id亦是如此。它用来保证标签的唯一性,同时表明标签属于哪一行——当前就是第一行。例如,第3行中price列的nameid就是price_3,等等。Total和Grand Total域是不可用的,因为它们是计算列,是只读的。所有的任务都有三个JavaScript函数完成:AppendRow与同名按钮相关联,DeleteRow与删除X关联,以及UpdateTotal,只要在Quantity或者Price域按下某个键就会被触发,计算总和。首先来看第一个函数:

// Reads the quantity and price columns in the form and computes the
// totals and grand total, filling these in.
function UpdateTotals(table_id)
{
var numrows = document.getElementById(table_id).rows.length - 1;  // don't count the header row!
var i, totalcost = 0.00;
for (i = 1; i <= numrows; i++) {
// Compute total for each row
var q = parseInt(document.getElementById('quant_' + i).value);
var price = parseFloat(document.getElementById('price_' + i).value);
var cost;
if (!q || !price)
cost = 0.00;
else
cost = q * price;
var total = document.getElementById('total_' + i);
total.value = '$' + cost;
totalcost = totalcost + cost;  // Keep running grand total
}
var total = document.getElementById('total');
total.value = '$' + totalcost;
}

这个函数使用表格id作为唯一的输入参数,计算表单的总数和总数之和。它首先计算行数,然后在其上循环。注意,第1行(下标为0)是表格的头,所以我们跳过它从第2行(下标为1)开始。它读取每一行的数量和价格信息,这里就利用了这些列id的命名规则;接着,两者值都不为0,则将其相乘计算总数。计算结果保存到当前行的Total列中。总数之和也被计算出来放在Grand Total域中。注意,尽管它们处于不可用状态,我们仍然可以使用JavaScript函数对其进行赋值。现在,我们来看AppendRow函数:

// Appends a row to the given table, at the bottom of the table.
function AppendRow(table_id)
{
var row = document.getElementById(table_id).rows.item(1);  // 1st row
var newid = row.parentNode.rows.length;  // Since this includes the header row, we don't need to add one
var newrow = row.cloneNode(true);
rowrenumber(newrow, newid);
row.parentNode.appendChild(newrow);      // Attach to table
// Clear out data from new row.
var curnode = document.getElementById('catno_' + newid);
curnode.value = "";
curnode.tabIndex = newid;
curnode = document.getElementById('descr_' + newid);
curnode.value = "";
curnode = document.getElementById('quant_' + newid);
curnode.value = "";
curnode = document.getElementById('price_' + newid);
curnode.value = "";
curnode = document.getElementById('total_' + newid);
curnode.value = "";
curnode = document.getElementById('delete_' + newid);
curnode.innerHTML = "X";
curnode = document.getElementById('delete_1');  // Really only need this when newid = 2
curnode.innerHTML = "X";
}

再次,表格的id是唯一的输入参数。我们获取DOM树中表格的第一个tr的句柄放在变量row中,然后检查其父节点以获取表格中总行数。接着,我们使用DOM函数cloneNode创建一个当前行的拷贝。这个函数的输入参数表示我们最好还是包含所有子节点,所以我们获得整个行的拷贝,而不是一个空的tr节点。助手函数,rowrenumber,用来根据相应的行数(它是表单可输入行的总数加1)修改所有id。DOM函数appendChild将新行添加到表格中。最后,我们把输入域,因为它们复制于第一行——我们只想复制结构,而不复制数据。X被放在第1行的删除列中,新行中也是(当只剩下一行时它就被空白替代,这些用户就不会尝试删除了)。我们来简单看一下重新编号id的函数:

// Given a tr node and row number (newid), this iterates over the row in the
// DOM tree, changing the id attribute to refer to the new row number.
function rowrenumber(newrow, newid)
{
var curnode = newrow.firstChild;      // td node
while (curnode) {
var curitem = curnode.firstChild;   // input node (or whatever)
while (curitem) {
if (curitem.id) {  // replace row number in id
var idx = 0;
var spl = curitem.id.split('_');
var baseid = spl[0];
curitem.id = baseid + '_' + newid;
if (curitem.name)
curitem.name = baseid + '_' + newid;
if (baseid == 'catno')
curitem.tabIndex = newid;
}
curitem = curitem.nextSibling;
}
curnode = curnode.nextSibling;
}
}

这个从以变量newrow传入的tr节点开始遍历DOM树。它完成两个级别的遍历,查找所有没有id的域。我们只做两个级别深入(例如,for循环的两个嵌套),因为我们知道在表格中这些id在什么地方。第一级是td标签,第二级是input标签,也就是我们的所有id所在的地方。因为我们对id采用了特别的命名方{type}_{rownumber},现在我们就可以利用它,使用JavaScript的split函数将字符串分开,然后使用新的行号替换旧的。我们同时更新name域,如果存在的话,以及第一列的tabIndex

表单的最后一部分是删除行的函数:

// Give a node within a row of the table (one level down from the td node),
// this deletes that row, renumbers the other rows accordingly, updates
// the Grand Total, and hides the delete button if there is only one row
// left.
function DeleteRow(el)
{
var row = el.parentNode.parentNode;   // tr node
var rownum = row.rowIndex;            // row to delete
var tbody = row.parentNode;           // tbody node
var numrows = tbody.rows.length - 1;  // don't count header row!
if (numrows == 1)                     // can't delete when only one row left
return false;
var node = row;
tbody.removeChild(node);
var newid = -1;
// Loop through tr nodes and renumber - only rows numbered
// higher than the row we just deleted need renumbering.
row = tbody.firstChild;
while (row) {
if (row.tagName == 'TR') {
newid++;
if (newid >= rownum)
rowrenumber(row, newid);
}
row = row.nextSibling;
}
if (numrows == 2) {  // 2 rows before deleting - only 1 left now, so 'hide' delete button
var delbutton = document.getElementById('delete_1');
delbutton.innerHTML = ' ';
}
UpdateTotals(tbody.parentNode.id);     // Grand Total may change after a delete, so update it
}

在HTML中,这个函数在 span标签中被调用,以 this作为输入参数。因此进入该函数, el就是被点击的 span标签的指针,并且我们必须向上走两级找到DOM树中的 tr节点。我们也可以使用 this.parentNode.parentNode作为输入参数。首先,我们必须保证至少有两行,因为只剩下一行时是不允许删除的。然后,我们使用DOM函数 removeChild删除行节点。注意,它是作为父元素 tbody节点的方法被调用的。
     接下来,我们遍历表格树在每一个 tr节点上停止。在被删除行以后的行,如果有的话,都需要重新编号,因为它们行号已经减少了1(例如,删除行4意味着行5变成行4,行6变成行5,等等,而行1、2和3保持不变)。我们使用方便的 rowrenumber函数完成这个任务,这个函数在前面已经讨论过。然后,我们停下来检查是不是只剩下一行(也就是说,在删除的行前要有两行)。如果是这样,我们使用空白替换X来隐藏删除按钮,以使用户不会尝试删除它(但是如果用户不知何故地要删除,什么都不会发生,因为在上面我们已经对这种情况做了检查)。最后,删除一行意味着 Grand Total的值可能改变,除非删除行为空,所以我们调用 UpdateTotals保证计算结果保持一致。
      这可能是到现在为止我们看到的最复杂的例子了,但是如果你将它拆分为不同的组成部分,你会发现其实非常简单。这个例子大量利用了DOM函数和DOM树,并且很有希望能让你对按照自己的意愿动态操作HTML页面有一个初步认识。作为练习,尝试为每一行增加一列,该列包含一个 Insert Row按钮,点击按钮可以在当前行之前插入一空白行。提示:应该只需对 AppendRow函数做一下简单调整就可以实现。

Elusive Text

    在这个最后的例子中,仍然是使用DOM,我们将学习一个简单的方法,用来在输入域中显示临时的文本,这个文本在输入域不完全为空时消失。这个方法可以用来节省空间,例如,直接把输入域的标题放在输入框内而不是左边和上面。下面就是一个可以运行的例子:
<script language=javascript> function getElementAbsPosX(el) { var dx = 0; if (el.offsetParent) { dx = el.offsetLeft + 8; while (el = el.offsetParent) { dx += el.offsetLeft; } } return dx; } function getElementAbsPosY(el) { var dy = 0; if (el.offsetParent) { dy = el.offsetTop + el.offsetHeight / 2; while (el = el.offsetParent) { dy += el.offsetTop; } } return dy; } // Look for previous INPUT tag and move cursor there. function ChangeFocus(el) { while (el.tagName != 'INPUT') el = el.previousSibling; el.focus(); } // Turn on or off help text depending on content of INPUT field. function UpdateHelpText(el) { var label = el; while (label.tagName != 'DIV') label = label.nextSibling; if (el.value == 'undefined' || el.value == '') { // Field is empty; show text label.style.left = getElementAbsPosX(el) + 'px'; label.style.top = (getElementAbsPosY(el) - 7) + 'px'; label.style.visibility = 'visible'; } else { // Field is not empty; hide text if (label) label.style.visibility = 'hidden'; } } </script> .helptext { position: absolute; color: #999999; z-index: 10; font-size: small; overflow: hidden; }

Enter Login Name
Enter Password

<script language=JavaScript type=text/javascript> UpdateHelpText(document.getElementsByName('login')[0]); UpdateHelpText(document.getElementsByName('password')[0]); </script>
    创建这个简单的页面的HTML代码如下所示:

<style type="text/css">
.helptext {
position: absolute;
color: #999999;
z-index: 10;
font-size: small;
overflow: hidden;
}
</style>
<form action="" method="post">
<input type="text" name="login" value="" οnkeyup="UpdateHelpText(this);" οnmοuseup="UpdateHelpText(this);" />
<div class="helptext" id="label_login" οnclick="ChangeFocus(this);">
Enter Login Name
</div>
&nbsp;&nbsp;
<input type="password" name="password" value="" οnkeyup="UpdateHelpText(this);" οnmοuseup="UpdateHelpText(this);" />
<div class="helptext" id="label_password" οnclick="ChangeFocus(this);">
Enter Password
</div>
&nbsp;&nbsp;
<input type="submit" name="submit" value="Login" οnclick="return false;" />
</form>
<script type="text/javascript" language="JavaScript">
UpdateHelpText(document.getElementsByName('login')[0]);
UpdateHelpText(document.getElementsByName('password')[0]);
</script>

首先我们为浮动文本定义CSS样式。绝对定位用来让文本位于页面上其他元素的最上层(这里是输入框)。同样, z-index设为high用来保证文本出现在最上层,而不会隐藏到其他元素后面。overflow设置为hidden,因为我们不想在文本周围出现滚动条。如果它不能正好符合空白,将会被截断。
   这个表单是一个标准的表单,和函数UpdateHelpText相关连,在输入域中有键被按下或释放,或者鼠标被释放时。这个函数,如我们将要看到的,根据输入区域是否为空来隐藏或者显示文本。因此,只要我们开始在输入框中键入信息,浮动文本就会消失。当我们完全删除它的内容是,它又会魔术般重现。我们在表单之后就调用这个函数,初始化文本并把它正确定位在表单内。在每一个input标签之后的是div标签,包含了文本,利用了我们上面创建的helptext CSS类。注意,因为我们设置z-index比较高使文本在最上层,现在就有了一个问题,点击文本时就选中了这个文本而不是移动光标到了输入区域,而后者可能正是用户想要的。为了修正这个问题,在鼠标点击帮助文本时我们调用一个函数ChangeFocus。下面我们看一下这两个JavaScript函数:

// Look for previous INPUT tag and move cursor there.
function ChangeFocus(el)
{
while (el.tagName != 'INPUT')
el = el.previousSibling;
el.focus();
}
// Turn on or off help text depending on content of INPUT field.
function UpdateHelpText(el)
{
var label = el;
while (label.tagName != 'DIV')
label = label.nextSibling;
if (el.value == '') {  // Field is empty; show text
label.style.left = getElementAbsPosX(el) + 'px';
label.style.top = (getElementAbsPosY(el) - 7) + 'px';
label.style.visibility = 'visible';
}
else { // Field is not empty; hide text
if (label)
label.style.visibility = 'hidden';
}
}

ChangeFocus相当简单。它从当前节点开始(div标签),在DOM树中回溯直到找到第一个input标签。在input标签上调用focus将文本光标送到输入域,就好像用户直接点击了input区域。结果就是点击浮动文本就好像直接点击了输入区域。注意,我们可以使用id将div标签和相应的input标签连接起来——例如,input标签的idlogindividlogin_label,我们就可以很容易从一个节点找到另外一个节点——但是,我们使用HTML页面结构的知识来找到节点。如果我们使用id,那么关于页面结构的知识和假设就没有必要了,因此对于一般的应用程序它可能不失为一种较好的方法。
    UpdateHelpText从input标签开始寻找div标签,和上面方法一样。如果input域包含文本,浮动文本使用visibility CSS属性隐藏。否则就是设为了visible显示文本。每次调用这个函数时,文本都被正好放在input域内部。因此,如果你改变浏览器文本大小或者放大倍率,例如,如果文本没有在合适的位置,只需点击input框或者按下一个键就会把文本重新定位到合适的位置。定位函数getElementAbsPosX和getElementAbsPosY获取input元素的以像素为单位的绝对位置,使得浮动文本可以准确定位。关于它们如何工作以及源代码,可以参考本系列的第一篇文章。
 
总结(Summary)

在这一部分,我们探讨了一些使用JavaScript操作DOM的技巧。通过这些技巧,开发者可以修改HTML页面的任何部分,可以增加原来页面没有的内容,删除内容,甚至可以四处拖拽。这些例子只不过是浅尝辄止,一点想象力就可以实现。有一点很重要,需要指出,尽管现在多数浏览器都允许重写计算机内存中的HTML页面,如果你做了过多的更改,事情可能变得有点使人不太愉快,有些浏览器对这种情况的处理比较好。特别地,像尝试使用回调函数创建新节点的情况在输入变量上可能有些不太容易把握,这种情况在IE和Mozilla家族的浏览器之间的处理方法就大相径庭。我希望你已经发现这几篇文章的意义,也希望他们可以激发您的灵感,使用一小段的高级JavaScript让你网站更加生动有趣。

JavaScript高级编程 III相关推荐

  1. JavaScript 高级编程(二)

    JavaScript 高级编程(二) BOM 一套操作浏览器的API. 常见对象 window: 代表整个浏览器窗口 注意: window是BOM中的一个对象, 并且是一个顶级的对象(全局) Navi ...

  2. javascript高级编程教程,javascript基础入门案例

    谁有比较好的javascript视频教程 李炎恢的javascript教程,在verycd上可以下载. 结合<javascript高级程序设计>学习,应该会比较好,他这个教程就是参考了&l ...

  3. 【读书笔记】JavaScript高级编程(二)

    2019独角兽企业重金招聘Python工程师标准>>> 书中第3章 基本概念摘要(一) 3.3 变量 使用var操作符定义的变量将成为定义该变量的作用域中的局部变量.也就是说,如果在 ...

  4. javascript高级编程学习笔记(二)——继承

    2019独角兽企业重金招聘Python工程师标准>>> 写读书笔记的好处在于加深记忆,前一篇总结了编程中创建的对象的几种方式,以及常用的方式,这一篇总结实现继承的方式: 1.对象冒充 ...

  5. JavaScript高级编程设计(第三版)——第二章:在html中使用javaScript

    系列文章目录 第三章:基本概念 目录 系列文章目录 前言 一.javaScript是什么? 1.有两种引入方式 1.1 嵌入式 1.2 外部引入 1.3 noscript标签 标签 1.4文档模式 2 ...

  6. JavaScript高级编程设计(第三版)——第四章:变量作用域和内存问题

    系列文章目录 第二章:在html中使用javaScript 第三章:基本概念 第四章:变量作用域和内存问题 第五章:引用类型 目录 系列文章目录 前言 一.基本数据类型和引用类型的值? 1.数据类型 ...

  7. JavaScript高级编程设计(第三版)——第三章:基本概念

    系列文章目录 第二章:在html中使用javaScript 第三章:基本概念 第四章:变量作用域和内存问题 目录 系列文章目录 前言 一.语法 1.标识符 2.关键字和保留字 二.数据类型 1.nul ...

  8. JavaScript高级编程(一)——基本概念

    语法 关键字与保留字 变量 ECMAScript的变量是松散类型的,即每个变量仅仅只是一个用于保存值的占位符而已: var  message;     定义变量但未初始化但变量,会保存一个特殊但值-- ...

  9. 《JavaScript高级编程》HTML中的JavaScript

    本章节主要介绍了一些JavaScript的标签属性和历史遗留问题: 历史遗留问题: 其中历史遗留问题主要包括: 1.XHTML和HTML的关系: 2.关于有无支持JavaScript的浏览器表情问题, ...

最新文章

  1. 一种无需留坑为页面动态添加View方案
  2. if(a==1 a==2 a==3),为true,你敢信???
  3. 4.12Python数据处理篇之Matplotlib系列(十二)---绘图风格的介绍
  4. MKNetWorkKit打印URL
  5. python抓取股票数据_Python股票处理之一_获取国内股票数据
  6. C#-利用ZPL语言完毕条形码的生成和打印
  7. ASP.NET Core Authentication and Authorization
  8. java stopself_然后,即使我停止了服务,Context.startForegroundService()也没有调用Service.startForeground()...
  9. 字符设备驱动高级篇4——设备类(自动创建和删除设备文件)相关代码分析
  10. onSaveInstanceState() 和 onRestoreInstanceState()
  11. C#查找指定窗口的子窗口的句柄
  12. Android中使用Ant编译打包
  13. 计算机课程布置作业,计算机是如何工作的(教案)
  14. 对Map集合排序,先对value降序,value相同的情况下,key升序
  15. 高等数学—常见三角函数
  16. nowcoder 鹏
  17. 全网最详细,宿主机ping虚拟机的主机名失败,但ping虚拟机的ip成功
  18. Hive中如何统计用户三个月或者以上的行为数据
  19. 第二次上机作业 大连理工大学
  20. 【BZOJ 2243】染色

热门文章

  1. 解析dump的几种方式
  2. 联想拯救者Y7000/Y7000P EFI
  3. Python实现基于动态时间规整的股市交易策略测试
  4. 自己动手写操作系统--搭建保护模式下的运行环境:bochs下安装freedos
  5. 云服务器 文件管理,云服务器文件管理工具
  6. MapGuide 6.5、MapGuide Open Source 和MGEnterprise2007区别
  7. 每日文献:2018-01-29
  8. 在windows上部署IIS web服务
  9. 大数据毕设项目 深度学习火焰检测识别 python opencv
  10. 【组网工程】cisco packet tracer 路由器组网