实现效果
- 点击tab栏。可以切换效果;
- 点击+号可以天剑tab项和内容项;
- 点击x号,可以删除当前的tab项和内容项;
- 双击tab项文字或者内容文字,可以修改里面的文字;
用面向对象的思想来写这个tab栏,思路是先把公共的属性和方法抽取出来,写到我们的类里面,再根据类实例化我们的对象,创建各种各样的对象,
抽取对象(增删改查)
- 该对象具有切换功能
- 该对象具有添加功能
- 该对象具有删除功能
- 该对象具有修改功能
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link type="text/css"rel="stylesheet"href="tab.css">
</head>
<body>
<div class="tabsbox" id="tab">
<!--标签-->
<nav class="firstnav">
<ul>
<li class="liactive"><span>测试一</span> <span class="guangbi">×</span></li>
<li><span>测试二</span> <span class="guangbi">×</span></li>
<li><span>测试二</span> <span class="guangbi">×</span></li>
</ul>
<div class="tabadd">
<span>+</span>
</div>
</nav>
<!--内容-->
<div class="tabscon">
<section class="conactive">测试一</section>
<section>测试二</section>
<section>测试三</section>
</div>
</div>
<script src="tab.js"></script>
</body>
</html>
var that;//定义一个全局变量,使后来添加的可以获取到constructor中的元素
class Tab{
constructor(id){
that=this;
this.main=document.querySelector(id);//获取id,
this.add=this.main.querySelector('.tabadd');//获取添加栏
//li的父元素
this.fatherli=this.main.querySelector('.firstnav ul:first-child');
//section的父元素
this.fathersection=this.main.querySelector('.tabscon');
this.init();//调用和init使得this和that指向constructor中的元素
}
//获取所有的li和section和×和巴拉巴拉,这里单独写一个函数来获取,使得每一次操作的时候,都重新获得所有的元素
updateNode(){
this.lis=this.main.querySelectorAll('li');
this.sections=this.main.querySelectorAll('section');
this.remove=this.main.querySelectorAll('.guangbi');
this.spans=this.main.querySelectorAll('.firstnav li span:first-child');
}
//初始化让元素绑定事件
init(){
this.updateNode();//先获得所有的元素
this.add.onclick=this.addTab;//绑定添加事件的函数
for(var i=0;i<this.lis.length;i++){//遍历所有的的顺序
this.lis[i].index=i;//给对应的li增添索引号
//给每一函数绑定点击事件
this.lis[i].onclick=this.toggleTab;
this.remove[i].onclick=this.removeTab;
this.spans[i].ondblclick=this.editTab;
this.sections[i].ondblclick=this.editTab;
}
}
//切换
toggleTab(){
that.clearClass();//先暴力清除之前所有有添加的属性,全部清空
this.className="liactive";//获取当前的对象标签
that.sections[this.index].className="conactive";//这里要调用全局变量that,因为如果使用this的话指向的是调用它的函数及(init)但是我们要的是class里的属性
}
clearClass(){
for(var i=0;i<this.lis.length;i++){//清除,遍历一遍,清除当前有属性的
this.lis[i].className='';
this.sections[i].className='';
}
}
//添加
addTab(){
that.clearClass();//先清除
var li='<li class="liactive"><span>新选项</span> <span class="guangbi">×</span></li>';//定义新标签
var section='<section class="conactive">新内容</section>';//定义新填空栏
that.fathersection.insertAdjacentHTML('beforeend',section);//利用insertAdjacentHTML来将定义的元素插入父元素的后面
that.fatherli.insertAdjacentHTML('beforeend',li);
that.init();//添加完后需要重新返回到init函数中去初始化绑定事件
}
//删除功能
removeTab(e){
e.stopPropagation();//先阻止冒泡,防止点击的时候进行切换
var index=this.parentNode.index;//虽然小叉叉没有序号,但是它的父元素有序号呀
that.lis[index].remove();//把序号赋给每歌li并且调用删除功能
that.init();//执行完操作后进行初始化
index--;//序号减一到前面一个去,当我们删除了选中当前li时,让它的前一个处于选中状态
if(document.querySelector('.licative'))//如果删除的表示当前的标签。即当前标签依然存在,则返回
return;
that.lis[index]&&that.lis[index].click();//当全部删除时无index=-1不存在,就会报错,所以这里设置为当存在index的时候再执行后面的点击。防止手多全删除了报错
}
//修改功能
editTab(){
window.getSelection?window.getSelection().removeAllRanges():document.selection.empty();//手动取消双击选中文字的操作
var str=this.innerHTML;//定义一个str并添加字符串,这里的this指向的是pan
this.innerHTML='<input type="text"/>';//添加的内容,即文本框
var input=this.children[0];//定义input为当前选中的第一个子元素
input.value=str;//把初始的内容赋给value使其显示在文本框里
input.select();//选中当前的文本框
input.onblur=function(){//当对象失去焦点时执行函数
this.parentNode.innerHTML=this.value;//这里的this指向的时input,我们要把新的值赋给span,span是input的父亲所以这里要获取它的父元素
}
input.onkeyup=function(){//绑定一个键盘操作事件,当点击回车时达到同样的效果,手动调用表单失去焦点
var e = event || window.event;
if(e.keyCode===13){
this.onblur();
}
}
}
}
new Tab('#tab');//通过construor来传递id
实现思路:
①切换:
- 通过类名来设置css体现切换效果;
- 每次进行切换前都要进行清除;
②添加功能:
- 创建新的选项卡li和新的内容section;
- 把创建的两个元素追加到对应的父元素(使用insertAdjacemtHTML()可以直接把字符串元素添加到父元素中;
③删除功能:
- 点击×可以删除当前的选项卡和当前的section;
- 叉号是没有索引号的,但是他的父亲有,这个索引号正是我们所需要的
- 点击叉号可以删除这个索引号对应的li和section
- 注意记得阻止冒泡泡
- 当我们删除的不是当前元素,保持当前;
- 当我们全部删除时防止报错,于是判断存在才执行
④修改功能
- 双击选项卡的时候可以实现修改功能
- 双击文字的时候生成一个文本框,把原始的值给文本框,在输入完毕之后再把值赋给span。
- 绑定一个键盘事件
当我们的页面加载的时候,就会调用this.init()这个方法,init再去调用updateNode这个方法;这个方法就会去获取所有的小li和小section,利用for循环给三个小li绑定事件。但是当我们点击添加时会增加小li,此时需要再后面重新执行init函数;此时this.updateNode()重新去获取它的元素,此时就拿到了所有的小li,此时再给所有的小li绑定一个点击事件。这样就可以给后怎加的元素添加了点击的效果。
困难和心得
①一开始把js的引用写在了head当中,导致实例无法获得相应的css样式名称。
1,在head中时,所代表的functions只加载而不执行,执行是在某一事件触发后才开始。
2,在body中时,直接加载并执行
dom没有渲染完自然无法获得相应的元素;
②获取元素的方法和清除的函数要单独写,因为每次增加或删除总是要调用,如果获取元素写在类里面,会导致后来添加的东西没有被算进去。单独写一个获取元素的函数有利于当我们执行每一步操作时可以重新获得所有的元素;
③关于this,因为this指向的是调用它的那个函数,但是我们要拿到的是类里面获取到的元素,所有定义一个全局变量that,可以让后面的函数拿到constructor中的元素;
④考虑索引的序号问题,当它报错的时候就要考虑是否莫得序号了,这样子的话就没有必要执行下面的操作了。我们可以写一个简单的判断是否当前元素存在,不存在我们就returen了
⑤绑定键盘事件的写法,实现手动调用函数
input.onkeyup=function(){//绑定一个键盘操作事件,当点击回车时达到同样的效果,手动调用表单失去焦点
var e = event || window.event;
if(e.keyCode===13){
this.onblur();
后续
后来学到bind的时候,发现这里可以用bind来写
//var that;定义一个全局变量,使后来添加的可以获取到constructor中的元素
class Tab{
constructor(id){
this.main=document.querySelector(id);
this.add=this.main.querySelector('.tabadd');
this.fatherli=this.main.querySelector('.firstnav ul:first-child'); this.fathersection=this.main.querySelector('.tabscon');
this.init();
}
init(){
this.updateNode();
this.add.onclick=this.addTab.(this.add, this);
for(var i=0;i<this.lis.length;i++){
this.lis[i].index=i;
this.lis[i].onclick=this.toggleTab.(this,lis[i],this)
this.remove[i].onclick=this.removeTab.(this.remove[i],this);
this.spans[i].ondblclick=this.editTab;
this.sections[i].ondblclick=this.editTab;
}
toggleTab(that)={}
把全局变量that去掉,利用bind捆绑,并改变this内部的指向。
第一个参数写的是当前的元素要修改我们的类名,函数里面的this没有改变指向;第二个参数是把constructor里面的this当成一个实参传递给我们下面的这个函数,这个函数里面接受过来之后that就是我们constructor里面的this了