Webprogramozás
Horváth Győző
egyetemi docens
horvath.gyozo@inf.elte.hu
1117 Budapest, Pázmány Péter sétány 1/c., 4.725
{ utasítások }
[1, 2, 3]
– tömb{ nev: "Győző" }
– objektumdocument.querySelector('css selector')
document.querySelectorAll('css selector')
innerHTML
)elem.addEventListener(type, handler)
event
)preventDefault
)delegate
)Kódszervezés az üzleti logikai rétegben
// math.js
const add = (a, b) => a + b;
// app.js
console.log(add(40, 2));
<body>
<!-- ... -->
✒><script src="math.js"></script><✒
<script src="app.js"></script>
</body>
// math.js
const add = (a, b) => a + b;
export { add };
// app.js
import { add } from "./math.js";
console.log(add(40, 2));
<body>
<!-- ... -->
<script ✒>type="module"<✒ src="app.js"></script>
</body>
// in-place export
export const add = (a, b) => a + b;
export const multiply = (a, b) => a * b;
// separate export
const add = (a, b) => a + b;
const multiply = (a, b) => a * b;
export { add, multiply };
// default export
export default const add = (a, b) => a + b;
// rename exports
const add = (a, b) => a + b;
const multiply = (a, b) => a * b;
export { add as customAdd, multiply as customMultiply };
// export from module
export * from "./other.js";
// import entities
import { add, multiply } from "./math.js";
// import defaults
import add from "./math.js";
// rename imports
import { add as mathAdd } from "./math.js";
// import module object
import * as MyMath from "./math.js";
// import just for side effects
import "./something.js";
// import URL
import * as MyMath from "http://some.where.hu/math.js"
serve
package
npx serve
http://localhost:5000
// Helper/utility function
function range(n) {
return Array.from({length: n}, (e, i) => i + 1);
}
// HTML generator
function genList(list) {
return `<ul>${list.map(e => `<li>${e}</li>`).join('')}</ul>`;
}
// Business logic
const add = (a, b) => a + b;
// Event handler
function onClick(e) {
// ...
}
class Tile {
constructor(x, y) {
this.x = y;
this.y = y;
}
get coords() {
return {x, y};
}
distance(tile) {
return Math.sqrt(
(tile.x - this.x) ** 2 + (tile.y - this.y) ** 2
);
}
}
Globális változók és metódusok
let number = 0;
function increase() {
number++;
}
function init() {
number = 0;
}
increase();
console.log(number); // 1
Objektum, ~névtér
const game = {
number: 0,
increase: function() {
this.number++;
},
init: function () {
this.number = 0;
}
}
game.init();
game.increase();
console.log(game.number); // 1
Osztály
class Game {
constructor() {
this.number = 0;
}
increase() {
this.number++;
}
}
const game = new Game();
game.increase();
console.log(game.number); // 1
Függvény, saját hatókörrel (felfedő modul minta), IIFE
const game = (function () {
let number = 0;
function increase() {
number++;
}
function init() {
number = 0;
}
function getNumber() {
return number;
}
return { init, increase, getNumber};
})();
game.init();
game.increase();
console.log(game.getNumber()); // 1
Modul, függvény
// game.js
let number = 0;
export function increase() {
number++;
}
export function init() {
number = 0;
}
export function getNumber() {
return number;
}
// main.js
import { increase, init, getNumber } from "./game.js";
increase();
console.log(getNumber()); // 1
Modul, osztály
// game.js
export class Game {
constructor() {
this.number = 0;
}
increase() {
this.number += 1;
}
}
import { Game } from './game.js';
const game = new Game();
game.increase();
console.log(game.number);
// ✔ Ebből könnyű HTML-t generálni
let count = 3
const movies = [
{ id: 1, title: 'The Shack', year: 2017, seen: true },
{ id: 2, title: 'Fireproof', year: 2008, seen: true },
{ id: 3, title: 'Courageous', year: 2011, seen: false },
]
<!-- ✔ Előfordulnak olyan adatok, amelyeket a HTML attribútumban
kell tárolni -->
<div data-id="1">The Shack</div>
<!-- ✖ Ebből nehéz az adatot kinyerni -->
<ul>
<li data-id="1" class="seen" >The Shack (2017)</li>
<li data-id="2" class="seen" >Fireproof (2008)</li>
<li data-id="3" class="unseen">Courageous (2011)</li>
</ul>
<input id="newItem">
<button id="addItem">Add</button>
<ul id="todoList">✒>
<li>Buy milk</li>
<li>Learn JavaScript</li>
<✒</ul>
const todoList = document.querySelector("#todoList");
const button = document.querySelector("#addItem");
const input = document.querySelector("#newItem");
function handleButtonClick() {
const newItem = input.value;
✒>todoList.innerHTML += `<li>${newItem}</li>`;<✒
}
button.addEventListener("click", handleButtonClick);
<input id="newItem">
<button id="addItem">Add</button>
<ul id="todoList">
<li>Buy milk</li>
<li>Learn JavaScript</li>
</ul>
const todoList = document.querySelector("#todoList");
const button = document.querySelector("#addItem");
const input = document.querySelector("#newItem");
function handleButtonClick() {
const newItem = input.value;
✒>const newListItem = document.createElement("li");<✒
newListItem.innerHTML = newItem;
✒>todoList.appendChild(newListItem);<✒
}
button.addEventListener("click", handleButtonClick);
Todo lista elemei
<ul id="todoList">✒>
<li>Buy milk</li>
<li>Learn JavaScript</li>
<✒</ul>
const todoList = document.querySelector("#todoList");
const listItems = document.querySelectorAll("li");
function handleButtonClick() {
// Input
const newItem = input.value;
✒>const listContent = Array.from(listItems).map(li => li.innerText);<✒
// Process
listContent.push(newItem);
// Output
const newListItem = document.createElement("li");
newListItem.innerHTML = newItem;
todoList.appendChild(newListItem);
}
✒>const list = [];<✒
const input = document.querySelector("input");
const button = document.querySelector("button");
const todoList = document.querySelector("ul");
function handleButtonClick() {
// Input
const newItem = input.value;
// Process
✒>list.push(newItem);<✒
// do something else with the data
// Output
const newElement = document.createElement("li");
newElement.innerHTML = newItem;
✒>todoList.appendChild(newElement);<✒
}
button.addEventListener("click", handleButtonClick);
(Imperatív felületkezelés)
A felület mint az adat leképezése (függvény)
const list = [];
✒>function renderList(list) {
return list.map(item => `<li>${item}</li>`).join("");
}<✒
// ...
function handleButtonClick() {
// Input
const newItem = input.value;
// Process
list.push(newItem);
// Output
✒>todoList.innerHTML = renderList(list);<✒
}
button.addEventListener("click", handleButtonClick);
(Deklaratív felületkezelés)
function handleButtonClick() {
const newItem = input.value;
list.push(newItem);
// Output
✒>const newElement = document.createElement("li");
newElement.innerHTML = newItem;
todoList.appendChild(newElement);<✒
}
✒>function renderList(list) {
return list.map(item => `<li>${item}</li>`).join("");
}<✒
const input = document.querySelector("input");
const button = document.querySelector("button");
const todoList = document.querySelector("ul");
function handleButtonClick() {
// Input
const newItem = input.value;
// Process
list.push(newItem);
// Output
✒>todoList.innerHTML = renderList(list);<✒
}
Számkitalálós játék
<input id="tipp">
<button id="tippGomb">Tipp!</button>
<ul id="tippek"></ul>
let kitalalandoSzam = 42;
let vege = false;
const tippek = [50, 25, 38, 44];
function tipp(tippeltSzam) {
tippek.push(tippeltSzam);
vege = (tippeltSzam === kitalalandoSzam);
}
function veletlenEgesz(min, max) {
const veletlen = Math.random();
const tartomany = max - min + 1;
return Math.trunc(veletlen * tartomany) + min;
}
const tippInput = document.querySelector("#tipp");
const gomb = document.querySelector("#tippGomb");
const tippLista = document.querySelector("#tippek");
gomb.addEventListener("click", tippeles);
function tippeles(e) {
// beolvasás
const tippeltSzam = parseInt(tippInput.value);
// feldolgozás
tipp(tippeltSzam);
// kiírás
// deklaratív felületkezelés
tippLista.innerHTML = tippek.map(szam =>
`<li>${szam} (${hasonlit(szam, kitalalandoSzam)})</li>`
).join("");
// imperatív felületkezelés
gomb.disabled = vege;
}
function hasonlit(szam, kitalalandoSzam) {
if (szam < kitalalandoSzam) return "nagyobb";
if (szam > kitalalandoSzam) return "kisebb";
return "egyenlő";
}
function genLista(tippek, kitalalandoSzam) {
return tippek.map(szam =>
`<li>${szam} (${hasonlit(szam, kitalalandoSzam)})</li>`
).join("");
}
function tippeles(e) {
// beolvasás
const tippeltSzam = parseInt(tippInput.value);
// feldolgozás
tipp(tippeltSzam);
// kiírás
tippLista.innerHTML = genLista(tippek, kitalalandoSzam);
gomb.disabled = vege;
}
Memóriajáték
<div id="main">
<form action="">
n = <input type="number" id="n" value="3"> <br>
m = <input type="number" id="m" value="4">
<button type="button">Start new game</button>
</form>
<div id="board"></div>
<div id="status"></div>
</div>
<script src="game.js"></script>
<script src="index.js"></script>
<!-- Inside #board -->
<table>
<tr>
<td>
<div class="card">
<div class="front">1</div>
<div class="back"></div>
</div>
</td>
</tr>
</table>
let board = []
let gameState = 0 // 0, 1, 2
function init() {
board = []
gameState = 0
}
function initBoard(n, m) {
const numbers = Array(n * m / 2).fill(0).map((e, i) => i + 1)
const values = [...numbers, ...numbers].sort((a, b) => Math.random() < 0.5 ? 1 : -1)
board = Array(n).fill(0).map(() => Array(m).fill(0).map(() => ({
value: values.shift(),
flipped: false,
solved: false,
})))
gameState = 1
}
function selectCard(i, j) {
const flipped = flippedCards()
if (isFlipped(i, j) || isSolved(i, j) || flipped.length === 2) {
return
}
// flip over
board[i][j].flipped = true
flipped.push(board[i][j])
// check cards
if (flipped.length === 2 && flipped[0].value === flipped[1].value) {
flipped.forEach(card => {
card.solved = true
card.flipped = false
})
}
// check win
if (isWin()) {
gameState = 2
}
}
function isFlipped(i, j) {
return board[i][j].flipped
}
function isSolved(i, j) {
return board[i][j].solved
}
function turnBack() {
board.forEach(row => row.forEach(card => card.flipped = false))
}
function isWin() {
return board.every(row => row.every(card => card.solved))
}
function flippedCards() {
return board.flatMap(row => row.filter(card => card.flipped))
}
function countSolved() {
return board.flatMap(row => row.filter(card => card.solved)).length
}
const form = document.querySelector('form')
const button = form.querySelector('button')
const boardDiv = document.querySelector('#board')
const statusDiv = document.querySelector('#status')
function xyCoord(card) {
const td = card.closest('td')
const x = td.cellIndex
const tr = td.parentNode
const y = tr.sectionRowIndex
return { x, y }
}
button.addEventListener('click', onGenerate)
function onGenerate(e) {
e.preventDefault()
const n = form.querySelector('#n').valueAsNumber
const m = form.querySelector('#m').valueAsNumber
if (n * m % 2 !== 0) {
return
}
initBoard(n, m)
render()
}
boardDiv.addEventListener('click', onSelectCard)
function onSelectCard(e) {
const card = e.target.closest('.card')
if (boardDiv.contains(card)) {
if (flippedCards().length === 2) {
return
}
const {x, y} = xyCoord(card)
selectCard(y, x)
✒>render()<✒
if (flippedCards().length === 2) {
setTimeout(turnBackAndRender, 1000)
}
}
}
function turnBackAndRender() {
turnBack()
✒>render()<✒
}
function render() {
renderBoard(board)
renderStatus(countSolved(), gameState)
}
function renderBoard(board) {
boardDiv.innerHTML = `
<table>
${board.map(row => `
<tr>
${row.map(card => `
<td>
<div class="card ${card.flipped || card.solved ? 'flipped' : ''} ${card.solved ? 'solved' : ''}">
<div class="front">${card.value}</div>
<div class="back"></div>
</div>
</td>
`).join('')}
</tr>
`).join('')}
</table>`
}
function renderStatus(solved, gameState) {
statusDiv.innerHTML = `
<p>Game state: ${gameState}</p>
<p>You have already solved ${solved} cards.</p>
`
}
function onSelectCard(e) {
const card = e.target.closest('.card')
if (boardDiv.contains(card)) {
if (flippedCards().length === 2) {
return
}
const {x, y} = xyCoord(card)
selectCard(y, x)
✒>update()<✒
if (flippedCards().length === 2) {
setTimeout(turnBackAndRender, 1000)
}
}
}
function turnBackAndRender() {
turnBack()
✒>update()<✒
}
function render() {
renderBoard(board)
renderStatus(countSolved(), gameState)
}
function update() {
✒>updateBoard()<✒
renderStatus(countSolved(), gameState)
}
✒>function updateBoard() {
board.forEach((row, i) => row.forEach((card, j) => {
document.querySelector(`table tr:nth-child(${i+1}) td:nth-child(${j+1}) .card`).classList.toggle('flipped', card.flipped || card.solved)
document.querySelector(`table tr:nth-child(${i+1}) td:nth-child(${j+1}) .card`).classList.toggle('solved', card.solved)
}))
}<✒
<!-- ... -->
<script type="module" src="index.js"></script>
import { xyCoord } from "./helper.js";
import { Game } from "./game.js";
import { render, update } from "./render.js";
const form = document.querySelector('form')
const button = form.querySelector('button')
const boardDiv = document.querySelector('#board')
const statusDiv = document.querySelector('#status')
const game = new Game()
button.addEventListener('click', onGenerate)
function onGenerate(e) {
// ...
game.initBoard(n, m)
render(game)
}
boardDiv.addEventListener('click', onSelectCard)
function onSelectCard(e) {
const card = e.target.closest('.card')
if (boardDiv.contains(card)) {
// ...
game.selectCard(y, x)
update(game)
if (game.flippedCards().length === 2) {
setTimeout(turnBackAndRender, 1000)
}
}
}
function turnBackAndRender() {
game.turnBack()
update(game)
}
export class Game {
constructor() {
this.board = []
this.gameState = 0 // 0, 1, 2
}
init() {
this.board = []
this.gameState = 0
}
initBoard(n, m) {
const numbers = Array(n * m / 2).fill(0).map((e, i) => i + 1)
const values = [...numbers, ...numbers].sort((a, b) => Math.random() < 0.5 ? 1 : -1)
this.board = Array(n).fill(0).map(() => Array(m).fill(0).map(() => ({
value: values.shift(),
flipped: false,
solved: false,
})))
this.gameState = 1
}
selectCard(i, j) {
const flipped = this.flippedCards()
if (this.isFlipped(i, j) || this.isSolved(i, j) || flipped.length === 2) {
return
}
// flip over
this.board[i][j].flipped = true
flipped.push(this.board[i][j])
// check cards
if (flipped.length === 2 && flipped[0].value === flipped[1].value) {
flipped.forEach(card => {
card.solved = true
card.flipped = false
})
}
// check win
if (this.isWin()) {
this.gameState = 2
}
}
isFlipped(i, j) {
return this.board[i][j].flipped
}
isSolved(i, j) {
return this.board[i][j].solved
}
turnBack() {
this.board.forEach(row => row.forEach(card => card.flipped = false))
}
isWin() {
return this.board.every(row => row.every(card => card.solved))
}
flippedCards() {
return this.board.flatMap(row => row.filter(card => card.flipped))
}
countSolved() {
return this.board.flatMap(row => row.filter(card => card.solved)).length
}
}
const boardDiv = document.querySelector('#board')
const statusDiv = document.querySelector('#status')
export function render(game) {
renderBoard(game.board)
renderStatus(game.countSolved(), game.gameState)
}
export function update(game) {
updateBoard(game.board)
renderStatus(game.countSolved(), game.gameState)
}
export function updateBoard(board) {
board.forEach((row, i) => row.forEach((card, j) => {
document.querySelector(`table tr:nth-child(${i+1}) td:nth-child(${j+1}) .card`).classList.toggle('flipped', card.flipped || card.solved)
document.querySelector(`table tr:nth-child(${i+1}) td:nth-child(${j+1}) .card`).classList.toggle('solved', card.solved)
}))
}
export function renderBoard(board) {
boardDiv.innerHTML = `
<table>
${board.map(row => `
<tr>
${row.map(card => `
<td>
<div class="card ${card.flipped || card.solved ? 'flipped' : ''} ${card.solved ? 'solved' : ''}">
<div class="front">${card.value}</div>
<div class="back"></div>
</div>
</td>
`).join('')}
</tr>
`).join('')}
</table>`
}
export function renderStatus(solved, gameState) {
statusDiv.innerHTML = `
<p>Game state: ${gameState}</p>
<p>You have already solved ${solved} cards.</p>
`
}
export function xyCoord(card) {
const td = card.closest('td')
const x = td.cellIndex
const tr = td.parentNode
const y = tr.sectionRowIndex
return { x, y }
}