新版Excel(完结版)
上篇博客講述了此mvc版本的Excel的model層實現(xiàn),這里我繼續(xù)上篇博客的內(nèi)容,把剩下的內(nèi)容敘述完。最終效果:新版Excel
首先,我先描述下次版本的Excel在功能上與上個版本的相同點與不同點:
- 在表的結(jié)構(gòu)上,此次表為動態(tài)固定表,也是Excel表格的模式,即行列大小是固定的,所謂的增刪行列就是增加并刪除或者刪除并增加,具體效果可以自行嘗試。
- 此次的選擇效果除了上次的cell的多選效果外,還新增了行列的多選效果。
- 在編輯效果上除了上次支持的enter鍵以及鼠標焦點切換來確定輸入內(nèi)容外,此次新增了tab鍵確定輸入內(nèi)容的效果。
- 此次在resize功能上除了本身支持的resize效果外還擴展了反向壓縮resize的效果。
- 最重要的一個新增功能,上述的行列增刪功能以及resize功能都支持多選操作的效果!
主要功能就這些,還有一些邊界的細節(jié)功能我在這就不敘述了,大家有興趣可以自己玩玩。
下面是此版項目的目錄結(jié)構(gòu):
除了上述模塊外,還有一個單元測試模塊未顯示,由于此版項目代碼量很多,所以在這里我不再給出全部的源碼,大家可以通過此github鏈接下載完整的源代碼:新版Excel源碼
我先講解此mvc項目的運轉(zhuǎn)流程:
首先最外層的函數(shù)接口是main.js文件,此文件調(diào)用controller層的initController.js文件的initsheet函數(shù),此函數(shù)只做了兩件事,調(diào)用model層的表格數(shù)據(jù)初始化函數(shù),然后調(diào)用將model層數(shù)據(jù)傳給view層來初始化UI頁面。
然后,在view層的初始化函數(shù)里綁定此次項目所做功能所需的一些事件,而事件的具體實現(xiàn)都交給controller層來管理。
/* eslint-disable max-len */ import portray from './portray.js';import RowHeaderController from '../controllers/rowHeaderController.js'; import ColHeaderController from '../controllers/colHeaderController.js'; import cornerClickHandler from '../controllers/cornerController.js'; import Cell from '../controllers/cellController.js'; import Resize from '../controllers/resizeController.js'; /* eslint-disable max-len */ // time out event resize const colHeaderController = new ColHeaderController(); const rowHeaderController = new RowHeaderController(); const cell = new Cell(); const resize = new Resize(); function createCorner(corner) {const thCorner = document.createElement('th');thCorner.innerText = corner.text;thCorner.classList.add('corner');thCorner.addEventListener('click', (e) => {cornerClickHandler(e);}, false);return thCorner; }function createColHeader(colHeader) {const thColHeader = document.createElement('th');const resizeE = document.createElement('div');const span = document.createElement('span');thColHeader.classList.add('colHeader');resizeE.classList.add('resizeE');thColHeader.appendChild(span);thColHeader.appendChild(resizeE);thColHeader.children[0].innerText = colHeader.text;resizeE.addEventListener('mousedown', resize.resizeColHeaderDownHandler, false);thColHeader.style.width = `${colHeader.width}px`;thColHeader.addEventListener('click', (e) => {ColHeaderController.colHeaderClickHandler(e);}, false);thColHeader.addEventListener('mousedown', (e) => {colHeaderController.colHeaderDownHandler(e);}, false);thColHeader.addEventListener('mouseup', (e) => {colHeaderController.colHeaderUpHandler(e);}, false);thColHeader.addEventListener('mousemove', (e) => {colHeaderController.colHeaderMoveHandler(e);}, false);thColHeader.addEventListener('contextmenu', (e) => {ColHeaderController.colHeaderMenuHandler(e);}, false);return thColHeader; }function createRowHeader(rowHeader) {const tdRowHeader = document.createElement('td');const resizeS = document.createElement('div');const span = document.createElement('span');tdRowHeader.classList.add('rowHeader');resizeS.classList.add('resizeS');tdRowHeader.appendChild(span);tdRowHeader.appendChild(resizeS);tdRowHeader.children[0].innerText = rowHeader.text;tdRowHeader.style.height = `${rowHeader.height}px`;resizeS.addEventListener('mousedown', resize.resizeRowHeaderDownHandler, false);tdRowHeader.addEventListener('click', (e) => {RowHeaderController.rowHeaderClickHandler(e);}, false);tdRowHeader.addEventListener('mousedown', (e) => {rowHeaderController.rowHeaderDownHandler(e);}, false);tdRowHeader.addEventListener('mouseup', (e) => {rowHeaderController.rowHeaderUpHandler(e);}, false);tdRowHeader.addEventListener('mousemove', (e) => {rowHeaderController.rowHeaderMoveHandler(e);}, false);tdRowHeader.addEventListener('contextmenu', (e) => {RowHeaderController.rowHeaderMenuHandler(e);}, false);return tdRowHeader; }function createCell(dataIndex, colWidth) {const tdCell = document.createElement('td');tdCell.style.maxWidth = `${colWidth}px`;tdCell.classList.add('cell');tdCell.setAttribute('data-index', dataIndex);tdCell.addEventListener('click', (e) => {Cell.cellClickHandler(e);}, false);tdCell.addEventListener('dblclick', (e) => {cell.cellDbClickHandler(e);}, false);tdCell.addEventListener('mousedown', (e) => {cell.cellDownHandler(e);}, false);tdCell.addEventListener('mousemove', (e) => {cell.cellMoveHandler(e);}, false);return tdCell; } function BindButtonEvent() {const addButton = document.getElementsByClassName('add')[0];const removeButton = document.getElementsByClassName('remove')[0];addButton.addEventListener('click', () => {colHeaderController.addColHeaderHandler();}, false);removeButton.addEventListener('click', () => {colHeaderController.removeColHeaderHandler();}, false);addButton.addEventListener('click', () => {rowHeaderController.addRowHeaderHandler();}, false);removeButton.addEventListener('click', () => {rowHeaderController.removeRowHeaderHandler();}, false); } export default function initTable(corner, rowHeaders, colHeaders,selectType, activeCellCoordinate, selectUpperLeftCoordinate, selectBottomRightCoordinate) {const body = document.getElementsByTagName('body')[0];const table = document.createElement('table');table.classList.add('table');body.appendChild(table);const trFirst = document.createElement('tr');trFirst.appendChild(createCorner(corner));colHeaders.forEach((colHeader) => trFirst.appendChild(createColHeader(colHeader)));table.appendChild(trFirst);for (let i = 0; i < rowHeaders.length; i++) {const trOther = document.createElement('tr');trOther.appendChild(createRowHeader(rowHeaders[i]));for (let j = 0; j < colHeaders.length; j++) {trOther.appendChild(createCell(colHeaders[j].text, colHeaders[j].width));}table.appendChild(trOther);}document.addEventListener('mouseup', (e) => {cell.cellUpHandler(e);}, false);const bigFrame = document.createElement('div');bigFrame.classList.add('bigFrame');const smallFrame = document.createElement('div');smallFrame.classList.add('smallFrame');table.appendChild(bigFrame);table.appendChild(smallFrame);portray(selectType, selectUpperLeftCoordinate, selectBottomRightCoordinate, activeCellCoordinate);BindButtonEvent(); }每次我們在前端ui頁面觸發(fā)事件后,controller層來執(zhí)行具體的事件邏輯,然后更改model層的數(shù)據(jù),最后傳遞給view層來更新頁面。這不就是最簡單的mvc的核心流程思想嗎
至于每個模塊里的每個文件是干什么的,從文件名應(yīng)該就能理解,這里我就不多講解。
最后,我講解一下反向壓縮resize的算法思想:
首先,我們明白最基本的resize功能的實現(xiàn)邏輯,無非就三步:在down事件里記錄下初識位置,在move的時候根據(jù)初始值來確定該變量并及時更新,最后再up里重置數(shù)據(jù)。
由于反向壓縮resize操作會改變其它元素的數(shù)據(jù),所以下move時候的及時更新就很有可能會出現(xiàn)重復(fù)計算的問題,所以我在這里采取的解決方案是通過每次在down的時候深拷貝一份model層的原始數(shù)據(jù),在move層里使用深拷貝的那份數(shù)據(jù)進行邏輯操作,如此一來實時更新model層數(shù)據(jù)也便無后顧之憂。不得不說,此方案雖然不高效但是的確能解決問題。
到此,此項目的講解就結(jié)束了,如果有看源碼不明白的地方或者對源碼有更好的簡潔,歡迎評論區(qū)留言,我會及時回復(fù)。
總結(jié)
以上是生活随笔為你收集整理的新版Excel(完结版)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【信号与烧脑的IP地址介绍、计算】
- 下一篇: [Swagger] Asciidoc 配