Skip to content

Commit 1bbf805

Browse files
committed
Add new project 08
1 parent 573888d commit 1bbf805

File tree

1 file changed

+283
-0
lines changed

1 file changed

+283
-0
lines changed

08-excel-js/index.html

+283
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="UTF-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<title>Excel.js</title>
8+
<style>
9+
body {
10+
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
11+
}
12+
13+
*,
14+
*::before,
15+
*::after {
16+
box-sizing: border-box;
17+
}
18+
19+
img {
20+
max-width: 20px;
21+
height: auto;
22+
}
23+
24+
table {
25+
border-collapse: collapse;
26+
}
27+
28+
thead,
29+
tr td:first-child {
30+
background: #eee;
31+
}
32+
33+
th,
34+
td {
35+
border: 1px solid #ccc;
36+
font-weight: normal;
37+
font-size: 12px;
38+
text-align: center;
39+
width: 64px;
40+
height: 20px;
41+
vertical-align: middle;
42+
position: relative
43+
}
44+
45+
/* td:active {
46+
border-radius: 2px;
47+
outline: 2px solid #09f;
48+
} */
49+
50+
span,
51+
input {
52+
position: absolute;
53+
inset: 0;
54+
vertical-align: middle;
55+
display: inline-flex;
56+
justify-content: center;
57+
align-items: center;
58+
}
59+
60+
input {
61+
border: 0;
62+
opacity: 0;
63+
pointer-events: none;
64+
width: 100%;
65+
border-radius: 2px;
66+
67+
&:focus {
68+
opacity: 1;
69+
outline: 2px solid #09f;
70+
}
71+
}
72+
73+
.selected {
74+
background: rgb(174, 223, 255);
75+
}
76+
77+
th.selected {
78+
background: rgb(146, 211, 255);
79+
}
80+
</style>
81+
<script type="module">
82+
const $ = el => document.querySelector(el)
83+
const $$ = el => document.querySelectorAll(el)
84+
85+
const $table = $('table')
86+
const $head = $('thead')
87+
const $body = $('tbody')
88+
89+
const ROWS = 10
90+
const COLUMNS = 5
91+
const FIRST_CHAR_CODE = 65
92+
93+
const times = length => Array.from({ length }, (_, i) => i)
94+
const getColumn = i => String.fromCharCode(FIRST_CHAR_CODE + i)
95+
96+
let selectedColumn = null
97+
98+
let STATE = times(COLUMNS)
99+
.map(i => times(ROWS).map(j => ({ computedValue: 0, value: 0 })))
100+
101+
console.log(STATE)
102+
103+
function updateCell({ x, y, value }) {
104+
const newState = structuredClone(STATE)
105+
const constants = generateCellsConstants(newState)
106+
107+
const cell = newState[x][y]
108+
109+
cell.computedValue = computeValue(value, constants) // -> span
110+
cell.value = value // -> input
111+
112+
newState[x][y] = cell
113+
114+
computeAllCells(newState, generateCellsConstants(newState))
115+
116+
STATE = newState
117+
118+
renderSpreadSheet()
119+
}
120+
121+
function generateCellsConstants(cells) {
122+
return cells.map((rows, x) => {
123+
return rows.map((cell, y) => {
124+
const letter = getColumn(x) // -> A
125+
const cellId = `${letter}${y + 1}` // -> A1
126+
return `const ${cellId} = ${cell.computedValue};`
127+
}).join('\n')
128+
}).join('\n')
129+
}
130+
131+
function computeAllCells(cells, constants) {
132+
console.log('computeAllCells')
133+
cells.forEach((rows, x) => {
134+
rows.forEach((cell, y) => {
135+
const computedValue = computeValue(cell.value, constants)
136+
cell.computedValue = computedValue
137+
})
138+
})
139+
}
140+
141+
function computeValue(value, constants) {
142+
if (typeof value === 'number') return value
143+
if (!value.startsWith('=')) return value
144+
145+
const formula = value.slice(1)
146+
147+
let computedValue
148+
try {
149+
computedValue = eval(`(() => {
150+
${constants}
151+
return ${formula};
152+
})()`)
153+
} catch (e) {
154+
computedValue = `!ERROR: ${e.message}`
155+
}
156+
157+
console.log({ value, computedValue })
158+
159+
return computedValue
160+
}
161+
162+
const renderSpreadSheet = () => {
163+
const headerHTML = `<tr>
164+
<th></th>
165+
${times(COLUMNS).map(i => `<th>${getColumn(i)}</th>`).join('')}
166+
</tr>`
167+
168+
$head.innerHTML = headerHTML
169+
170+
const bodyHTML = times(ROWS).map(row => {
171+
return `<tr>
172+
<td>${row + 1}</td>
173+
${times(COLUMNS).map(column => `
174+
<td data-x="${column}" data-y="${row}">
175+
<span>${STATE[column][row].computedValue}</span>
176+
<input type="text" value="${STATE[column][row].value}" />
177+
</td>
178+
`).join('')}
179+
</tr>`
180+
}).join('')
181+
182+
$body.innerHTML = bodyHTML
183+
}
184+
185+
$body.addEventListener('click', event => {
186+
const td = event.target.closest('td')
187+
if (!td) return
188+
189+
const { x, y } = td.dataset
190+
const input = td.querySelector('input')
191+
const span = td.querySelector('span')
192+
193+
const end = input.value.length
194+
input.setSelectionRange(end, end)
195+
input.focus()
196+
197+
$$('.selected').forEach(el => el.classList.remove('selected'))
198+
selectedColumn = null
199+
200+
input.addEventListener('keydown', (event) => {
201+
if (event.key === 'Enter') input.blur()
202+
})
203+
204+
input.addEventListener('blur', () => {
205+
console.log({ value: input.value, state: STATE[x][y].value })
206+
207+
if (input.value === STATE[x][y].value) return
208+
209+
updateCell({ x, y, value: input.value })
210+
}, { once: true })
211+
})
212+
213+
$head.addEventListener('click', event => {
214+
const th = event.target.closest('th')
215+
if (!th) return
216+
217+
const x = [...th.parentNode.children].indexOf(th)
218+
if (x <= 0) return
219+
220+
selectedColumn = x - 1
221+
222+
$$('.selected').forEach(el => el.classList.remove('selected'))
223+
th.classList.add('selected')
224+
$$(`tr td:nth-child(${x + 1})`).forEach(el => el.classList.add('selected'))
225+
})
226+
227+
document.addEventListener('keydown', event => {
228+
if (event.key === 'Backspace' && selectedColumn !== null) {
229+
times(ROWS).forEach(row => {
230+
updateCell({ x: selectedColumn, y: row, value: '' })
231+
})
232+
renderSpreadSheet()
233+
}
234+
})
235+
236+
document.addEventListener('copy', event => {
237+
if (selectedColumn !== null) {
238+
const columnValues = times(ROWS).map(row => {
239+
return STATE[selectedColumn][row].computedValue
240+
})
241+
242+
event.clipboardData.setData('text/plain', columnValues.join('\n'))
243+
event.preventDefault()
244+
}
245+
})
246+
247+
document.addEventListener('click', event => {
248+
const { target } = event
249+
250+
const isThClicked = target.closest('th')
251+
const isTdClicked = target.closest('td')
252+
253+
if (!isThClicked && !isTdClicked) {
254+
$$('.selected').forEach(el => el.classList.remove('selected'))
255+
selectedColumn = null
256+
}
257+
})
258+
259+
renderSpreadSheet()
260+
</script>
261+
</head>
262+
263+
<body>
264+
<img
265+
src="https://upload.wikimedia.org/wikipedia/commons/thumb/7/73/Microsoft_Excel_2013-2019_logo.svg/1200px-Microsoft_Excel_2013-2019_logo.svg.png" />
266+
267+
<table>
268+
<thead></thead>
269+
<tbody></tbody>
270+
</table>
271+
</body>
272+
273+
</html>
274+
275+
276+
<!--
277+
278+
- Añadir la funconalidad de filas
279+
- Haz la suma por rangos=A1:A20
280+
- Seleccionar por celas
281+
282+
283+
-->

0 commit comments

Comments
 (0)