1.实现的页面效果
2.JS相关代码实现
(1)实现功能要求
- 增删查改功能
- 双击改tab栏文字内容
- 用JS面向对象的思路
(2)实现步骤
1. 搭建最大的那个框架
先新建一个大类,里面要包含constructor这个构造函数(我们知道,在js当中这个函数是自动执行的)
class Tab {
constructor() {
// 获取元素(获取到文档元素--最大的那个页面)
this.main = document.querySelector(id);
}}
2. 获取到页面之后,搭建大的框架
//增
addTab(){}
//删
removeTab(){}
//切换
toggleTab(){}
//改
editTab(){
这个里面包含了双击修改文字内容的功能
}
3. 添加内容和功能
<main>
<h4>
Js 面向对象 动态添加标签页
</h4>
<div class="tabsbox" id="tab">
<!-- tab 标签 -->
<nav class="fisrstnav">
<ul>
<li class="liactive"><span>测试1</span><span class="iconfont icon-guanbi"></span></li>
<li><span>测试2</span><span class="iconfont icon-guanbi"></span></li>
<li><span>测试3</span><span class="iconfont icon-guanbi"></span></li>
</ul>
<div class="tabadd">
<span>+</span>
</div>
</nav>
<!-- tab 内容 -->
<div class="tabscon">
<section class="conactive">测试1</section>
<section>测试2</section>
<section>测试3</section>
</div>
</div>
</main>
4. 实现tab栏切换改变内容
我们可以想到,想实现切换功能肯定会有点击事件发生,那我们首先肯定是获取元素并绑定事件。我们又可以想到不止这一个功能需要有点击事件,所以我们直接定义一个函数,谁需要谁直接调用
我们定义一个init函数,首先要获取元素(把页面主要框架写着方便梳理),看页面元素,我们需要盒子(#tabsbox,#liactive,#tabscon,#conactive)来实现功能。先获取,注意,这里我们应该写到constructor里面,因为我们需要一执行代码就获取到元素
// li的父元素
this.ul = this.main.querySelector(‘.fisrstnav ul:first-child’);
// section 父元素
this.fsection = this.main.querySelector(‘.tabscon’);
this.lis = this.main.querySelectorAll(‘li’);
this.sections = this.main.querySelectorAll(‘section’);
this.spans = this.main.querySelectorAll(‘.fisrstnav li span:first-child’);
获取到元素之后添加点击事件init(){}
我们想要知道是具体的哪一个选项卡要实现功能,就必须要知道该选项卡的索引号index。直接遍历li
init() {
// init 初始化操作让相关的元素绑定事件
for (var i = 0; i < this.lis.length; i++) {
this.lis[i].index = i;
this.lis[i].onclick = this.toggleTab;
}
}
toggleTab() {
this.className = 'liactive';//点击当前选项卡,获得当前的类名为liactive
that.sections[this.index].className = 'conactive';//当前选项卡对应的底部内容
}
写到这,还不够,因为这样只要点击过,所有的选项卡都会成为选中状态,我们需要利用排他思想,只让当前的选项卡为选中状态
// 清除所有li 和section 的类
clearClass() {
for (var i = 0; i < this.lis.length; i++) {
this.lis[i].className = '';
this.sections[i].className = '';
}
}
然后让 toggleTab()调用这个类
添加一句代码,这里需要注意的是,应该写在最上面,让他最先执行
that.clearClass();
这里为什么用that稍后会指出
5. 接着在末尾增加新的tab栏
添加新的选项卡,我们可以想到给按钮添加点击事件,然后让选项卡追加内容
同样的先获取元素
this.add = this.main.querySelector(‘.tabadd’);
然后添加功能(在这里面比较牛B的是添加内容,不像之前还得一点一点写进去,直接运用一种新的办法insertAdjacentHTML,具体的使用方法可以去查一下)
addTab() {
// (1) 创建li元素和section元素
var random = Math.random();//这个是无关紧要的,为了验证的
var li = '<li class="liactive"><span>新选项卡</span><span class="iconfont icon-guanbi"></span></li>';
var section = '<section class="conactive">测试 ' + random + '</section>';
// (2) 把这两个元素追加到对应的父元素里面
that.ul.insertAdjacentHTML('beforeend', li);
that.fsection.insertAdjacentHTML('beforeend', section);
}
这里又会出现新的问题了,添加之后,因为是后来添加的。所以获取不到他们的新的索引号,那我们就可以想到,动态添加元素,从而让大家都有索引号供我们调用
updateNode() {
this.lis = this.main.querySelectorAll('li');
this.sections = this.main.querySelectorAll('section');
this.spans = this.main.querySelectorAll('.fisrstnav li span:first-child');
}
添加之后调用,在哪调用比较合适呢,我们可以想到让他获取index之前就动态获取元素,所以我们把它添加到init()里面,而且要在最上面,让他最先执行。
然后我们需要在addtab(){}中调用
that.init();
这里又需要注意啦,同样的会出现选定状态不变的情况,所以我们再次调用排他思想那个函数
that.clearClass();
6. 添加删除选项卡
实现删除功能,同理,先获取元素,在添加功能
this.remove = this.main.querySelectorAll(‘.icon-guanbi’);
而且我们需要把他写到动态添加函数里面,随时获取新的index
删除的思路是什么:获取事件之后,我们点击后要删除,获得索引号,那li 也没有索引号,我们便直接获取ul的,也就是他的父亲节点
removeTab(e) {
e.stopPropagation(); // 阻止冒泡 防止触发li 的切换点击事件
var index = this.parentNode.index;
// 根据索引号删除对应的li 和section remove()方法可以直接删除指定的元素
that.lis[index].remove();
that.sections[index].remove();
that.init();
}
我们写到这的时候,会发现,当你删除某个选项卡的时候,其选中状态会发生改变,我们需要设置一个条件语句,来解决这个问题
// 当我们删除的不是选中状态的li 的时候,原来的选中状态li保持不变
if (document.querySelector(‘.liactive’)) return;
// 当我们删除了选中状态的这个li 的时候, 让它的前一个li 处于选定状态
index–;
现在又有新的问题产生了,当我们删除第一个之后index就不应该再减一了。所以我们需要设置条件,在这个项目中,老师利用的是短路特性来解决的这个问题
// 手动调用我们的点击事件 不需要鼠标触发
that.lis[index] && that.lis[index].click();
7. 双击修改选项卡内容
双击禁止选定文字(直接记住固定代码就好了
)
window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
双击后获取到当前内容,然后给它添加一个能输入文字的文本框
var str = this.innerHTML;
this.innerHTML ='<input type="text" />';
var input = this.children[0];
input.value = str;
我们还要实现离开文本框之后,输入的内容传给span,添加离开事件,或者按下回车之后也要传回给span
// 当我们离开文本框就把文本框里面的值给span
input.onblur = function() {
this.parentNode.innerHTML = this.value;
};
// 按下回车也可以把文本框里面的值给span
input.onkeyup = function(e) {
if (e.keyCode === 13) {
// 手动调用表单失去焦点事件 不需要鼠标离开操作
this.blur();
}
}
最后我们还要实现的功能是双击之后文字处于选定状态,可以直接一下子全部改变
input.select(); // 文本框里面的文字处于选定状态
最后的最后,我们还需要实例化,使整个代码调用起来
- 还有一个遗留的小问题:为什么用that 这里that只是this的一个替代品。我们this指向的对象因为不同的调用对象是不同的,这个项目里面的that指代的是实例化的大的this。从而保证指向的对象是正确的。
3.完整代码
var that;
class Tab {
constructor(id) {
// 获取元素
that = this;
this.main = document.querySelector(id);
this.add = this.main.querySelector('.tabadd');
// li的父元素
this.ul = this.main.querySelector('.fisrstnav ul:first-child');
// section 父元素
this.fsection = this.main.querySelector('.tabscon');
this.init();
}
init() {
this.updateNode();
// init 初始化操作让相关的元素绑定事件
this.add.onclick = this.addTab;
for (var i = 0; i < this.lis.length; i++) {
this.lis[i].index = i;
this.lis[i].onclick = this.toggleTab;
this.remove[i].onclick = this.removeTab;
this.spans[i].ondblclick = this.editTab;
this.sections[i].ondblclick = this.editTab;
}
}
// 因为我们动态添加元素 需要从新获取对应的元素
updateNode() {
this.lis = this.main.querySelectorAll('li');
this.sections = this.main.querySelectorAll('section');
this.remove = this.main.querySelectorAll('.icon-guanbi');
this.spans = this.main.querySelectorAll('.fisrstnav li span:first-child');
}
// 1. 切换功能
toggleTab() {
// console.log(this.index);
that.clearClass();
this.className = 'liactive';
that.sections[this.index].className = 'conactive';
}
// 清除所有li 和section 的类
clearClass() {
for (var i = 0; i < this.lis.length; i++) {
this.lis[i].className = '';
this.sections[i].className = '';
}
}
// 2. 添加功能
addTab() {
that.clearClass();
// (1) 创建li元素和section元素
var random = Math.random();
var li = '<li class="liactive"><span>新选项卡</span><span class="iconfont icon-guanbi"></span></li>';
var section = '<section class="conactive">测试 ' + random + '</section>';
// (2) 把这两个元素追加到对应的父元素里面
that.ul.insertAdjacentHTML('beforeend', li);
that.fsection.insertAdjacentHTML('beforeend', section);
that.init();
}
// 3. 删除功能
removeTab(e) {
e.stopPropagation(); // 阻止冒泡 防止触发li 的切换点击事件
var index = this.parentNode.index;
console.log(index);
// 根据索引号删除对应的li 和section remove()方法可以直接删除指定的元素
that.lis[index].remove();
that.sections[index].remove();
that.init();
// 当我们删除的不是选中状态的li 的时候,原来的选中状态li保持不变
if (document.querySelector('.liactive')) return;
// 当我们删除了选中状态的这个li 的时候, 让它的前一个li 处于选定状态
index--;
// 手动调用我们的点击事件 不需要鼠标触发
that.lis[index] && that.lis[index].click();
}
// 4. 修改功能
editTab() {
var str = this.innerHTML;
// 双击禁止选定文字
window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
// alert(11);
this.innerHTML = '<input type="text" />';
var input = this.children[0];
input.value = str;
input.select(); // 文本框里面的文字处于选定状态
// 当我们离开文本框就把文本框里面的值给span
input.onblur = function() {
this.parentNode.innerHTML = this.value;
};
// 按下回车也可以把文本框里面的值给span
input.onkeyup = function(e) {
if (e.keyCode === 13) {
// 手动调用表单失去焦点事件 不需要鼠标离开操作
this.blur();
}
}
}
}
new Tab('#tab');