JavaScript生成TOC

本人爲Javascript菜鳥,有問題請指正...前端

TOC目錄

最近用Flask搭建我的博客在製做文章顯示頁面時,須要在前端頁面生成TOC。就打算本身寫一個JavaScript腳本。算法

須要生成的目標HTML代碼以下:設計模式

<ul>
    <li><a href="#TOC1">標題1</a></li>
    <li><a href="#TOC2">標題2</a>
        <ul>
            <li><a href="#TOC2.1">標題2.1</a></li>
            <li><a href="#TOC2.1">標題2.2</a>
                <ul>
                    <li><a href="#TOC2.2.1">標題2.2.1</a></li>
                </ul>
            </li>
        </ul>
    </li>
    ...
</ul>

在看了JavaScript權威指南這本書後,發現其中雖然有例子生成TOC,可是並無顯示層級。不過,其中有一個記錄TOC各個錨點序號的好方法:數組

# 使用一個數組報錯<h1到h6標題出現的次數
var sectionNumbers = [0,0,0,0,0,0]
# 取各級標題的level,如<h1>的level爲1
var level = parseInt(header.tagName.charAt(1));
# 當處理一個標籤後,計算加1
sectionNumbers[level-1]++
# 而後去當前序號 好比序號爲2.1.1
sectionNumber = sectionNumbers.slice(0,level).join('.')

分析算法邏輯

吹牛B以前須要打草稿,寫代碼以前也如此。app

剛開始很糾結如何生成有層級結構的HTML代碼,最後發現可使用設計模式中的組合模式來完成這一功能。具體點就是將li節點看做葉子節點,ul節點看錯做容器。那麼就有一下限制:post

  1. 只有ul節點能夠插入li節點this

  2. li節點須要插入子節點,就須要建立一個ul子節點,讓子節點去插入li節點spa

  3. ul與它的li子節點level相同設計

若是當前節點須要處理一個header,就須要做出如下判斷:3d

  1. 判斷本身爲ul節點仍是li節點

  2. 若是爲ul節點,且待處理的header的level與本身相同,那麼就直接生成一個li節點並插入;若是待處理的header的level比本身大,那麼就找到子節點,交給子節點去處理。

  3. 若是爲li節點,且待處理的header的level比本身大,那麼就取li節點的ul節點,交給ul節點去處理

  4. 一直向下去處理header,直到插入成功

代碼實現

爲了儘可能減小代碼的污染,使用Fucntion.call(args)方式了動態的給節點添加屬性:

var sectionNumbers = [0, 0, 0, 0, 0, 0];

function Toc() {
    this.headers = $(this).find(':header');
    var ul = document.createElement('ul');
    this.toc = TocObj.call(ul, 1);
    for(var i = 0; i < this.headers.length; i++) {
        // if(i > 1) break;
        this.toc.add(this.headers[i]);
    }
    console.log(this.toc);
}

function TocObj(level) {
    this.level = level;
    this.num = 0;
    this.add = function(header) {
        var flag = this.tagName == 'UL'; // ul節點和li節點處理header的方式不經過
        var level = parseInt(header.tagName.charAt(1));
        if(flag) { // 只有ul節點才能插入li
            if(this.num == 0 || level == this.level) {
                var link = document.createElement('a');
                link.href = '';
                link.innerHTML = header.innerHTML;
                sectionNumbers[level-1]++;
                for(var i = level; i < sectionNumbers.length; i++) sectionNumbers[i] = 0;
                link.href = "#TOC" + sectionNumbers.slice(0, level).join('.');
                var li = document.createElement('li');
                li.insertBefore(link, li.firstChild);
                this.num++;
                this.appendChild(TocObj.call(li, level));
            } else if(level > this.level){
                if(this.num == 0) { 
                    throw new Error("level error 1");  
                }
                var lastChild = this.lastChild;
                lastChild.add(header); // 讓ul節點的li節點去處理header
            }
        } else { // li節點讓它的ul子節點去插入li
            if(level == this.level) {
                throw new Error("level error 2");
            } else if(level > this.level){ 
                if(this.num == 0) { // 沒有ul子節點,就建立一個
                    var ul = document.createElement('ul');
                    this.appendChild(ul);
                    this.num++;
                    TocObj.call(ul, level).add(header); // 注意設置level
                } else { 
                    var lastChild = this.lastChild;
                    lastChild.add(header); //讓ul節點去處理這個header
                }
            }
        }
    }
    return this;
}

var toc = document.getElementsByClassName('post')[0];
Toc.call(toc);

效果展現

對於有如下結構的HTML代碼:

<div class="col-md-9 post">
    <h1>標題1</h1>
    <h2>標題1.1</h2>
    <h2>標題1.2</h2>
    <h1>標題2</h1>
    <h2>標題2.1</h2>
    <h3>標題2.1.1</h3>
    <h1>標題3</h1>
    <h1>標題4</h1>
    <h1>標題5</h1>
</div>

輸出結構以下:

clipboard.png

相關文章
相關標籤/搜索