HTML-Encoder Showcase

So What this is all about

I wrote my HTML-Encoder as thought experiment and it's kinda like a JSX alternative. The general idea is to write 100% valid HTML code that can be rendered to Typescript/Javascript instructions that can later be render back on either the client or the server side. Things get more complicated when I introduced "live" updates that allow you to make changes in the HTML output with minimal DOM-rendering (it also provide you quick access to selected HTML element).

In this showcase I give a few quick examples of using my encoder. It was also a good test-case scenario for me to try build something a bit more advanced than my original test-units.

Movie Search

Demo

Code:

template.html:

            

Typescript:

            import { getNode } from './movie-search.template.js';
            const url = /* URL goes here */;
            const app = getNode({});
  
            export function initApp() {
              const form = app.set.searchForm as unknown as HTMLFormElement;
              const searchField = app.set.searchField as unknown as HTMLInputElement;
              form.onsubmit = () => search(searchField.value) && false;
              return app as Node;
            }
  
            async function search(searchValue: string) {
              const response = await fetch(`${url}&s=${searchValue}`);
              const results = await response.json();
              app.set.movies = results.Search;
            }
          

ToDo App

Demo

Code:

template.html:

            

Typescript:

          import { getNode } from './todo.template.js';
          import initState, { State } from './state.js';
          
          type Task = { label: string; completed: boolean; removeId?: string; completedId?: string }
          const state: State = initState({
            tasks: [{
              label: 'build application',
              completed: false,
            }, {
              label: 'stylise ui',
              completed: false,
            }, {
              label: 'add documentation',
              completed: false,
            }],
            show: 'all'
          });
          
          
          const app = getNode({
            count: getCountString(state.tasks.length), tasks: prepTasks(state.tasks, state.show)
          });
          
          function getCountString(count: number): any {
            if (count > 1) {
              return `${count} items left`;
            } else if (count === 1) {
              return `Only 1 item left`
            }
            return '';
          }
          
          function prepTasks(tasks: Task[], show: string): Task[] {
            return tasks
              .filter(task => (show == 'all') || (show == 'active' && !task.completed) || (show === 'completed' && task.completed))
              .map((task, i) => ({ ...task, removeId: `remove_${i}`, completedId: `complete_${i}` }));
          }
          
          export function initApp() {
            const form = app.set.taskForm as unknown as HTMLFormElement;
            form.addEventListener('change', evt => {
              const source = evt.target as HTMLInputElement;
              const sourceId = source.id;
              
              if (sourceId.indexOf('complete') === 0) {
                const copy = state.tasks.slice(0);
                const task: Task = copy[+source.value];
                copy[+source.value] = { label: task.label, completed: !task.completed };
                state.tasks = copy;
              } else if (sourceId.indexOf('remove_') === 0) {
                const copy = state.tasks.slice(0);
                copy.splice(+source.value, 1);
                state.tasks = copy;
              } else if (source.name === 'filter') {
                state.show = source.value;
              }
            });
            (app.set.taskForm as unknown as HTMLFormElement).onsubmit = () => false;
            
            const taskField: HTMLInputElement = app.set.taskField as unknown as HTMLInputElement;
            taskField.addEventListener('keyup', (evt) => {
              if ((evt as KeyboardEvent).key === 'Enter') {
                state.tasks = [{ label: taskField.value, completed: false }, ...state.tasks];
                taskField.value = '';
              }
            });
            
            (app.set.removeCompleted as unknown as HTMLButtonElement).addEventListener('click', () => {
              state.tasks = state.tasks.slice(0).filter((task: Task) => !task.completed);
            });
            
            state.$?.tasks.add(tasks => refresh(tasks, state.show));
            state.$?.show.add(show => refresh(state.tasks, show));
            
            return app as Node;
          }
          
          
          function refresh(tasks: Task[], show: 'all' | 'active' | 'completed') {
            const filteredTasks = prepTasks(tasks, show);
            app.set.tasks = filteredTasks as any;
            app.set.count = getCountString(filteredTasks.length) as any;
          }
          

Shopping App

Demo