[JavaScript] Object-oriented tab bar page switching case

We have used native js to realize the paging switching effect of the Tab bar before. This is a process-oriented way. For details, see [JavaScript] Application Case of Custom Attributes - Tab Bar Paging Switching . Today we will use an object-oriented approach . The way to re-implement the tab bar page switching.

Two programming ideas:

  1. Process-oriented POP (Process-oriented programming): Analyze the steps needed to solve the problem, and then use functions to implement the steps in order.
  2. Object-oriented OOP (Object-oriented programming): decompose the transaction into individual objects, and then divide the work between the objects. Divide problems by object functions, not steps.

Let’s take a look at what effect the tab bar page switching should achieve:

  1. Click a Tab bar to achieve the switching effect;
  2. Click the "+" button to add a new Tab column;
  3. Click the "x" button to delete the current Tab column;
  4. Double-click a Tab column or Tab column content to edit it.

We can extract a large object, which is the tab object. The functional modules it has include switch function, add function, delete function and edit function . The parameter that needs to be passed in is the element that needs to be toggled by tab bar, and we pass in its id value .
So we can extract such a class :

class Tab {
    
    
    constructor(id) {
    
    
    	//获取元素
        this.main = document.getElementById(id);
    }
    //切换功能
    toggleTab(){
    
    }
    //添加功能
    addTab(){
    
    }
    //删除功能
    removeTab(){
    
    }
    //编辑功能
    editTab(){
    
    }
}

First build the page structure:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- 引入样式文件 -->
    <link rel="stylesheet" href="style.css">
</head>

<body>
    <div class="tabsBox" id="tab">
        <div class="tab_list">
            <ul>
                <li class="current"><span>Tab1</span><span class="guanbi-btn">x</span></li>
                <li><span>Tab2</span><span class="guanbi-btn">x</span></li>
                <li><span>Tab3</span><span class="guanbi-btn">x</span></li>
            </ul>
            <div class="tabAdd">
                <span>+</span>
            </div>
        </div>
        <div class="tab_con">
            <section class="item">Tab1内容</section>
            <section>Tab2内容</section>
            <section>Tab3内容</section>
        </div>
    </div>
    <!-- 引入js文件 -->
    <script src="tab.js"></script>
</body>

</html>
* {
    
    
    margin: 0;
    padding: 0;
}

li {
    
    
    list-style: none;
}

.tabsBox {
    
    
    width: 600px;
    margin: 50px auto;
}

.tab_list {
    
    
    height: 30px;
    border: 1px solid rgb(182, 50, 50);
}

.tab_list li {
    
    
    float: left;
    /* width: 100px; */
    height: 30px;
    padding: 0 20px;
    line-height: 30px;
    text-align: center;
    cursor: pointer;
    position: relative;
    border-right: 1px solid rgb(182, 50, 50);
}

.guanbi-btn {
    
    
    position: absolute;
    right: 0;
    height: 12px;
    width: 12px;
    line-height: 6px;
    border-bottom-left-radius: 12px;
    font-size: 4px;
    background-color: #333;
    color: #fff;
}

.tabAdd span {
    
    
    float: left;
    height: 15px;
    width: 15px;
    line-height: 15px;
    text-align: center;
    border: 1px solid rgb(182, 50, 50);
    margin: 6px 0 0 6px;
    cursor: pointer;
}

.tab_list .current {
    
    
    border-bottom: 1px solid #fff;
}

.tab_con {
    
    
    border: 1px solid rgb(182, 50, 50);
    border-top: 0;
    height: 300px;
    padding: 10px;
    box-sizing: border-box;
}

.tab_con section {
    
    
    display: none;
    width: 100%;
    height: 100%;
}

.tab_con .item {
    
    
    display: block;
}

.tab_list input {
    
    
    width: 80px;
    height: 20px;
}

.tab_con input {
    
    
    width: 70%;
    height: 60%;
}

At this point, we only need one line of code to create a tab object, and pass in the id of the element to be the switching effect of the tab bar, as follows:

var mytab = new Tab('tab');

Then implement the functional modules one by one. During the implementation process, special attention should be paid to the pointing of this . This process requires a very clear understanding of the pointing of this, following the principle of who calls who points to whom, and this in the class points to the specific instantiated object .

1. Preparation

  1. Before that, we first need to obtain various element objects, which we write in the constructor. The constructor is executed as soon as the page loads.
class Tab {
    
    
    constructor(id) {
    
    
    	//获取元素
        this.main = document.getElementById(id);
        this.ul=this.main.querySelector('.tab_list ul:first-child');
        this.lis = this.ul.querySelectorAll('li');
        this.spans=this.ul.querySelectorAll('li span:first-child');
        this.con=this.main.querySelector('.tab_con');
        this.sections=this.con.querySelectorAll('section');
        this.guanbiBtns=this.main.querySelectorAll('.tab_list .guanbi-btn');
        this.add=this.main.querySelector('.tabAdd');
    }
    //切换功能
    toggleTab(){
    
    }
    //添加功能
    addTab(){
    
    }
    //删除功能
    removeTab(){
    
    }
    //编辑功能
    editTab(){
    
    }
}
var mytab = new Tab('tab');
  1. Then create an initialization function to bind events to the element. If you want the page to load and the event has been bound, then you need to execute this initialization function in the constructor.
    According to the analysis of functions, bind corresponding events to them.
    In addition, we need to add an index attribute to li to facilitate the section to display the corresponding content. We mentioned it when we implemented it with native js, so you can check it out. [JavaScript] Application Case of Custom Attributes——Tab Bar Page Switching
class Tab {
    
    
    constructor(id) {
    
    
    	//获取元素
        this.main = document.getElementById(id);
        this.ul=this.main.querySelector('.tab_list ul:first-child');
        this.lis = this.ul.querySelectorAll('li');
        this.spans=this.ul.querySelectorAll('li span:first-child');
        this.con=this.main.querySelector('.tab_con');
        this.sections=this.con.querySelectorAll('section');
        this.guanbiBtns=this.main.querySelectorAll('.tab_list .guanbi-btn');
        this.add=this.main.querySelector('.tabAdd');
        this.init();
    }
    //初始化函数
    init(){
    
    
        //重新绑定事件
        for(var i = 0;i<this.lis.length; i++){
    
    
            this.lis[i].dataIndex=i;
            this.lis[i].addEventListener('click', this.toggleTab);
            this.spans[i].addEventListener('dblclick',this.editTab);
            this.sections[i].addEventListener('dblclick',this.editTab);
            this.guanbiBtns[i].addEventListener('click',this.removeTab);
        }
        this.add.addEventListener('click',this.addTab);
    }
    //切换功能
    toggleTab(){
    
    }
    //添加功能
    addTab(){
    
    }
    //删除功能
    removeTab(){
    
    }
    //编辑功能
    editTab(){
    
    }
}
var mytab = new Tab('tab');
  1. Define a global variable that to store the largest instantiated object. Because the point of this changes with the caller during the implementation process, and we sometimes need to use the instantiated object itself.
var that;
class Tab {
    
    
    constructor(id) {
    
    
    	that = this;
    	//获取元素
        this.main = document.getElementById(id);
        this.ul=this.main.querySelector('.tab_list ul:first-child');
        this.lis = this.ul.querySelectorAll('li');
        this.spans=this.ul.querySelectorAll('li span:first-child');
        this.con=this.main.querySelector('.tab_con');
        this.sections=this.con.querySelectorAll('section');
        this.guanbiBtns=this.main.querySelectorAll('.tab_list .guanbi-btn');
        this.add=this.main.querySelector('.tabAdd');
        this.init();
    }
    ......//省略一下
}

Second, realize the switching function

  1. The switching function is very simple, that is, the change of the style. Click on a tab column, and its className is changed to 'current', just pay attention to the exclusivity.
    Note that all this points in this function should be this.lis[i] , because this.lis[i] is the caller of the toggleTab function. this.lis[i].addEventListener('click', this.toggleTab); If you want to use the lis or sections of the object, it must be that.xxx.
//切换功能
toggleTab(){
    
    
    for(var i=0;i<that.lis.length;i++){
    
    
        that.lis[i].className='';
        that.sections[i].className='';
    }
    this.className='current';
    that.sections[this.dataIndex].className='item';
}
  1. We often use the code to clear all element styles in exclusivity, so a new function clearStyle() is encapsulated here , and the encapsulated function can change the original that to this, that.clearStyle(); this is the tab object pointed to ( always pay attention to the pointing problem of this!!! ), just call it where it needs to be used:
//切换功能
toggleTab(){
    
    
    that.clearStyle();
    this.className='current';
    that.sections[this.dataIndex].className='item';
}
//清除样式 排他思想
clearStyle(){
    
    
    for(var i=0;i<this.lis.length;i++){
    
    
        this.lis[i].className='';
        this.sections[i].className='';
    }
}

The effect is as follows:Please add a picture description

3. Realize adding function

Create a new element and append it to the parent element.

//添加功能
addTab(){
    
    
    //直接用字符串形式创建li和section,创建新的Tab页时,tab页停留在创建的新元素上,之前所停留的页面要清除样式。
    that.clearStyle();
    var li = '<li class="current"><span>新建Tab页</span><span class="guanbi-btn">x</span></li>';
    var section = '<section class="item">新建Tab页内容</section>';
    //把这两个元素追加到对应的父元素里面
    that.ul.insertAdjacentHTML('beforeend',li);
    that.con.insertAdjacentHTML('beforeend',section);
}

The effect is as follows:
insert image description here

  • You can see that there is a small bug, the newly added element has no switching effect. This is because before executing this function, all li and sections have been obtained, and switching events have been bound to them, but the newly added elements have not added switching events. At this point, we need to reacquire all the li and sections, so that we can obtain the newly added li and sections, and then re-bind events for them. We encapsulate these operations into an update function . After adding the element, call the update function once to update the element.
  • Because li and section, guanbiBtns, and spans are all corresponding, they need to be updated together.
  • At this point, you can optimize the code, take out the acquisition and binding of li, section, guanbiBtns, and spans from the original place and put them in the update function, and then call the update function in the initialization function as the initial acquisition element.
var that;
class Tab {
    
    
    //传入要做Tab栏的元素的id
    constructor(id){
    
    
        that=this;
        //获取元素
        this.main = document.getElementById(id);
        this.ul=this.main.querySelector('.tab_list ul:first-child');
        this.con=this.main.querySelector('.tab_con');
        this.add=this.main.querySelector('.tabAdd');
        this.init();
    }
    //初始化函数
    init(){
    
    
        this.update();
        //绑定事件
        this.add.addEventListener('click',this.addTab);
    }
    //切换功能
    toggleTab(){
    
    
        that.clearStyle();
        this.className='current';
        that.sections[this.dataIndex].className='item';
    }
    //添加功能
    addTab(){
    
    
        //直接用字符串形式创建li和section,创建新的Tab页时,tab页停留在创建的新元素上,之前所停留的页面要清除样式。
        that.clearStyle();
        var li = '<li class="current"><span>新建Tab页</span><span class="guanbi-btn">x</span></li>';
        var section = '<section class="item">新建Tab页内容</section>';
        //把这两个元素追加到对应的父元素里面
        that.ul.insertAdjacentHTML('beforeend',li);
        that.con.insertAdjacentHTML('beforeend',section);
        that.update();
    }
    //删除功能
    removeTab(){
    
    }
    //编辑功能
    editTab(){
    
    }
    //清除样式 排他思想
    clearStyle(){
    
    
        for(var i=0;i<this.lis.length;i++){
    
    
            this.lis[i].className='';
            this.sections[i].className='';
        }
    }
    //更新li和section
    update(){
    
    
        //重新获取lis和sections和guanbiBtns
        this.lis = this.ul.querySelectorAll('li');
        this.sections=this.con.querySelectorAll('section');
        this.guanbiBtns=this.main.querySelectorAll('.tab_list .guanbi-btn');
        this.spans=this.ul.querySelectorAll('li span:first-child');

        //重新绑定事件
        for(var i = 0;i<this.lis.length; i++){
    
    
            this.lis[i].dataIndex=i;
            this.lis[i].addEventListener('click', this.toggleTab);
            this.spans[i].addEventListener('dblclick',this.editTab);
            this.sections[i].addEventListener('dblclick',this.editTab);
            this.guanbiBtns[i].addEventListener('click',this.removeTab);
        }
    }
}

The effect is as follows:
insert image description here

4. Realize the delete function

Record the index value of the current click, delete the corresponding li and section respectively, and update the element after deletion.

//删除功能
removeTab(){
    
    
    var index=this.parentNode.dataIndex;
    //移除对应的li元素
    this.parentNode.remove();
    //移除对应的section元素
    that.sections[index].remove();
    that.update();
}

The implementation effect is as follows:
insert image description here
You can see that there are two bugs:

  1. When the deletion is not the currently selected tab bar, the click event of the span will bubble up to the parent element li. When the delete button is clicked, the element is selected and the element is deleted. The tab column that was originally selected is no longer selected, and the tab column that is not selected after deletion is removed. In order to prevent the click event of the parent element li from being triggered, it is necessary to prevent bubbling. The ideal state should be to delete elements normally, and the originally selected tab bar remains selected.
  2. When the currently selected tab column is deleted, there will be no selected tab column after deletion. The ideal state should be that his previous one is selected, but we should pay attention to the judgment threshold.
//删除功能
    removeTab(e){
    
    
        //阻止冒泡
        e.stopPropagation();
        var index=this.parentNode.dataIndex;
        //移除对应的li元素
        this.parentNode.remove();
        //移除对应的section元素
        that.sections[index].remove();
        that.update();
        //如果删除的是当前没有选定状态的li,那么删除他自己后,保持原来的li仍处于选定状态
        if(document.querySelector('.current')) return;
        //如果删除的是当前处于选定状态的li且处于选定状态的li不在第一个,那么删除后让他的前一个li处于选定状态
        if(index > 0){
    
    
            that.lis[index - 1].click();
        } else {
    
    
        //如果删除的是当前处于选定状态的li且处于选定状态的li在第一个,那么删除后让他的后一个li处于选定状态
            if(that.lis.length == 0){
    
    
                return;
            }else{
    
    
                that.lis[index].click();
            }
        }
    }

The effect is as follows:
insert image description here

5. Realize the editing function

  1. After double-clicking, insert a text box and allow editing, which is equivalent to changing the original innerHTML into a text box. The text box here has been styled in css in advance.
//编辑功能
editTab(){
    
    
    // 双击禁止选中文字
    window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
    this.innerHTML='<input type="text" />';
}
  1. Then let the value in the text box be equal to the original tab column title and be in the selected state. If the user wants to edit it, he can directly input it to overwrite it.
//编辑功能
editTab(){
    
    
    // 双击禁止选定文字
    window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
    //原来的tab栏标题
    var str=this.innerHTML;
    this.innerHTML='<input type="text" />';
    var input = this.children[0];
    input.value = str;
    //里面的文字处于被选中状态,同时也自动获得了焦点
    input.select();
}
  1. After losing focus, give the value in the text box to the original span/section element, just change the innerHTML to only the value, and delete the text box skillfully.
//编辑功能
editTab(){
    
    
    // 双击禁止选定文字
    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;
    //里面的文字处于被选中状态,同时也自动获得了焦点
    input.select();
    //失去焦点后,把文本框里面的值给span
    input.addEventListener('blur', function () {
    
    
        this.parentNode.innerHTML = this.value;
    })
}
  1. Pressing the Enter key can also give the value in the text box to the span/section element, which is equivalent to calling the blur function once.
//编辑功能
editTab(){
    
    
    // 双击禁止选定文字
    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;
    //里面的文字处于被选中状态,同时也自动获得了焦点
    input.select();
    //失去焦点后,把文本框里面的值给span
    input.addEventListener('blur', function () {
    
    
        this.parentNode.innerHTML = this.value;
    })
    //按下回车键也可以把文本框里面的值给span
    input.addEventListener('keyup', function (e) {
    
    
        if (e.keyCode == 13) {
    
    
            this.blur();
        }
    })
}

The effect is as follows:
insert image description here

Summarize

So far, we have achieved all the effects. Through this case, we can gain an in-depth understanding of object-oriented programming ideas , and at the same time, we can truly understand the pointing problem of this .

Guess you like

Origin blog.csdn.net/weixin_43790653/article/details/123513375