JavaScript applications without a framework
A lengthy background
More than a decade ago, I was co-leading a project where we were building a web mail application that looked and behaved like a desktop mail application. We build it using one of the earliest JavaScript frameworks called SproutCore. The idea of the framework was simple - build the user interface on the client and only fetch data from the server.
Within a few years, the technology scene exploded with new JavaScript frameworks claiming to the best single page application framework around. And obviously led to a lot of flame wars.
Today we talk about microservices, headless CMS, API-first, etc. But all of these concepts come from software engineering fundamentals. One of the early system architecture methodology was COM programming. Many of the "new" approaches are rebranding of the same fundamentals. Although obviously in the context of the today's technology advances.
In the same vein, popular approaches to development of sites has come a full circle from sending all HTML for a page from the server to rich javascript applications to (now) the move to javascript based server side rendering. Primarily driven by the need for SEO.
Taking the hybrid approach, we could still develop applications in one language such as Java, Python or Go and still use JavaScript components for the interactive pieces on the client browser. Though this seems like stating the obvious, you would be surprised how many times this does not come as an option during design discussions. Passionate developers would argue that we should go SSR with React or Vue.
领英推荐
Sticking to engineering fundamentals
This is where web components help break the argument and follow one of the fundamentals of software design - decoupling.
Web components being an extension of HTMLElement, provide a way to build JavaScript powered components without actually needing a complex framework such as React. And they work with any framework that you might choose to use based on the applications requirements.
As a mature developer, you may think this is not new. Yes, and this article is intended for those who might still be learning about capabilities of web components.
Below is a simple approach to build a TicTacToe game based on the first part of the famous React tutorial. There are lot improvements you could do to this such as creating a generic base class, supporting asynchronous API fetches, use shadow DOM, etc. But the idea is that you can build UI components without needing complex frameworks or requiring complex build setup. And this adheres to an industry standard, that you can take from one project to another.
-------------
tictactoe.js
-------------
class TicTacToe extends HTMLElement {
? constructor() {
? ? super();
? ? this.boardState = {
? ? ? squares: Array(9).fill(null),
? ? ? xIsNext: true,
? ? ? winner: null
? ? }
? ? this.render();
? }
? getTemplate() {
? ? return? `
? ? ? {{#if winner}}
? ? ? ? <div class="status">Winner: {{winner}}</div>
? ? ? {{else}}
? ? ? ? {{#if xIsNext}}
? ? ? ? <div class="status">Next player: X</div>
? ? ? ? {{else}}
? ? ? ? <div class="status">Next player: O</div>
? ? ? ? {{/if}}
? ? ? {{/if}}
? ? ? <div class="game">
? ? ? ? {{#each squares}}
? ? ? ? ? <button class="square" data-index={{@index}}>{{this}}</button>
? ? ? ? {{/each}}
? ? ? </div>
? ? ? <button class="reset">Reset game</button>
? ? `;
? }
? getContext() {
? ? this.boardState.winner = this.calculateWinner();
? ? return this.boardState;
? }
??
? render() {
? ? const template = Handlebars.compile(this.getTemplate());
? ? this.innerHTML = template(this.getContext());
? ? this.attachEvents();
? }
? attachEvents() {
? ? var that = this;
? ? this.querySelectorAll('.square').forEach((item) => {
? ? ? item.addEventListener('click', (e) => {
? ? ? ? const indexClicked = e.target.dataset.index;
? ? ? ? that.handleClick(indexClicked);
? ? ? })
? ? });
? ? this.querySelector('.reset').addEventListener('click', (e) => {
? ? ? console.log(e)
? ? ? that.resetGame();
? ? });
? }
? handleClick(indexClicked) {
? ? if(this.boardState.winner || this.boardState.squares[indexClicked]) {
? ? ? return;
? ? }
? ? this.boardState.squares[indexClicked] = this.boardState.xIsNext? 'X' : 'O';
? ? this.boardState.xIsNext = !this.boardState.xIsNext;
? ? this.render();
? }
??
? resetGame() {
? ? this.boardState = {
? ? ? squares: Array(9).fill(null),
? ? ? xIsNext: true,
? ? ? winner: null
? ? }
? ? this.render();
? }
? calculateWinner() {
? ? const lines = [
? ? ? [0, 1, 2],
? ? ? [3, 4, 5],
? ? ? [6, 7, 8],
? ? ? [0, 3, 6],
? ? ? [1, 4, 7],
? ? ? [2, 5, 8],
? ? ? [0, 4, 8],
? ? ? [2, 4, 6],
? ? ];
? ? const squares = this.boardState.squares;
? ? for (let i = 0; i < lines.length; i++) {
? ? ? const [a, b, c] = lines[i];
? ? ? if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
? ? ? ? return squares[a];
? ? ? }
? ? }
? ? return null;
? }
}
customElements.define('tic-tac-toe', TicTacToe);
-------------
index.html
-------------
<!DOCTYPE html>
<html lang="en">
? <head>
? ? <meta charset="UTF-8" />
? ? <link rel="icon" type="image/svg+xml" href="/vite.svg" />
? ? <meta name="viewport" content="width=device-width, initial-scale=1.0" />
? ? <script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.7.7/handlebars.min.js"></script>
? ? <title>Tic Tac Toe</title>
? ? <style>
? ? :root {
? ? ? --global-font-size: 24px;
? ? ? --cell-button-size: 50px;
? ? }
? ? body {
? ? ? font: "Century Gothic", Futura, sans-serif;
? ? ? margin: 20px;
? ? }
? ? ol, ul {
? ? ? padding-left: 30px;
? ? }
? ? .game {
? ? ? display: grid;
? ? ? grid-template-columns: repeat(3, var(--cell-button-size));
? ? }
? ? .status {
? ? ? margin-bottom: 10px;
? ? ? font-size: var(--global-font-size);
? ? }
? ? .square {
? ? ? background: #fff;
? ? ? border: 1px solid #999;
? ? ? float: left;
? ? ? font-size: var(--global-font-size);
? ? ? font-weight: bold;
? ? ? line-height: var(--cell-button-size);
? ? ? height: var(--cell-button-size);
? ? ? margin-right: -1px;
? ? ? margin-top: -1px;
? ? ? padding: 0;
? ? ? text-align: center;
? ? ? width: var(--cell-button-size);
? ? }
? ? .reset {
? ? ? margin-top: 10px;
? ? }
? ? </style>
? </head>
? <body>
? ? <tic-tac-toe></tic-tac-toe>
? </body>
</html>