WeChat applet implements fixed header and list table components

WeChat applet implements fixed header and list table components

need:

WeChat applet implements fixed header and fixed column table components (applicable to mobile terminals with some minor modifications)

Function Points

  • Sorting Table
  • The header can be fixed
  • The first column is fixed (it can be optimized to configure the left and right sides of the specified column to be fixed)
  • Page turning (pull up to load) monitoring

Rendering

Implementation ideas

I started to think of using three ScrollViews to implement scroll linkage. If the header and columns are fixed, the header and columns should scroll accordingly when the table content scrolls. After writing a demo, I found that it would be very stuck to monitor the position information of one ScrollView to set the position of the other two ScrollViews, and the experience was extremely bad. Use position: sticky; to make the header stick to the top of the table, and the first element of each row stick to the left side of the current row.

Problems encountered:

  • When the table slides left, the fixed column slides out of the screen after sliding one screen. Solution: Dynamically set the width of the table. Principle: The reason for sliding out is that the entire row slides out of the screen, and the sticky is positioned relative to the left side of the entire row.
  • After the table height is set to 100%, the useReachBottom pull-up monitoring fails. If the table height is set higher, the fixed header will fail. Solution: Use ScrollView to wrap the table and use onScrollToLower to listen for loading

Specific code (react\taro3.0)

index.tsx

/**
 * Slidable, fixed header, fixed column table components* @example <Table data={data} dataAttribute={dataAttribute} sortTypeChange={sortTypeChange} handleRow={toDetails}/>
 */

import React, { useState, useMemo, useEffect } from 'react'
import classNames from 'classnames'

// components
import { View, Text, ScrollView } from '@tarojs/components'

// utils
import { noop } from '@/utils/util'

// styles
import styles from './index.module.less'

interface DataAttributeItem {
  title: string
  key: string | number
  sortKey?: string | number
}

interface Props {
  data: Array<any>
  dataAttribute: Array<DataAttributeItem>
  sortTypeChange?: (sort_item_id: any, sort_desc: boolean) => void
  handleRow?: (data: any) => void
  handleScrollToLower?: (e: any) => void
}

export default function Table(props: Props) {
  const { data, dataAttribute, sortTypeChange = noop, handleRow = noop, handleScrollToLower = noop } = props
  const [isSortDesc, setIsSortDesc] = useState<boolean>(true)
  const [sortIndex, setSortIndex] = useState<number>(1)
  const tableWidth = useMemo(() => {
    return `${(dataAttribute.length * 148 + 48)}rpx`
  }, [dataAttribute])

  const tableHeight = useMemo(() => {
    return `${((data.length + 1) * 96)}rpx`
  }, [data])

  const handleSortItem = (attrItem, attrIndex) => {
    if (attrIndex === 0) {
      return
    }
    const beforeIndex = sortIndex
    const sortKey = attrItem.sortKey
    dataAttribute.map((item, index)=>{
      if (item.sortKey === sortKey) {
        if (beforeIndex === index) {
          setIsSortDesc(!isSortDesc)
        } else {
          setSortIndex(index)
          setIsSortDesc(true)
        }
      }
    })
  }

  useEffect(()=>{
    const sort_desc = isSortDesc
    const sort_item_id = dataAttribute[sortIndex].sortKey
    sortTypeChange(sort_item_id,sort_desc)
  },[sortIndex, isSortDesc])


  return (
    <ScrollView className={styles['table']} scrollY scrollX onScrollToLower={handleScrollToLower}>
      <View className={styles['sticky-box']} style={{height: tableHeight}}>
        <View className={styles['grey-box']} style={{width: tableWidth, position: 'sticky'}}/>
        <View className={styles['table__head']} style={{width: tableWidth, position: 'sticky'}}>
          {dataAttribute.map((attrItem, attrIndex) => (
            <View className={styles['table__head__td']} key={attrIndex} onClick={()=>handleSortItem(attrItem, attrIndex)}>
              <Text
                className={classNames({
                  [styles['table__head__td__text']]: true,
                  [styles['table__head__td__text-active']]: sortIndex === attrIndex,
                })}
                key={attrIndex}
              >{attrItem.title}</Text>
              {attrIndex !== 0 && <View
                className={classNames({
                  [styles['table__head__td__sorter-indicate']]: true,
                  [styles['table__head__td__sorter-indicate--asc-active']]: sortIndex === attrIndex && !isSortDesc,
                  [styles['table__head__td__sorter-indicate--desc-active']]: sortIndex === attrIndex && isSortDesc
                })}
              />}
            </View>
          ))}
        </View>
        {data.map((dataItem, dataIndex) => (
          <View className={styles['table__row']} key={dataIndex} style={{width: tableWidth}} onClick={() => handleRow(dataItem)}>
            {dataAttribute.map((attrItem, attrIndex) => {
              return (
                <Text className={styles['table__row__td']} key={attrIndex}>{dataItem[attrItem.key] || '-'}</Text>
              )
            })}
          </View>
        ))}
      </View>
    </ScrollView>
  )
}

index.module.less

@import '~@/assets/style/mixins/ellipsis.less';
page{
  font-size: 26rpx;
  line-height: 60rpx;
  color: #222;
  height: 100%;
  width: 100%;
}
.grey-box{
  height: 10rpx;
  top: 0;
  background: #f8f8f8;
  z-index: 100;
}
.table{
  position: relative;
  overflow: scroll;
  width: 100%;
  height: 100%;
  overflow: scroll;
  &__head{
    position: relative;
    height: 96rpx;
    white-space: nowrap;
    // position: sticky;
    top: 10rpx;
    z-index: 100;
    height: 88rpx;
    font-size: 24rpx;
    line-height: 88rpx;
    color: #aaabbd;
    background-color: #f8f8f8;
    border-bottom: 2rpx solid #ecf1f8;
    background-color: #fff;
    white-space: nowrap;
    display: flex;
    &__td{
      .ellipsis();
      width: 148rpx;
      // padding-right: 40rpx;
      display: flex;
      justify-content: flex-start;
      align-items: center;
      background-color: #fff;
      position: relative;
      box-sizing: border-box;
      &:nth-child(1) {
        padding-left: 24rpx;
        width: 154rpx;
        margin-right: 40rpx;
        position: sticky;
        z-index: 10;
        left: 0;
      }
      &__text{
        display: inline;
        &-active{
          color: #6d70ff;
        }
      }
      &__sorter-indicate{
        width: 24rpx;
        height: 24rpx;
        display: inline-block;
        background-repeat: no-repeat;
        background-size: 100% 100%;
        background-image: url('https://icon1.png');
        &--asc-active {
          background-image: url('https://icon2.png');
        }
        &--desc-active {
          background-image: url('https://icon3.png');
        }
      }
    }
  }
  &__row{
    position: relative;
    height: 96rpx;
    white-space: nowrap;
    display: flex;
    justify-content: flex-start;
    align-items: center;
    border-bottom: 2rpx solid #ecf1f8;
    &__td{
      // .ellipsis();
      overflow: scroll;
      white-space: nowrap;
      width: 148rpx;
      // padding-right: 40rpx;
      display: inline-block;
      background-color: #fff;
      position: relative;
      box-sizing: border-box;
      font-size: 26rpx;
      line-height: 96rpx;
      &:nth-child(1) {
        margin-right: 40rpx;
        padding-left: 24rpx;
        width: 154rpx;
        position: sticky;
        z-index: 10;
        left: 0;
      }
    }
  }
}

Specific code (Mini Program native)

<ScrollView class="table" scroll-x scroll-y bindscrolltolower="handleScrollToLower">
  <View class="sticky-box" style="height:{{tableHeight}}rpx;">
    <View class="table__head" style="width:{{tableWidth}}rpx;">
      <View class="table__head__td" wx:for="{{dataAttribute}}" wx:key="attrIndex" wx:for-index="attrIndex" wx:for-item="attrItem">
        <Text
          class="table__head__td__text"
        >{{attrItem.title}}</Text>
      </View>
    </View>
    <View class="table__row" wx:for="{{data}}" wx:key="dataIndex" wx:for-index="dataIndex" wx:for-item="dataItem" style="width:{{tableWidth}}rpx;">
      <Text class="table__row__td" wx:for="{{dataAttribute}}" wx:key="dataIndex" wx:for-index="attrIndex" wx:for-item="attrItem">{{dataItem[attrItem.key] || '-'}}</Text>
    </View>
  </View>
</ScrollView>
const app = getApp()
Page({
  data: {
    data: [
      {
        a: 123,
        b: 456,
        c: 489,
        d: 789,
        e: 458,
        f: 789
      },
      {
        a: 123,
        b: 456,
        c: 489,
        d: 789,
        e: 458,
        f: 789
      },
      {
        a: 123,
        b: 456,
        c: 489,
        d: 789,
        e: 458,
        f: 789
      },
      {
        a: 123,
        b: 456,
        c: 489,
        d: 789,
        e: 458,
        f: 789
      },
      {
        a: 123,
        b: 456,
        c: 489,
        d: 789,
        e: 458,
        f: 789
      },
      {
        a: 123,
        b: 456,
        c: 489,
        d: 789,
        e: 458,
        f: 789
      },
      {
        a: 123,
        b: 456,
        c: 489,
        d: 789,
        e: 458,
        f: 789
      },
      {
        a: 123,
        b: 456,
        c: 489,
        d: 789,
        e: 458,
        f: 789
      },
      {
        a: 123,
        b: 456,
        c: 489,
        d: 789,
        e: 458,
        f: 789
      },
      {
        a: 123,
        b: 456,
        c: 489,
        d: 789,
        e: 458,
        f: 789
      },
      {
        a: 123,
        b: 456,
        c: 489,
        d: 789,
        e: 458,
        f: 789
      },
      {
        a: 123,
        b: 456,
        c: 489,
        d: 789,
        e: 458,
        f: 789
      },
      {
        a: 123,
        b: 456,
        c: 489,
        d: 789,
        e: 458,
        f: 789
      },
      {
        a: 123,
        b: 456,
        c: 489,
        d: 789,
        e: 458,
        f: 789
      },
      {
        a: 123,
        b: 456,
        c: 489,
        d: 789,
        e: 458,
        f: 789
      },
      {
        a: 123,
        b: 456,
        c: 489,
        d: 789,
        e: 458,
        f: 789
      },
      {
        a: 123,
        b: 456,
        c: 489,
        d: 789,
        e: 458,
        f: 789
      },
      {
        a: 123,
        b: 456,
        c: 489,
        d: 789,
        e: 458,
        f: 789
      },
      {
        a: 123,
        b: 456,
        c: 489,
        d: 789,
        e: 458,
        f: 789
      },
      {
        a: 123,
        b: 456,
        c: 489,
        d: 789,
        e: 458,
        f: 789
      },
    ],
    dataAttribute: [
      {
        title: 'First column',
        key: 'a'
      },
      {
        title: 'Column 2',
        key: 'b'
      },
      {
        title: 'Column 3',
        key: 'c'
      },
      {
        title: 'Column 4',
        key: 'd'
      },
      {
        title: 'Column 5',
        key: 'e'
      },
      {
        title: 'Column 6',
        key: 'f'
      }
    ],
    tableHeight: (20 + 1) * 96,
    tableWidth: 200 * 6 + 60
  }
})
page{
  font-size: 26rpx;
  line-height: 60rpx;
  color: #222;
  height: 100%;
  width: 100%;
}
.table{
  display: block;
  position: relative;
  overflow: scroll;
  width: 100%;
  height: 100%;
}
.sticky-box{
}
.table__head{
  height: 96rpx;
  white-space: nowrap;
  position: sticky;
  top: 0rpx;
  z-index: 100;
  height: 88rpx;
  font-size: 24rpx;
  line-height: 88rpx;
  color: #aaabbd;
  background-color: #f8f8f8;
  border-bottom: 2rpx solid #ecf1f8;
  background-color: #fff;
  white-space: nowrap;
  display: flex;
}
.table__head__td{
  width: 200rpx;
  display: flex;
  justify-content: flex-start;
  align-items: center;
  background-color: #fff;
  box-sizing: border-box;
  position: relative;
  overflow: hidden;
  white-space: nowrap;
  -o-text-overflow:ellipsis;
  text-overflow: ellipsis;
}
.table__head__td:nth-child(1) {
  padding-left: 24rpx;
  width: 260rpx;
  margin-right: 40rpx;
  position: sticky;
  z-index: 101;
  left: 0rpx;
}
.table__head__td__text{
  display: inline;
}
.table__row{
  position: relative;
  height: 96rpx;
  white-space: nowrap;
  display: flex;
  justify-content: flex-start;
  align-items: center;
  border-bottom: 2rpx solid #ecf1f8;
}
.table__row__td{
  overflow: scroll;
  white-space: nowrap;
  width: 200rpx;
  display: inline-block;
  background-color: #fff;
  box-sizing: border-box;
  font-size: 26rpx;
  line-height: 96rpx;
  position: relative;
  overflow: hidden;
  white-space: nowrap;
  -o-text-overflow:ellipsis;
  text-overflow: ellipsis;
}
.table__row__td:nth-child(1) {
  margin-right: 40rpx;
  padding-left: 24rpx;
  width: 260rpx;
  position: sticky;
  z-index: 10;
  left: 0;
}

Summarize

This is the end of this article about how to implement fixed headers and table components in WeChat Mini Programs. For more relevant content about fixed headers in WeChat Mini Programs, please search for previous articles on 123WORDPRESS.COM or continue to browse the related articles below. I hope you will support 123WORDPRESS.COM in the future!

<<:  Detailed explanation of the implementation principle of ACID transaction in Mysql

>>:  SSM projects are frequently deployed as war packages, using tomcat and maven to implement hot deployment configuration

Recommend

Detailed analysis of each stage of nginx's http request processing

When writing the HTTP module of nginx, it is nece...

A brief discussion on MySQL select optimization solution

Table of contents Examples from real life Slow qu...

Share MySql8.0.19 installation pit record

The previous article introduced the installation ...

MySQL controls the number of attempts to enter incorrect passwords

1. How to monitor MySQL deadlocks in production e...

Detailed tutorial on MySQL installation and configuration

Table of contents Installation-free version of My...

Example of how to set automatic creation time and modification time in mysql

This article describes how to set the automatic c...

Analysis and solution of a MySQL slow log monitoring false alarm problem

Previously, for various reasons, some alarms were...

Summary of the use of TypeScript in React projects

Preface This article will focus on the use of Typ...

Vue keeps the user logged in (various token storage methods)

Table of contents How to set cookies Disadvantage...

Sample code for implementing neon button animation effects with CSS3.0

Today I will share with you a neon button animati...

How to define data examples in Vue

Preface In the development process, defining vari...

Simple operation of installing vi command in docker container

When using a docker container, sometimes vim is n...

How to use CocosCreator for sound processing in game development

Table of contents 1. Basics of audio playback in ...

Detailed explanation of Linux rpm and yum commands and usage

RPM package management A packaging and installati...

CSS to achieve scrolling image bar example code

On some websites, you can often see some pictures...