本人为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之前需要打草稿,写代码之前也如此。
刚开始很纠结如何生成有层级结构的HTML代码,最后发现可以使用设计模式中的组合模式来完成这一功能。具体点就是将li节点看作叶子节点,ul节点看错作容器。那么就有一下限制:
只有ul节点可以插入li节点
li节点需要插入子节点,就需要创建一个ul子节点,让子节点去插入li节点
ul与它的li子节点level相同
如果当前节点需要处理一个header,就需要作出以下判断:
判断自己为ul节点还是li节点
如果为ul节点,且待处理的header的level与自己相同,那么就直接生成一个li节点并插入;如果待处理的header的level比自己大,那么就找到子节点,交给子节点去处理。
如果为li节点,且待处理的header的level比自己大,那么就取li节点的ul节点,交给ul节点去处理
一直向下去处理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>
输出结构如下:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。