zoukankan      html  css  js  c++  java
  • 选项卡页

    目标

    选项卡用于显示已经打开过的页面.在这些页面之间切换.

    制作js插件类,实现基础的选项卡页功能.选项卡的显示,关闭,移动,选项卡页的缓存.

    使用document.createDocumentFragment缓存已打开页面,替代iframe方案

    图示1

    图示2

    html结构

    由选项卡容器(.cachetabs)和显示容器两部分,主要功能实现在选项卡,显示容器只是加载页面.

    选项卡容器html结构如下.由按钮和选项卡导航区域组成,按钮含前进,后退,关闭按钮组.选项卡框是个带横向滚动条的div

     1 <div class="cachetabs" id="cachetabs1">
     2   <a class="cachetabs-left"></a>
     3   <nav class="cachetabs-navbox"><div class="cachetabs-nav"></div></nav>
     4   <a class="cachetabs-right"></a>
     5   <span class="cachetabs-menutitle">功能</span>
     6   <div class="cachetabs-menugroup">
     7     <span class="cachetabs-goto-active">定位当前页</span>
     8     <span class="cachetabs-close-all">关闭全部</span>
     9     <span class="cachetabs-close-other">关闭其它</span>
    10   </div>
    11 </div>

    功能特点

    1. 加载新页面:缓存当前页面后,加载新页面,同时增加一个选项卡.如果选项卡标题有相同的,则在选项卡标题加上(n)
    2. 选项卡容器上加上主题类,可使用不同颜色主题.变化部位在于活动选项卡颜色和底边框颜色
    3. 导航按钮:选项卡超出可视范围后,使用前进后退按钮滚动选项卡框
    4. 点击选项卡时,如果靠近选项卡框的两端,则调整该选项卡到中间位置
    5. 关闭所有选项卡.关闭除活动选项卡外的.定位当前活动选项卡,当其不在可视范围内时.
    6. 当前活动页面不缓存,当页面从非活动转活动时,其对应缓存会删除
    7. 缓存页面使用document.createDocumentFragment,加入其中的DOM会从当前文档中脱离.用以替代经典的iframe方法,
    8. 经过测试,一个填写过的表单页面在放入文档片段对象之后,再取出来时,其状态不变.

    使用

    // 调用方式
    // 配置
    let tabscfg = {
      // 显示容器的ID
      screenId: 'cachetabs_mainbox',
      // 选项卡容器的ID
      cachetabsId: 'cachetabs1'
    };
    // 实例化
    let cachetabs = new $.cacheTabs(tabscfg);
    // 载入页面 html:页面dom,menutitle:选项卡标题
    cachetabs.load(html, tabtitle)

    类与样式

    // 缓存组件
    // 此缓存页面插件多用于AJAX载入片段页时.是一个显示新页面或者已缓存页面的构架.是一个iframe的替代解决方案.
    // 主要作用是,在一个容器中操作缓存和显示文档.每当要显示一个文档时,先将容器中当前的文档缓存到片段中,然后
    // 再显示新文档.这个被缓存的文档它的状态不变.将再次显示它时,从缓存中调出来.
    // 需要引用: JQ, JsExtFun.js
    $.extend({
        // 创建对象 let cachetabs = new $.cacheTabs(config);
        // {screenId:'显示内容的容器ID',cachetabsId:'选项卡容器ID'}
        cacheTabs: function (config)
        {
            /*=================*
             * init config
             *=================*/
            let self = this;
            if (!config) throw '必须传入配置对象';
            // cfg 
            let cfg = {};
            // 内容显示框JQ对象
            cfg.screenJQ = $('#' + config.screenId);
            // 选项卡框JQ对象
            cfg.tabsJQ = $('#' + config.cachetabsId);
            // 活动选项卡类名
            cfg.activeCls = 'active';
            // 选项卡与缓存键关联属性名
            cfg.cacheKey = 'cacheId';
    
            /*====================*
             * 方法 public
             *====================*/
            // 载入页面:缓存当前容器中的页面.将新页面显示在容器中.增加一个新选项卡
            self.load = function (doms, title, closeE)
            {
                // 选项卡容器
                let navJQ = cfg.tabsJQ.find('.cachetabs-nav');
                // 如果已经存在相同title的页面,将其命名为title(n),n>=1
                if (navJQ.find('.cachetabs-tab').length > 0)
                {
                    title = titleExist(title, navJQ);
                }
                // 选项卡为空时,当前无页面,无需缓存.
                if (navJQ.find('.cachetabs-tab').length > 0)
                {
                    // 缓存当前DOM
                    let cacheId = domToCache();
                    // 找到当前活动的选项卡,去掉活动状态,添加缓存id,关联选项卡与缓存
                    navJQ.find('.' + cfg.activeCls).removeClass(cfg.activeCls).attr(cfg.cacheKey, cacheId);
                }
    
                // 新增页面的选项卡
                let tab = '<label class="cachetabs-tab {0}" title="{1}">{1}<a class="cachetabs-tabclose" title="关闭">×</a></label>';
                tab = String.Format(tab, cfg.activeCls, title);
    
                // 显示容器加载新页
                cfg.screenJQ.html(doms);
                // 选项卡页加入新选项卡
                navJQ.append(tab);
                // 绑定新tab的点击事件与关闭事件
                bindEventForLabelBtn(navJQ.find('.' + cfg.activeCls));
                bindEventForCloseBtn(navJQ.find('.' + cfg.activeCls), closeE);
                // 选项卡框滚动条移动到最后
                navScroller(1);
            }
           
            /*====================*
             * 方法 private
             *====================*/
            // 选项卡重复标题检查,如果已经存在相同title的,将其命名为title(n),n>=1
            let titleExist = function (title, navJQ)
            {
                let i = 1;
                let copytitle = title;
                while (true)
                {
                    if (navJQ.find('.cachetabs-tab[title="' + copytitle + '"]').length > 0)
                    {
                        let copyindex = copytitle.lastIndexOf('(');
                        if (copyindex > 0)
                            copytitle = copytitle.substring(0, copyindex);
                        copytitle = String.Format('{0}({1})', copytitle, i);
                        i++;
                        continue;
                    }
                    break;
                }
                return copytitle;
            }
            // 将当前页面添加到缓存 ,将对应选项卡
            let domToCache = function ()
            {
                // 取出当前容器中的页面节点
                let activeDoms = cfg.screenJQ.contents();
                // 添加进缓存
                let cacheId = store.add(activeDoms);
                //console.log('当前页面放入缓存区成功!cacheId=' + cacheId + '内容:' + activeDoms);
                return cacheId;
            }
            // 将缓存页面载入到显示容器中
            let cacheToDom = function (cacheId)
            {
                let doms = store.get(cacheId);
                if (doms == null)
                {
                    //console.log('缓存页面cacheId无效,cacheId:' + cacheId);
                    return;
                }
                //console.log('缓存页面已经载入,cacheId:' + cacheId);
                cfg.screenJQ.html(doms);
            }
            // 调整选项卡框的滚动条值,使用选项卡显示在合适的位置上
            // len:滚动距离,>0 : 右滚此距离, <0 : 左滚, 0 : 滚动到最左, 1 : 到最右,
            //              'left': 左滚默认距离, 'right': 右滚默认距离
            let navScroller = function (len, tabJQ)
            {
                let navJQ = cfg.tabsJQ.find('.cachetabs-nav');
    
                // 滚动条位置
                let sPosition = navJQ.scrollLeft();
                // nav宽度
                let w = navJQ.width();
                // nav文档长度
                let swidth = navJQ[0].scrollWidth;
                // 需要滚动的新位置
                let toPosition = 0;
                //
                if (len == 0)
                    toPosition = 0;
                else if (len == 1)
                    toPosition = swidth;
                else if (len == 'left')
                    toPosition = sPosition - (w / 4);
                else if (len == 'right')
                    toPosition = sPosition + (w / 4);
                else
                    toPosition = sPosition + len;
                // 移动滚动条, 此处无需判是否滚动到头或者尾.如果传入的滚动位置无效,则会自动设为0或最大
                navJQ.scrollLeft(toPosition);
                //console.log('滚动位置: ' + toPosition);
                //console.log('文档长度: ' + navJQ[0].scrollWidth);
            }
            // 调整选项卡框的滚动条值,当指定选项卡靠近选项卡框左边或右边时,使其处于中间位置
            let navScrollerByTab = function (tabJQ)
            {
                let navJQ = cfg.tabsJQ.find('.cachetabs-nav');
                // 界限值100px,大致是一个按钮的宽度
                let tagLen = 100;
                // 滚动条位置
                let sPosition = navJQ.scrollLeft();
                // nav宽度
                let w = navJQ.width();
                // nav文档长度
                let swidth = navJQ[0].scrollWidth;
                // 离选项卡框左起位置
                let tabPosition = tabJQ.position().left;
                //
                //console.log('滚动条位置 ' + sPosition + ' 文档宽度 ' + w + ' nav文档长度 ' + swidth + ' 离选项卡框的位置 ' + tabPosition);
                if (tabPosition < tagLen || (tabPosition + 2*tagLen) > w)
                {
                    navJQ.scrollLeft(sPosition + (tabPosition - w / 2));
                    //console.log('判定移动距离:' + (sPosition + (tabPosition - w / 2)));
                }
            }
            /*=============================*
             * 事件绑定
             *=============================*/
            // 选项卡关闭按钮: tabJQ:选项卡JQ对象,onClosing:关闭时事件.返回false取消关闭
            let bindEventForCloseBtn = function (tabJQ, onClosing)
            {
                // 点击标签选项卡上的关闭按钮时,关闭当前页面,清除其缓存,载入上次的页面到容器中
                tabJQ.find('.cachetabs-tabclose').on('click', function (event)
                {
                    event.stopPropagation();
                    if (typeof onClosing == 'function')
                    {
                        if (onClosing() == false)
                            return;
                    }
    
                    // 当前页面:则删除当前页面,加载缓存中最后一个页面到显示容器中
                    if (tabJQ.hasClass(cfg.activeCls))
                    {
                        let lastkey = store.KeyIndex.Last();
                        // 如果没有缓存页了,说明关闭的是最后一个选项卡.此时删除页面即可
                        if (lastkey == null)
                        {
                            cfg.screenJQ.empty();
                        }
                        else
                        {
                            cacheToDom(lastkey);
                            // 激活缓存对应选项卡,去掉关联属性,删除该缓存.
                            let navJQ = cfg.tabsJQ.find('.cachetabs-nav');
                            navJQ.find(String.Format('.cachetabs-tab[{0}={1}]', cfg.cacheKey, lastkey))
                                .addClass(cfg.activeCls).removeAttr(cfg.cacheKey);
                            store.remove(lastkey);
                        }
                    } else
                    {
                        let cacheId = tabJQ.attr(cfg.cacheKey);
                        // 非当前页面:直接清除缓存
                        store.remove(cacheId);
                    }
                    // 删除选项卡
                    tabJQ.remove();
                })
            }
            let bindEventForInit = function ()
            {
                bindEventForLeftRightBtn();
                bindEventForGoToActiveTab();
                bindEventForCloseAll();
                bindEventForCloseAllWithOutActive();
            }
            // 选项卡点击:选项卡之间的切换
            let bindEventForLabelBtn = function (tabJQ)
            {
                // 点击标签选项卡时,缓存当前容器中的页面,对应缓存页面加载到容器中
                tabJQ.on('click', function ()
                {
                    // 点击选项卡时,位置会相应调整,确保点击的选项卡完全显示在父级的可见区域.
                    navScrollerByTab(tabJQ);
                    // 点击的是活动页面,退出
                    if ($(this).hasClass(cfg.activeCls))
                        return;
    
                    // 缓存当前DOM
                    let cacheId_current = domToCache();
                    // 找到当前活动的选项卡,去掉活动状态,添加缓存id,关联选项卡与缓存
                    $(this).parent().find('.' + cfg.activeCls).removeClass(cfg.activeCls).attr(cfg.cacheKey, cacheId_current);
    
                    // 激活点击的选项卡,获取其缓存页加载到显示容器
                    let cacheId = $(this).attr(cfg.cacheKey);
                    cacheToDom(cacheId);
                    // 删除其关联属性,缓存
                    $(this).addClass(cfg.activeCls).removeAttr(cfg.cacheKey);
                    store.remove(cacheId);
                })
            }
            // 选项卡前进,后退 按钮事件绑定
            let bindEventForLeftRightBtn = function ()
            {
                // 选项卡页向左滚动,调整选项卡框的scroll值
                cfg.tabsJQ.find('.cachetabs-left').on('click', function ()
                {
                    navScroller('left');
                })
    
                // 选项卡页向右滚动按钮,调整选项卡框的scroll值
                cfg.tabsJQ.find('.cachetabs-right').on('click', function ()
                {
                    navScroller('right');
                })
            }
            // 定位当前选项卡
            let bindEventForGoToActiveTab = function ()
            {
                cfg.tabsJQ.find('.cachetabs-goto-active').on('click', function ()
                {
                    let activeTab = cfg.tabsJQ.find('.' + cfg.activeCls);
                    if (activeTab.length == 0) return;
                    navScrollerByTab(activeTab);
                })
            }
            // 关闭所有选项卡
            let bindEventForCloseAll = function ()
            {
                cfg.tabsJQ.find('.cachetabs-close-all').on('click', function ()
                {
                    // 删除选项卡,删除缓存,清空显示容器
                    let navJQ = cfg.tabsJQ.find('.cachetabs-nav');
                    navJQ.empty();
                    store.clear();
                    cfg.screenJQ.empty();
                })
            }
            // 关闭除当前外所有选项卡
            let bindEventForCloseAllWithOutActive = function ()
            {
                cfg.tabsJQ.find('.cachetabs-close-other').on('click', function ()
                {
                    // 删除选项卡除活动的外,删除缓存.(活动页无缓存)
                    let navJQ = cfg.tabsJQ.find('.cachetabs-nav');
                    navJQ.find('.cachetabs-tab:not(.'+ cfg.activeCls+')').remove();
                    store.clear();
                })
            }
            /*==============================*
             * 缓存处理类
             *==============================*/
            let store = new function ()
            {
                let self = this;
                // 缓存数据
                self.Dict = {};
                // 缓存数据的键索引
                self.KeyIndex = [];
                // 添加DOM片段到缓存,然后返回缓存ID. doms:dom片段,不能是JQ对象
                self.add = function (doms)
                {
                    // 缓存索引范围 0~1023
                    let keycount = Object.keys(self.Dict).length;
                    if (keycount > 1023) return;
                    //
                    let newCacheId = '_' + Math.NextInt(keycount, 1023);
                    while (true)
                    {
                        if (self.Dict.hasOwnProperty(newCacheId))
                            newCacheId = '_' + Math.NextInt(keycount, 1023);
                        else
                            break;
                    }
                    if (!doms)
                    {
                        self.Dict[newCacheId] = null;
                    } else
                    {
                        // 建立新的文档片断对象
                        let fragdom = document.createDocumentFragment();
                        //console.log(fragdom);
                        //fragdom.appendChild(doms);
                        $(fragdom).append(doms);
                        self.Dict[newCacheId] = fragdom;
                    }
                    // 缓存ID插入排序列表
                    self.KeyIndex.push(newCacheId);
                    //console.log('添加了缓存,键:' + newCacheId);
                    //console.log('缓存字典长度' + Object.keys(self.Dict).length);
                    //console.log('缓存键列表' + self.KeyIndex);
                    return newCacheId;
                }
                // 删除缓存 成功则返回true,cacheId无效返回false
                self.remove = function (cacheId)
                {
                    if (self.Dict.hasOwnProperty(cacheId))
                    {
                        delete self.Dict[cacheId];
                        self.KeyIndex.Remove(cacheId);
                        //console.log('删除了缓存' + cacheId);
                        //console.log('缓存字典长度' + Object.keys(self.Dict).length);
                        //console.log('缓存键列表' + self.KeyIndex);
                        return true;
                    }
                    //console.log('删除缓存失败,不存在的缓存id:' + cacheId);
                    return false;
                }
                // 清空缓存
                self.clear = function ()
                {
                    self.Dict = {};
                    self.KeyIndex = [];
                    //console.log('删除了全部缓存');
                    //console.log('缓存字典长度' + Object.keys(self.Dict).length);
                    //console.log('缓存键列表' + self.KeyIndex);
                }
                // 根据ID取缓存
                self.get = function (cacheId)
                {
    
                    if (self.Dict.hasOwnProperty(cacheId))
                    {
                        //console.log('取出了缓存,键:' + cacheId);
                        return self.Dict[cacheId];
                    }
                    //console.log('取出缓存无效,键不存在,键:' + cacheId);
                    return null;
                }
            }
    
            /*==============================*
             * 初始化后绑定基础事件
             *==============================*/
            bindEventForInit();
        }
    })
    js
    .cachetabs {
      display: flex;
      position: relative;
      height: 42px;
      line-height: 40px;
      border-bottom: 2px solid #007bff;
      -webkit-user-select: none;
      -moz-user-select: none;
      -ms-user-select: none;
      user-select: none;
    }
    
    .cachetabs-left, .cachetabs-right {
      flex: 0 1 36px;
      border-left: 1px solid #ced4da;
      border-right: 1px solid #ced4da;
      cursor: pointer;
      background-color: #fff;
    }
    
    .cachetabs-right {
      text-align: right;
    }
    
      .cachetabs-left:before, .cachetabs-right:before {
        content: '';
        vertical-align: middle;
      }
    
    .cachetabs-left:before {
      display: inline-block;
      width: 0;
      height: 0;
      border: 12px solid transparent;
      border-right-color: #6c757d;
    }
    
    .cachetabs-right:before {
      display: inline-block;
      width: 0;
      height: 0;
      border: 12px solid transparent;
      border-left-color: #6c757d;
    }
    
    .cachetabs-left:hover, .cachetabs-right:hover, .cachetabs-tab:hover {
      background-color: #e9ecef;
    }
    
    .cachetabs-left:active, .cachetabs-right:active, .cachetabs-tab:active {
      background-color: #dee2e6;
    }
    
    .cachetabs-menutitle {
      flex-basis: 56px;
      text-align: center;
      background-color: #fff;
    }
    
      .cachetabs-menutitle:after {
        content: '';
        display: inline-block;
        width: 0;
        height: 0;
        border: 5px solid transparent;
        border-top-color: #6c757d;
        margin-left: 2px;
        vertical-align: middle;
      }
    
      .cachetabs-menutitle:hover {
        background-color: #e9ecef;
      }
    
        .cachetabs-menutitle:hover ~ .cachetabs-menugroup {
          display: block;
        }
    
    .cachetabs-menugroup {
      display: none;
      position: absolute;
      top: 40px;
      right: 0;
      width: 120px;
      color: #adb5bd;
      text-align: center;
      background-color: #fff;
      border-left: 2px solid #007bff;
      border-bottom: 2px solid #007bff;
      cursor: pointer;
    }
    
      .cachetabs-menugroup:hover {
        display: block;
      }
    
    .cachetabs-close-all, .cachetabs-close-other, .cachetabs-goto-active {
      display: block;
    }
    
    .cachetabs-goto-active {
      border-bottom: 1px solid #e9ecef;
    }
    
      .cachetabs-close-all:hover, .cachetabs-close-other:hover, .cachetabs-goto-active:hover {
        color: #495057;
        background-color: #e9ecef;
      }
    
    .cachetabs-navbox {
      flex: 1 1 0;
      width: 0;
      height: 40px;
      overflow: hidden;
      background-color: #f8f9fa;
    }
    
    .cachetabs-nav {
      position: relative;
      margin-right: 100px;
      white-space: nowrap;
      overflow-x: auto;
    }
    
    .cachetabs-tab {
      display: inline-block;
      padding: 0 15px;
      height: 40px;
      border-right: 1px solid #ced4da;
      font-size: 14px;
      cursor: pointer;
    }
    
      .cachetabs-tab.active {
        color: #fff;
        background-color: #007bff;
      }
    
    .cachetabs-tabclose {
      display: inline-block;
      width: 16px;
      height: 16px;
      line-height: 16px;
      color: #ced4da;
      text-align: center;
      margin-left: 4px;
    }
    
      .cachetabs-tabclose:hover {
        border-radius: 50% 50%;
        color: #fff;
        background-color: #dc3545;
        text-decoration: none;
      }
    
    .cachetabs.gray {
      border-bottom: 2px solid #6c757d;
    }
    
      .cachetabs.gray .cachetabs-tab.active {
        background-color: #6c757d;
      }
    
      .cachetabs.gray .cachetabs-menugroup {
        border-left: 2px solid #6c757d;
        border-bottom: 2px solid #6c757d;
      }
    
    .cachetabs.green {
      border-bottom: 2px solid #28a745;
    }
    
      .cachetabs.green .cachetabs-tab.active {
        background-color: #28a745;
      }
    
      .cachetabs.green .cachetabs-menugroup {
        border-left: 2px solid #28a745;
        border-bottom: 2px solid #28a745;
      }
    
    .cachetabs.red {
      border-bottom: 2px solid #dc3545;
    }
    
      .cachetabs.red .cachetabs-tab.active {
        background-color: #dc3545;
      }
    
      .cachetabs.red .cachetabs-menugroup {
        border-left: 2px solid #dc3545;
        border-bottom: 2px solid #dc3545;
      }
    
    .cachetabs.yellow {
      border-bottom: 2px solid #ffc107;
    }
    
      .cachetabs.yellow .cachetabs-tab.active {
        background-color: #ffc107;
      }
    
      .cachetabs.yellow .cachetabs-menugroup {
        border-left: 2px solid #ffc107;
        border-bottom: 2px solid #ffc107;
      }
    css
  • 相关阅读:
    【设计模式】责任者模式
    【Java工具方法】给集合按数量分组
    【Spring】非Spring IOC容器下获取Spring IOC上下文的环境
    【Java】模板方法模式
    【RabbitMQ】CentOS安装RabbitMQ,及简单的Java客户端连接
    【RabbitMQ】RabbitMQ的一些基础概念
    【Spring】简单的Spring AOP注解示例
    【Web】URI和URL,及URL的编码
    【加密】对称加密算法
    【Normal Form】数据库表结构设计所遵从的范式
  • 原文地址:https://www.cnblogs.com/mirrortom/p/9553407.html
Copyright © 2011-2022 走看看