JavaScript applications without a framework
webcomponents.org

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>        

要查看或添加评论,请登录

Adrain P.的更多文章

  • 1950s House to Zero Energy Home

    1950s House to Zero Energy Home

    Two years ago we bought an old 1060 square feet house which was built in 1954. Since the time it was built, there were…

  • Ockham's razor

    Ockham's razor

    Plurality should not be posited without necessity. Ockham's razor or the law of parsimony (lex parsimoniae) is a…

  • Interface inheritance vs Implementation inheritance

    Interface inheritance vs Implementation inheritance

    Implementation inheritance is a relationship where a child class inherits behaviour implementation from a base class…

    2 条评论

社区洞察

其他会员也浏览了