2013-03-20

增加、減少、移動 table 的列

隨著 HTML5 的推行與發展,互動頁面的數量比以往多,亦更加華麗,
而輔助這些互動功能的語法均需要使用 CSS 及 Javascript
在 Javascript 方面,由於得到 jQuery 的跨瀏覽器語法,在開發 Javascript 新功能時可以不再考慮瀏覽器的問題

可能不少網頁設計者都會遇過類似狀況
輸入文字欄位數量不夠,需要新加入一定數量的輸入文字爛位,但究竟多少才足夠卻不是設計師可以控制
最理想的狀況是由使用者在需要增加資料,增加輸入文字爛位
<table>
    <tbody>
        <tr>
            <td><input type="text"/></td>
            <td><input type="button" value="+"/></td>
            <td><input type="button" value="-"/></td>
            <td><input type="button" value="&#9660;+"/></td>
            <td><input type="button" value="&#9650;"/></td>
            <td><input type="button" value="&#9660;"/></td>
       </tr>
    </tbody>
</table>
以上的 HTML 會以下面的方式顯示
當 + 被按下後,選定的列之下增加一列
當 - 被按下後,選定的列會被移除
當 ▼+ 被按下後,複製選定的列於下一列
當 ▲ 被按下後,選定的列向上移
當 ▼ 被按下後,選定的列向下移
網上有不少使用 jQuery 的教學達成這種操作
而這裡則是使用 DOM 的方式達成這種操作

先利用 Javascript 的 Prototype 補充原來的 Javascript 的不足之處
Prototype Javascript
// DOM 的 Node 有 nextSibling 及 previousSibling 屬性
// 來獲取 Node 的上一個 Node 及 下一個 Node ,當沒有 Node 時,會得到 null
// 但 *Sibling 可能比較難記,可以自行增加 nextNode() 及 previousNode() 功能
// (Java 來說會以功能操作比較熟習)
Node.prototype.nextNode = function(){
    return this.nextSibling;
}
 
Node.prototype.previousNode = function(){
    return this.previousSibling;
}
 
// 但上一個 Node 或下一個 Node 不一定是 Tag
// 對於 DOM Tag 與 Tag 之間的空白字元都是 Node 只是 nodeType 的不同
// Tag 的 nodeType 為 1
Node.prototype.nextTag = function(){
    var node = this.nextNode();
    while (node != null && node.nodeType != 1){
        node = node.nextNode();
    }
    return node;
}

Node.prototype.previousTag = function(){
    var node = this.previousNode();
    while (node != null && node.nodeType != 1){
        node = node.previousNode();
    }
    return node;
}
 
// 獲取第一及最後的 Tag
Node.prototype.firstTag = function(){
    var node = this.firstChild;
    while (node != null && node.nodeType != 1){
        node = node.nextNode();
    }
    return node;
}

Node.prototype.lastTag = function(){
    var node = this.lastChild;
    while (node != null && node.nodeType != 1){
        node = node.previousNode();
    }
    return node;
}
 
// DOM 的 Node 只有 insertBefore 功能卻沒有 insertAfter 功能
// 可以自行編寫 insertAfter 來補足功能
Node.prototype.insertAfter = function(new_node, existing_node){
    if (new_node instanceof Node){
        existing_node.parentNode.insertBefore(new_node, existing_node.nextNode());
    }
}
 
// 讓兩個 DOM Node 的位置交換
Node.prototype.swap = function(node){
    if (node instanceof Node){
    var parent = this.parentNode;
        parent.insertBefore(node.cloneNode(true), this);
        parent.insertBefore(this.cloneNode(true), node);
        parent.removeChild(this);
        parent.removeChild(node);
    }
}
 
// 自編 insertRowBefore 及 insertRowAfter 功能
// 將新的 列 新增至 當前列 的 前 或 後
HTMLTableRowElement.prototype.insertRowBefore = function(row){
    if (row instanceof HTMLTableRowElement){
        this.parentNode.insertBefore(row, this);
    }
}

HTMLTableRowElement.prototype.insertRowAfter = function(row){
    if (row instanceof HTMLTableRowElement){
        this.parentNode.insertAfter(row, this);
    }
}

Javascript
function add_row(input){
    var td = input.parentNode;
    var tr = td.parentNode;
    var tx = tr.parentNode;
    var t_tr = tx.rows[0].cloneNode(true);
    t_tr.removeAttribute('style');
    tr.insertRowAfter(t_tr);
}

function copy_row_input_value(tr1, tr2){
    for (var i = 0; i < tr1.cells.length; i++){
        var element = tr1.cells[i].firstChild;
        if (element instanceof HTMLInputElement){
            if (element.type == 'radio' || element.type == 'checkbox'){
                tr2.cells[i].firstChild.checked = element.checked;
            } else {
                tr2.cells[i].firstChild.value = element.value;
            }
        } else if (element instanceof HTMLSelectElement){
            tr2.cells[i].firstChild.selectedIndex = element.selectedIndex;
        } else if (element instanceof HTMLTextAreaElement){
            tr2.cells[i].firstChild.innerHTML = element.innerHTML;
        }
    }
}

function copy_row(input){
    var td = input.parentNode;
    var tr = td.parentNode;
    var tx = tr.parentNode;
    var t_tr = tx.rows[0].cloneNode(true);
    t_tr.removeAttribute('style');
    tr.insertRowAfter(t_tr);
    copy_row_input_value(tr, t_tr);
}

function delete_row(input){
    var td = input.parentNode;
    var tr = td.parentNode;
    var tx = tr.parentNode;
    if (tx.rows.length <= 2){
        add_row(input);
    }
    tx.removeChild(tr);
}

function move(input, direction){
    var td = input.parentNode;
    var tr = td.parentNode;
    var tx = tr.parentNode;
    var t_tr = tr.cloneNode(true);
    copy_row_input_value(tr, t_tr);
    if (tr != tx.firstTag().nextTag() && direction == 'up'){
        tx.insertBefore(t_tr, tr.previousTag());
        tx.removeChild(tr);
    } else if (tr != tx.lastTag() && direction == 'down'){
        tx.insertAfter(t_tr, tr.nextTag());
        tx.removeChild(tr);
    }
}
 
function move_up(input){
    move(input, 'up');
}

function move_down(input){
    move(input, 'down');
}
HTML
<table>
    <tbody>
        <tr style="display: none;">
            <td><input type="text"/></td>
            <td><input type="button" value="+" onclick="add_row(this);"/></td>
            <td><input type="button" value="-" onclick="delete_row(this);"/></td>
            <td><input type="button" value="&#9660;+" onclick="copy_row(this);"/></td>
            <td><input type="button" value="&#9650;" onclick="move_down(this);"/></td>
            <td><input type="button" value="&#9660;" onclick="move_up(this);"/></td>
        </tr>
        <tr>
            <td><input type="text"/></td>
            <td><input type="button" value="+" onclick="add_row(this);"/></td>
            <td><input type="button" value="-" onclick="delete_row(this);"/></td>
            <td><input type="button" value="&#9660;+" onclick="copy_row(this);"/></td>
            <td><input type="button" value="&#9650;" onclick="move_down(this);"/></td>
            <td><input type="button" value="&#9660;" onclick="move_up(this);"/></td>
        </tr>
    </tbody>
</table>
由於第一列使用 display: none 來隱藏列,因此複製的列需要移除 style 屬性
另外由於 input, select, textarea 等 表單元素 不會複製由用戶輸入的 value, checked, selected, innerHTML 等資料
複製列時需要連同 value, checked, selected, innerHTML 等資料一拼複製才能達至複製效果
以下是示範
可能不少人都覺得這種寫法仍然不夠 jQuery 簡單,結果限制亦很大,例如列的顯示必須完全相同
這種方法的可取之處是不需要使用 id, class 之類的 CSS Selector 達到效果

沒有留言 :

張貼留言