基于面向对象的tab栏crud功能实现

最近刚步入es6,上来就给我整面向对象,梦回java实验。。。

功能需求:

  1. 点击nav栏的各个版块可以切换,同时下面的内容也在跟随变化。
  2. 增加nav的版块功能,点击右侧的加号即可实现。
  3. 删除nav的版块功能,点击版块右上角的x号就可以删除。
  4. 编辑功能。双击nav和下方文本区都可以进行修改。

为什么是面向对象?

实际上,面向对象的好处不用多说,可维护性高,代码复用性强,低耦合......

但是刚刚接触写起来还是挺麻烦的。。。这也是js的第一个面向对象案例。

之前的所有案例都是面向过程的写法,怎么说呢,写过c的懂得都懂,一大串,根本没有模块化。

采用面向对象,就带来一个很棘手的问题,this的指向,接下来每一个迷惑的指向我都会分析一下。


接下来我会分为问题来循序渐进的进行整个过程的梳理

问题一:用class声明类并且完成constructor构造方法

在html中我们用的id选择器给整个tab栏命名的,所以实例化对象我们传进去的也是id。

var tab = new Tab('#tab');

接着我们的构造器constructor(id){ }

我们是在构造器里面获得dom元素的,因为当new的那一刻,就会调用构造器,我们就要获得这些dom元素。

所以你会看到接下来的获取dom元素:

constructor(id) {
            that = this;
            // 通过id获取元素
            this.main = document.querySelector(id);
            this.add = this.main.querySelector('.tabadd');
            // 获取li的父元素ul
            this.ul = this.main.querySelector('.fisrstnav ul')
                // 获取section的父元素 fsection
            this.fsection = this.main.querySelector('.tabscon');

            this.init();
        }

你很好奇,为什么还有that?为什么全都是this?为什么还有init() ?

接下来来到下面的问题

问题二:为什么全都是this?

js中,在类的声明内部,只要是公共的属性和方法,都必须加上this,否则就会报错。

很明显,这里的this是在constructor里面,因此指向实例化对象,就是当前的tab,这样就好理解了。

为什么我要强调this的指向?

关于类中this的指向,大致分为 在构造方法中this指向将要实例化的对象 和 在方法中this指向方法的调用者。

后面有的地方,你需要的不是调用这个方法的元素,而是实例化对象,那么就不能用this,否则会报错的。

这就引出下一个问题

问题三:为什么有that?

实际上这个that,大家也看到了,是在类的constructor的第一句赋值的:

that  = this;

但是这个that的声明 var that; 是在js的第一句,因此是全局变量。它也必须是全局变量,因为后面要很频繁的用它。

之后再用到that的时候,说明我们想要的是实例化对象,即整个tab,而不是指向调用当前方法的这个元素

问题四:init( ) 函数是干嘛的

顾名思义,是用来初始化操作的。

对于这个tab栏,有很多的状态栏要点击,里面有很多的span要点击,有很多的x号,有很多的section要点击,所以我们在constructor里面获取的时候,获取的都是伪数组,因此我们要用for循环给每一个都添加我们想要的事件,比如onclick,并且绑定相应的方法。

通俗来说,就是把for循环的事件绑定封装在了这个函数里面,方便调用。

注意,这个init属于谁呢?当然是属于实例化对象,整个tab,所以其他地方在调用的时候,都是that.init( ) !!!

init() {
            this.updateNode();
            for (var i = 0; i < this.lis.length; i++) {
                this.lis[i].index = i;
                this.lis[i].onclick = this.toggleTab;
                this.removes[i].onclick = this.delTab;
                this.spans[i].ondblclick = this.editTab;
                this.sections[i].ondblclick = this.editTab;
            }
            // 点击加号 执行addtab方法
            this.add.onclick = this.addTab;
        }

问题五:nav栏的切换 toggleTab()

我们用toggleTab( ) 来实现切换功能,本质上就是当前的栏改变样式,并且下面的section的内容也随着变化。

这就要求我们给每一个 li 一个专门的索引号,好比自定义属性。这个在init函数里面就已经实现了。

注意,是谁调用toggleTab( )?  是每一个小li

所以在toggleTab里面,我们利用排他思想,把其他元素的样式给清楚掉,留下我自己。

    // 1. 切换tab栏功能
    toggleTab() {
        that.clearClass(); // 让实例对象调用清除样式 如果是this,就是当前li调用了,显然不对
        this.className = 'liactive';
        that.sections[this.index].className = 'conactive';
    };
    clearClass() { // 
            for (var i = 0; i < this.lis.length; i++) {
                this.lis[i].className = '';
                that.sections[i].className = '';
            }
        }

可以看到,调用sections的是that,因为只有实例化对象tab可以调用sections,相当于父子关系。

然后再通过this.index 获得当前li的索引index值,把当前的liactive和conactive样式给它们。

这里封装clearclass,也就体现了面向对象的封装特性,细节决定成败。

问题六:nav栏的增加 addTab( )

同样的,我们把增加功能封装到addTab( )里面实现。

我们每点击一次右边的加号,就要创建一个新的版块,并且新的板块有liactive和conactive样式。

这要求我们给加号绑定点击事件,然后调用addTab( )。

然后我们写addTab()里面的内容。

首先还是clearclass,把其他元素的样式清掉,确保聚焦在新添加的元素身上。

然后调用insertAdjacentHTML这个API,不知道为什么这么长vscode还不提示?

这个API的好处就是,以往可能是document.createElement(),然后用innerHTML往里面添加内容,但是对于这个案例呢,里面内容比较多,不好添加。而这个API,就可以用字符串的形式,把整个html样式复制过来就可以了。

在复制的同时呢,就已经加上了liactive和conactive。然后采用beforeend的添加方式,放到父元素里面子元素的最后一个。

API的相关解释可以查阅mdn。

最后一句话点睛之笔:that.init( );

为什么呢?

因为当我们添加了新的板块之后,原先的直接在constructor里面获取的lis伪数组length已经不够了!

所以我们要不断地更新这些涉及到增删的元素的数目。

因此引出了我们的新的封装函数updateNode()

     // 因为我们点击删除和添加,是动态的,所以需要一直获得最新的元素个数,对于这样的元素我们放到update函数里面
    updateNode() {
            this.lis = this.main.querySelectorAll('li');
            this.sections = this.main.querySelectorAll('section');
            this.removes = this.main.querySelectorAll('.icon-guanbi');
            this.spans = this.main.querySelectorAll('.fisrstnav li span:first-child');
        }

可以看到,涉及到增加和删除的lis和sections的dom获取,都在updateNode里面。

那怎么实现更新效果呢?

简单啊,把updateNode放在init里面,因为init是给这些lis什么的绑定事件的,他俩放一起正好!

然后addTab()函数结束时,在最后一句写上that.init(),就达到了更新lis等数目的效果!

妙不可言~

接下来的删除原理也就明白了。

问题七:nav栏的删除 delTab( )

当点击nav栏每个板块的右上角的x号,就可以完成删除。

但我们不仅要完成删除功能,还有三个超级无敌细的细节:

1 > 当我们删除当前li之后,前一个li自动变成选定状态。

2 > 当我们删除的 li 不与当前 li 紧挨的时候,不执行上述效果。

3 > 点击x号,不会导致当前版块被选中

对于问题3>,这实际上是一个,不怎么容易被发现的冒泡问题。

x号是一个span,它是 li 的孩子,而 li 也有点击事件,因此你点击x号,自然就会被 li 捕捉,从而当前 li 被选中,所以我们要阻止冒泡:e.stopPropagation();  用的是事件对象e

开始删除工作:

谁调用的delTab()?是x号这个小span,我想获得我要删除的li的序号,怎么做?

没错,还是dom操作,var index = this.parentNode.index 即可。

然后调用remove()方法,就可以删除指定index的元素了。

别忘了!!!调用that.init( ) ,更新当前的数目。

对于问题1>,想的巧一点,既然我们获得了当前的index,我们想对前一个进行操作,就index--;

然后调用前一个的点击事件不就好了吗???

但是总有index < 0 的时候,不加判断就会出错。所以我们用一个&&运算

that.lis[ index ] && that.lis[ index ].click( );

如果存在,就调用click()。

 对于问题2>,想一下,如果删除之后,页面还有选中的,我就不要这个问题1的效果了;否则,我就要。那一句话就可以解决了

if( document.querySelector( '.liactive' ) ) return;

// 3. 删除tab栏功能
    delTab(e) {
        e.stopPropagation(); // 阻止冒泡,防止点击span导致父亲li捕获孩子span的点击事件
        var index = this.parentNode.index;
        // console.log(index);
        that.lis[index].remove();
        that.sections[index].remove();
        that.init(); // 删除之后 更新当前元素个数
        // !!! 当我们删除了选中的li之后,让它的前一个自动处于选定状态,通过调用前一个的手动点击事件
        // 但是这时候有个bug,如果我删除的不与当前选定框相邻,我不希望执行这个效果,所以需要判断一下。
        // 如果删除之后,当前页面有选中的,那就不执行,return就可以;否则就执行
        if (document.querySelector('.liactive')) return;
        index--;
        that.lis[index] && that.lis[index].click();
    };

问题八:nav栏和下方section的内容修改

如果不给input文本输入,那nav栏和下面的section内容都是纯文字,双击只会选中。

所以我们要双击的时候,给它一个input,光标离开的时候,再把input里面的内容给innerHTML。

注意,这里有一个我想不太懂的地方,给了span一个input,就变成它的子元素啦?

editTab() {
        var str = this.innerHTML; // 把原先的文本框内容存下来
        this.innerHTML = '<input type = "text">';
        var input = this.children[0];
        input.value = str;
        input.select(); // 让里面的文字自动处于选定状态
        // 失去光标时,把当前input的value给span的innerhtml即可
        input.onblur = function() {
                this.parentNode.innerHTML = this.value;
            }
            // 按下回车也可以实现把value值给span的innerhtml
        input.onkeyup = function(e) {
            if (e.keyCode === 13) { // 注意 keycode中的c 要大写!!!
                this.blur();
            }
        }
    };

这样整个tab栏的制作就完成了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值