Vue to achieve studio management background (4): state mode to achieve window docking, flexible, free

Time:2021-6-2

I was very satisfied with the tabs window I made yesterday. Today, I made it so that I can change the display style according to my size. If my width is too small, the tab page can float and dock on one side. Specific effect:
left

Vue to achieve studio management background (4): state mode to achieve window docking, flexible, free

right

Vue to achieve studio management background (4): state mode to achieve window docking, flexible, free

I always like simple and clear things, so I want to make it simple, but reality doesn’t allow it. The function is really complicated. I worked hard all afternoon and finally finished it.
The left and right windows should use the same control to maximize the reusability of the code. There are many control states: normal display (ordinary tabs window), list (display icon and title, pop-up tab page when clicking), mini list (only display icon, pop-up tab page when clicking).
When the control is on the left side of the interface, the tab page pops up on the right side. On the contrary, when the control is on the right side of the interface, the tab page pops up on the left side.
From normal tabs to list display, all tabs are not activated. Zoom in from the list to the normal tab. By default, a tab is selected.
With so many state requirements, the code is easy to mess up. But fortunately, there is a design pattern called “state mode”, which can solve this problem well. The disadvantage is that the amount of code in the initial stage is a little large, and the advantage is that it is convenient for later management.
Yesterday, we made two tabs controls, one is widgettabs, and the other is pagetabs. The latter can still meet our needs. We only need to modify widgettabs.
Some of the codes implemented yesterday are deleted. First, rewrite the template and write the script code according to the template, which can make the script code more practical. Just like in test driven development, it is a truth to write the test first and then write the code.
And I almost forgot. In yesterday’s code, we put all the styles in the style.css file, and let the post Vue be introduced globally. As we write more and more controls, this file will become more and more bulky and inconvenient to manage. This time, take the style code related to widgettabs to the Vue component.
First look at the template code:

<template>
  <div class="widget-tabs" :class="stateClass" ref="widget">
    <ul class="heads">
      <li v-for="tab in tabs" class="item" :class="{ 'active': tab.isShow }" @click="click(tab)">
        <div v-show="showIcon" class="tab-icon"><i :class="tab.icon"></i></div> 
        <span v-show="showTitle"> {{ tab.name }}</span>
      </li>
    </ul>
    <div v-show="showTabBody" class="tab-body" :class="dockLeft?'dock-left':''">
      <div v-show="showTabTitle" class="tab-title">
        <div>{{selectedTab ? selectedTab.name : ''}}</div>
        <div class="tab-close" @click="close">×</div>
      </div>
      <slot></slot>
    </div>
  </div>
</template>

The top div is the shell of our control. Class corresponds to three CSS classes in three states
1. Default state, empty string
2. List status, middle size
3. Mini list status, mini size
According to the CSSS class, the CSS code displays its child elements in different ways, so as to realize normal display or pop-up display.
Ref is equivalent to setting a unique ID for the div, through which we can get the corresponding DOM element in the code, so as to judge the size of the current control and adjust the display style of the control according to the size.
UL element shows the navigation tag part of the tabs control, and determines whether the tag is activated according to the display or hiding of each tab page. It also has the function of receiving mouse click events and passing them to the control script. The template basically has no logic, and mainly displays and receives events.
Whether the icon is displayed depends on the showicon calculation property.
Whether to display the title depends on the showtitle calculation property.
Whether the whole tab body is displayed or not is determined according to the calculation properties of showtabbody. Because the tab body is sometimes docked on the left side of the control and sometimes on the right side of the control, the docking method is determined according to the property dockleft. If docked on the left, it is true, otherwise it is false.
Tabtitle is the title area displayed when docking:

According to the calculation property showtabtitle to determine whether to display. The close button is responsible for receiving click events and passing them to the controller script. No matter how to implement it, the control script only needs to meet the requirements of the template. Equivalent to the interface, according to the interface design and implementation.
We have decided to use the state mode to implement it. According to the state, we design three state classes
Normal state (normal tabs control), middlestate (list state, with title and icon), ministate (Mini list state, only display icon). The latter two classes have some common operations, such as pop-up hidden tab, etc., which can inherit the common base class: liststate. The three state classes also have some functional intersection, and they can have a common base class state. The class diagram is as follows (I haven’t used UML tools for many years, just make do with Excel)

Vue to achieve studio management background (4): state mode to achieve window docking, flexible, free

If you don’t look carefully, you don’t know which high-end UML tool actually drew this diagram by Excel.

Code corresponding to status class:

class State{
  constructor(context){ this.context = context
  } 

  widthChange(width){ if(width <=90){ this.toState(this.context.miniState)
    } else if(width <=160){ this.toState(this.context.middleState)
    } else{ this.toState(this.context.normalState)
    }
  }

  showTabBody(){ return true }

  showTabTitle(){ return false }

  showIcon(){ return false }

  showTitle(){ return true }

  close(){}

  toState(state){ if(this.context.state !== state){ if(this.context.state === this.context.normalState){ this.context.selectedTab.isShow = false console.log('dddd')
      } if(state === this.context.normalState){ this.context.selectedTab.isShow = true } this.context.state = state
    }
  }

  stateClass(){ return '' }

}
class NormalState extends State{
  constructor(context){
    super(context)
  }

  clickTab(clickedTab){ this.context.tabs.forEach(tab => {
      tab.isShow = (tab.name == clickedTab.name) this.context.selectedTab = clickedTab
    });
  }
}// pop up display of label content is required
class ListState extends State{
  constructor(context){
    super(context)
  }

  showTabBody(){ return this.context.selectedTab.isShow
  }

  showTabTitle(){ return true }

  showIcon(){ return true }

  showTitle(){ return true }

  close(){ this.context.selectedTab.isShow = false }

  clickTab(clickedTab){ this.context.tabs.forEach(tab => { if(tab === clickedTab){
        tab.isShow = !tab.isShow this.context.selectedTab = clickedTab
      } else{
        tab.isShow = false }
    });
  }
}// the status shows the icon and title
class MiddleState extends ListState{
  constructor(context){
    super(context)
  }

  stateClass(){ return 'middle-size' }
}// only icons are displayed in this state
class MiniState extends ListState{
  constructor(context){
    super(context)
  }

  showTitle(){ return false }

  stateClass(){ return 'mini-size' }
}

Control script code:

export default {
  name: 'WidgetTabs',
  data() { return {
      tabs: [],
      state: null,
      selectedTab :null,
      dockLeft:false,
    }
  },

  created() { this.tabs = this.$children; this.normalState = new NormalState(this) this.middleState = new MiddleState(this) this.miniState = new MiniState(this) this.state = this.normalState
  },

  computed: {
    stateClass(){ return this.state.stateClass()
    },

    showIcon(){ return this.state.showIcon()
    },

    showTitle(){ return this.state.showTitle()
    },

    showTabBody(){ return this.state.showTabBody()
    },
    showTabTitle(){ return this.state.showTabTitle()
    },
  },

  methods: {
    click(clickTab) { this.state.clickTab(clickTab)
    },

    mouseMove(){ if(this.$refs.widget){ this.dockLeft = this.$refs.widget.offsetLeft < 50
        this.state.widthChange(this.$refs.widget.offsetWidth)
      }
    },

    mouseDown(event){
      document.addEventListener('mousemove', this.mouseMove)
    },

    mouseUp(event){
      document.removeEventListener('mousemove', this.mouseMove)
    },

    close(){ this.state.close()
    }
  },

  mounted () {
    document.addEventListener('mousedown', this.mouseDown)
    document.addEventListener('mouseup', this.mouseUp) this.tabs.forEach(tab => { if(tab.isShow){ this.selectedTab = tab
      }
    });
  },

  beforeDestroyed() {
    document.removeEventListener('mousedown', this.mouseDown)
    document.removeEventListener('mouseup', this.mouseUp)
  },
}

Initializes various states when a component is created. It should be noted that the width of the control needs to be dynamically obtained when the window changes to determine which state the control is in. There is no reset event in div in JS, which can be replaced by mouse event. Our window size is achieved by dragging the mouse, so we track the mouse dragging event, dynamically query the size of the control, and then distribute the event.
This control has been completed so far. The event of writing this article takes longer than writing code. I am born a programmer, not a writer.
For the code of the whole project in this history node, please check it on my GitHubhttps://github.com/vularsoft/studio-ui
How to find the history node:
Vue to achieve studio management background (4): state mode to achieve window docking, flexible, free
Rxeditor is a visual editing tool for bootstrap code. This series records the development process of the software. If you have any questions, please leave a message on ithub.

This work adoptsCC agreementReprint must indicate the author and the link of this article