ratiw / vuetable-2

data table simplify! -- datatable component for Vue 2.x. See documentation at
https://vuetable.com
MIT License
2.16k stars 400 forks source link

Responsive tables with detail-rows #438

Open suits-at opened 6 years ago

suits-at commented 6 years ago

As mentioned in #356 I would like to use vuetable-2, but I also need responsiveness. In my opinion the approach from the DataTables extension "responsive" gives the best user experience possible. (Demo) Only the data which fits on the screen is shown, everything als automatically gets hidden inside a detail-row (depentend on the viewport). By clicking the green "+", the hidden data of the clicked row gets visible. As vuetable-2 already has detail-rows included, and using this aproach to make table responsive by default would also be framework independent, I thought this might be a good option for including responsive behaviour to vuetables-2. What do you think @ratiw?

Georgeek commented 6 years ago

it would be awesome!

Georgeek commented 6 years ago

I've started something like this, but it hides all columns and doesnt recalculate table width for some reason:

mounted() {
 window.addEventListener('resize', this.handleResize)
}
methods: {
 handleResize() {
   let i = this.fields.length - 1
   if (dataTableWidth < browserWidth) {
      return
   } else {
     for (i; i > 0; i--) {
         if (dataTableWidth < browserWidth) return

        this.fields[i].visible = false
        this.$refs.vuetable.normalizeFields()
        console.log('hidding', this.fields[i])
      }
    }
  }
}
ratiw commented 6 years ago

DataTables has more comprehensive spec. It might be doable in v2.1 where the support for Row component has been implemented (the same idea as an extension in DataTables).

I think this should not be a standard feature as not everybody will want to use it. Please do not expect it to be implemented so soon as I can only do so much with the spare time I have.

suits-at commented 6 years ago

@Georgeek I had a look at your code and found a problem. You are looping over all elements, and set every field to visible = false. Your condition if (dataTableWidth < browserWidth) return never comes true, if you change the viewport just once and therefore the dataTableWidth does no change during your loop. Because of that, all items were set to visible = false at once. Another way would be to use this.fields[this.$refs.vuetable.countVisibleFields-1].visible = false; instead of using a for-loop to hide only one column at once. This works fine if you resize your browser window manually, but it does not work if you open the table on a samll device. Hope this helps improving your code so you might find a working solution for your needs. :)

Georgeek commented 6 years ago

@suits-at thank you for reply. It didnt work because at some circumstances it fires only once, despite the 'addEventListener' event. I've tried recursion as well and store col counter...

I've solved in another way (which i dont like and its temporary)

// methods

// if window resize horizontaly
    windowResize() {
      let width = window.innerWidth
      if (width !== this.screenWidth) {
        this.handleResize(this.fieldsLength);
        this.screenWidth = width;
      };
    },

// hiding cells 
    showHideColumn(colNum, show = false) {
      for (let i = 0; i < row.length; i++ ) {
          let col = row[i].cells[colNum]
          col.style.display = show ? '': 'none'
      }

      if (!show) {
        this.fieldsLength = colNum - 1
      } else if (show && colNum < this.fields.length - 1) {
        this.fieldsLength = colNum + 1
      } else {
        return this.fieldsLength
      }
    },

// fires on resize, stores in array columns brakepoints (to restore it next time)
    handleResize(col = this.fields.length - 1) {
        let browserWidth
        let tableCont = this.$refs.testtable
        let dataTableRect = tableCont.children[0].getBoundingClientRect()
        let dataTableOffset = dataTableRect.x
        let dataTableWidth = dataTableRect.width
        browserWidth = window.innerWidth - dataTableOffset - 20

      if (dataTableWidth < browserWidth) {
        if (!this.dtBreakpoints.length) return

        for(let i = 0; i < this.dtBreakpoints.length; i++) {
          if (this.dtBreakpoints[i].width < browserWidth) {
            this.showHideColumn(this.dtBreakpoints[i].col, true)
            this.dtBreakpoints.shift()
          }
        }
        return
      }
      if (col > 0) {
        let breakpoint = { col: col, width: dataTableWidth}
        this.showHideColumn(col, false)
        this.dtBreakpoints.unshift(breakpoint)
        this.handleResize(col - 1)
      }

Didnt mention mounted() where i add 'addEventListener' and resize initially

Georgeek commented 6 years ago

rewrote handleResize and deleted showHideColumn. Almost work as i wanted :)

    handleResize(col = this.fields.length - 1) {
        let browserWidth
        let tableCont = this.$refs.vuetable
        let dataTableRect = tableCont.$el.getBoundingClientRect()
        let dataTableOffset = dataTableRect.x
        let dataTableWidth = dataTableRect.width
        browserWidth = window.innerWidth - dataTableOffset

        if (dataTableWidth < browserWidth) {
          if (!this.dtBreakpoints.length) return
          for(let i = 0; i < this.dtBreakpoints.length; i++) {
            if (this.dtBreakpoints[i].width < browserWidth) {
              this.$nextTick()
                .then(() => this.fields[this.dtBreakpoints[i].col].visible = true)
                .then(() => this.$refs.vuetable.normalizeFields())
                .then(() => this.dtBreakpoints.shift())
            }
          }
          return
        }

        if (col > 0) {
          let breakpoint = { col: col, width: dataTableWidth + 20}

          this.dtBreakpoints.unshift(breakpoint)
          this.$nextTick()
            .then(() => this.fields[col].visible = false)
            .then(() => this.$refs.vuetable.normalizeFields())
            .then(() => this.handleResize(col - 1))
        }
    },
Georgeek commented 6 years ago

aslo need to take into account that resize works only on browser width on pc. To make it works on tablet and phone we need to check display orientation and screen width

mounted() {
    // js resize mounted
    this.fieldsLength = this.fields.length - 1
    window.addEventListener('optimizedResize', this.windowResize)
    window.addEventListener('orientationchange', this.onOrientationChange)
}

methods: {
    onOrientationChange() {
      this.$refs.vuetable.reload()
    },
   onDtLoaded() {
      this.handleOrientation(this.fields.length - 1)
   }
}
vuetable(ref="vuetable"
// omitted settings
              @vuetable:loaded="onDtLoaded"

handleOrientation is almost the same as handleResize (need to refactor)

    handleOrientation(col = this.fields.length - 1) {
        let tableCont = this.$refs.vuetable
        let dataTableRect = tableCont.$el.getBoundingClientRect()
        let dataTableOffset = dataTableRect.x
        let dataTableWidth = dataTableRect.right // было width

        let matchMedia = (window.matchMedia('(max-device-width: 1400px) and (orientation: landscape)').matches || window.matchMedia('(max-device-width: 1400px) and (orientation: portrait)').matches)

        let browserWidth = matchMedia ? screen.width - 50 : window.innerWidth 
      if (dataTableWidth < browserWidth) return

      if (col > 1 && col < this.fields.length) {
        let breakpoint = { col: col, width: dataTableWidth + 20}
        this.$nextTick()
          .then(() => this.fields[col].visible = false)
          .then(() => this.$refs.vuetable.normalizeFields())
          .then(() => this.dtBreakpoints.unshift(breakpoint))
          .then(() => this.decrementFieldsLenght(col))
          .then(() => {
            if (dataTableWidth > browserWidth) {
              this.handleOrientation(this.fieldsLength)
            }
          })
      }
    }
drascom commented 5 years ago

hi Georgeek thanks for your effort could you share full code implementation at codepen or jsfiddle ? tip: 1 - dont forget to add

 data () {
    return {
      fieldsLength:'',
      dtBreakpoints:[]
      }
  },

2- "optimizedResize" is not working at opera browser. i used "resize" instead. window.addEventListener('resize', this.windowResize)