js之mvcExcel(一)

这一期,我使用面向对象的风格来重构我上版的Excel代码。并且这一次基于最近的面向对象的calculator的基础上使用MVC的框架来进行实现。这篇博客,我将讲解最为重要的model层的实现思路。
model层,即模型层,用于创建项目所需模型以及管理模型数据。
在这里插入图片描述
以excel表格为例,初步分析,整个表格就是一个模型,但是这划分的细度显然不够。
再细想,我们不难得出四类模型:corner,cell,colHeader,rowHeader。
下面给出这四类模型的代码:

export default class Corner {
  constructor(text) {
    this.text = text;
  }
}

export default class Cell {
  constructor() {
    this.text = '';
  }
}

export default class ColHeader {
  constructor(text) {
    this.text = text;
    this.width = 64;
  }
}

export default class RowHeader {
  constructor(text) {
    this.text = text;
    this.height = 25;
  }
}

这代码结构为何如此简单,连基本的事件绑定都没有,这怎么可能实现前端的那个页面效果?
对,没错,model层仅仅这点代码肯定是不够的。
首先,为什么不在这里不绑定事件?因为事件是ui效果,属于view层的管理,不应该在这里绑定。
没有事件绑定,这些模型有何用处?原理很简单,model层,我可以通过把前端Excel拆解成四部分,每一部分的后台数据都在model层进行管理,所以完整的model层还需要一个管理类!

Sheet:

/* eslint-disable max-len */
import RowHeader from './rowHeader.js';

import ColHeader from './colHeader.js';

import Cell from './cell.js';

import Corner from './corner.js';

import SelectRange from './selectRange.js';

import Coordinate from './coordinate.js';

export default class Sheet {
  constructor() {
    this._onDataChangedCallback = null;
    this.rowHeaders = [];
    this.colHeaders = [];
    this.cells = []; // Two-dimensional array
    this.corner = null;
    this.activeCellCoordinate = new Coordinate(0, 0);
    this.boundaryCellCoordinate = new Coordinate(-1, -1);
    this.selectRange = new SelectRange(); // Vice Business
  }

  init(rowCount, colCount) {
    this.corner = new Corner('/');
    for (let i = 0; i < rowCount; i++) {
      this.rowHeaders.push(new RowHeader(`${i + 1}`));
    }
    for (let i = 0; i < colCount; i++) {
      this.colHeaders.push(new ColHeader(String.fromCharCode('A'.charCodeAt() + i)));
    }
    for (let i = 0; i < colCount; i++) {
      this.cells[i] = [];
      for (let j = 0; j < rowCount; j++) {
        this.cells[i].push(new Cell());
      }
    }
  }

  initEvent(onDataChangedCallback) {
    this._onDataChangedCallback = onDataChangedCallback;
  }

  onDataChange(data) {
    if (this._onDataChangedCallback != null) {
      this._onDataChangedCallback(data);
    }
  }

  changeSelectRange(selectType, startColIndex, startRowIndex, endColIndex, endRowIndex, activeCellColIndex, activeCellRowIndex) {
    if (this.selectRange.equal(selectType, startColIndex, startRowIndex, endColIndex, endRowIndex)
      && this.activeCellCoordinate.colIndex === activeCellColIndex && this.activeCellCoordinate.rowIndex === activeCellRowIndex) {
      this.onDataChange({ result: false });
      return;
    }
    this.selectRange.selectType = selectType;
    this.selectRange.selectUpperLeftCoordinate.colIndex = startColIndex;
    this.selectRange.selectUpperLeftCoordinate.rowIndex = startRowIndex;
    this.selectRange.selectBottomRightCoordinate.colIndex = endColIndex;
    this.selectRange.selectBottomRightCoordinate.rowIndex = endRowIndex;
    this.activeCellCoordinate.colIndex = activeCellColIndex;
    this.activeCellCoordinate.rowIndex = activeCellRowIndex;
    this.onDataChange({ result: true });
  }

  updateCellText(colIndex, rowIndex, text) {
    this.activeCellCoordinate.colIndex = colIndex;
    this.activeCellCoordinate.rowIndex = rowIndex;
    this.cells[colIndex][rowIndex].text = text;
    this.boundaryCellCoordinate.colIndex = Math.max(this.boundaryCellCoordinate.colIndex, colIndex);
    this.boundaryCellCoordinate.rowIndex = Math.max(this.boundaryCellCoordinate.rowIndex, rowIndex);
  }

  // final status
  changeColWidth(index, count, width) {
    if (count === 1) {
      let actualWidth = width + this.colHeaders[index].width;
      if (actualWidth >= 0) {
        this.colHeaders[index].width = width;
      } else {
        this.colHeaders[index].width = 0;
        let i = index - 1;
        while (i >= 0 && actualWidth < 0) {
          actualWidth += this.colHeaders[i].width;
          this.colHeaders[i].width = actualWidth >= 0 ? actualWidth : 0;
          i--;
        }
      }
    } else {
      const actualWidth = width < 0 ? 0 : width;
      for (let i = index; i < index + count; i++) {
        this.colHeaders[i].width = actualWidth;
      }
    }
  }

  changeRowHeight(index, count, height) {
    if (count === 1) {
      let actualHeight = height + this.rowHeaders[index].height;
      if (actualHeight >= 0) {
        this.rowHeaders[index].height = height;
      } else {
        this.rowHeaders[index].height = 0;
        let i = index - 1;
        while (i >= 0 && actualHeight < 0) {
          actualHeight += this.rowHeaders[i].height;
          this.rowHeaders[i].height = actualHeight >= 0 ? actualHeight : 0;
          i--;
        }
      }
    } else {
      const actualHeight = height < 0 ? 0 : height;
      for (let i = index; i < index + count; i++) {
        this.rowHeaders[i].height = actualHeight;
      }
    }
  }

  createRow() {
    const newCells = [];
    for (let j = 0; j < this.colHeaders.length; j++) {
      newCells[j] = new Cell();
    }
    return newCells;
  }

  resetRowText() {
    for (let i = 0; i < this.rowHeaders.length; i++) {
      this.rowHeaders[i].text = `${i + 1}`;
    }
  }

  addRows(index, count, height) {
    if (this.boundaryCellCoordinate.rowIndex + count >= this.rowHeaders.length) {
      throw new 'This behavior will delete existing data!'();
    }
    if (index <= this.boundaryCellCoordinate.rowIndex) {
      this.boundaryCellCoordinate.rowIndex += count;
    }
    for (let i = 0; i < count; i++) {
      this.cells.splice(index, 0, this.createRow());
      this.cells.pop();
      const newRowHeader = new RowHeader('1');
      newRowHeader.height = height;
      this.rowHeaders.splice(index, 0, newRowHeader);
      this.rowHeaders.pop();
    }
    this.resetRowText();
  }

  getNewRowIndex(index) {
    for (let j = index - 1; j >= 0; j--) {
      for (let i = 0; i < this.colHeaders.length; i++) {
        if (this.cells[i][j].text !== '') {
          return j;
        }
      }
    }
    return -1;
  }

  removeRows(index, count) {
    if (this.boundaryCellCoordinate.rowIndex >= index && this.boundaryCellCoordinate.rowIndex < index + count) {
      this.boundaryCellCoordinate.rowIndex = this.getNewRowIndex(index);
      this.boundaryCellCoordinate.colIndex = this.getNewColIndex(index);
    }
    for (let i = 0; i < count; i++) {
      this.cells.splice(index, 1);
      this.cells.push(this.createRow());
      this.rowHeaders.splice(index, 1);
      this.rowHeaders.push(new RowHeader('1'));
    }
    this.resetRowText();
  }

  resetColText() {
    for (let i = 0; i < this.colHeaders.length; i++) {
      this.colHeaders[i].text = String.fromCharCode('A'.charCodeAt() + i);
    }
  }

  addCols(index, count, width) {
    if (this.boundaryCellCoordinate.colIndex + count >= this.colHeaders.length) {
      throw new 'This behavior will delete existing data!'();
    }
    if (index <= this.boundaryCellCoordinate.colIndex) {
      this.boundaryCellCoordinate.colIndex += count;
    }
    for (let i = 0; i < count; i++) {
      for (let j = 0; j < this.rowHeaders.length; j++) {
        this.cells[j].splice(index, 0, new Cell());
        this.cells[j].pop();
      }
      const newColHeader = new ColHeader('A');
      newColHeader.width = width;
      this.colHeaders.splice(index, 0, newColHeader);
      this.colHeaders.pop();
    }
    this.resetColText();
  }

  getNewColIndex(index) {
    for (let i = index - 1; i >= 0; i--) {
      for (let j = 0; j < this.removeRows.length; j++) {
        if (this.cells[i][j].text !== '') {
          return i;
        }
      }
    }
    return -1;
  }

  // index most left index
  removeCols(index, count) {
    if (this.boundaryCellCoordinate.colIndex >= index && this.boundaryCellCoordinate.colIndex < index + count) {
      this.boundaryCellCoordinate.rowIndex = this.getNewRowIndex(index);
      this.boundaryCellCoordinate.colIndex = this.getNewColIndex(index);
    }
    for (let i = 0; i < count; i++) {
      for (let j = 0; j < this.rowHeaders.length; j++) {
        this.cells[j].splice(index, 1);
        this.cells[j].push(new Cell());
      }
      this.colHeaders.splice(index, 1);
      this.colHeaders.push(new ColHeader('A'));
    }
    this.resetColText();
  }
}

Coordinate:

export default class Coordinate {
  constructor(colIndex, rowIndex) {
    this.colIndex = colIndex;
    this.rowIndex = rowIndex;
  }
}

SelectRange :

/* eslint-disable max-len */
import Coordinate from './coordinate.js';

export default class SelectRange {
  constructor() {
    this.selectType = 'cell';
    this.selectUpperLeftCoordinate = new Coordinate(0, 0);
    this.selectBottomRightCoordinate = new Coordinate(0, 0);
  }

  equal(selectType, startColIndex, startRowIndex, endColIndex, endRowIndex) {
    return selectType === this.selectType
      && startColIndex === this.selectUpperLeftCoordinate.colIndex && startRowIndex === this.selectBottomRightCoordinate.rowIndex
      && endColIndex === this.selectBottomRightCoordinate.colIndex && endRowIndex === this.selectBottomRightCoordinate.rowIndex;
  }
}

sheet类的代码有点长,我做点解释:
首先看类的属性,除了我上述提到的四个基本模型外,
Coordinate :记录二维坐标轴里点的坐标
_onDataChangedCallback:用于与controller层进行交互的事件回调函数
activeCellCoordinate:用于记录记录支持编辑的方框的坐标,即那个白色的小方框
boundaryCellCoordinate:用于记录有数据的最外边的cell坐标(在add函数里被用到)
selectRange:选择范围,记录选择的类型以及大方框的坐标(左上角和右下角)

除了这些属性外,还有一些model层进行数据管理必备的函数:
表格的初始化;回调函数的控制;更换选择区域;更新cell元素的文本;行列resize的相应数据更新;增删行列的数据更新。
到此,model层就以搭建完成,view层的功能就是简单来说就是页面渲染,属于这个模块的任务有表格的创建以及各类事件的绑定,还有页面显示效果的更新。而controller层的功能就是具体实现view层所绑定的事件然后改变model层的数据最后再返回给view层更新页面显示。
这次除了代码类型的改变外,还在上次的基础上新增了一些其它的功能,目前view层和controller层的代码还未完全实现,所以具体功能这一部分留到下一期。
这里我给出此项目的github源码地址(还未更新完整):Excel完整代码
但是后续的代码更新也在这个网址中,下期见。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值