Input Components Demo

This page demo component based input fields.

Source Code of demo-inputs.tsx
(import statements omitted for simplicity, click to expand)
import { Style } from '../components/style.js'
import { o } from '../jsx/jsx.js'
import debug from 'debug'
import { title } from '../../config.js'
import { Routes } from '../routes.js'
import Menu from '../components/menu.js'
import { Switch } from '../components/router.js'
import DemoSelect from './demo-inputs/demo-select.js'
import DemoSingleFieldForm from './demo-inputs/demo-single-field-form.js'
import SourceCode from '../components/source-code.js'
const log = debug('demo-single-field-form.tsx')
log.enabled = true

let style = Style(/* css */ `
#demo-inputs .code-demo {
  display: flex;
  flex-wrap: wrap;
}
`)

let content = (
  <div id="demo-inputs">
    <h1>Input Components Demo</h1>
    <p>This page demo component based input fields.</p>

    {style}
    <link rel="stylesheet" href="/lib/prism/prism.css" />
    <script src="/lib/prism/prism.js"></script>

    <SourceCode page="demo-inputs.tsx" />

    <p>
      The example code snippets below are simplified for illustration. You can
      click "Source Code of [page]" to see the complete source code.
    </p>

    <fieldset>
      <legend>
        <Menu
          routes={[
            { url: '/inputs/select', menuText: '<Select/>' },
            {
              url: '/inputs/single-field-form',
              menuText: 'newSingleFieldForm()',
            },
          ]}
          separator=" | "
        />
      </legend>
      {Switch({
        '/inputs': DemoSelect,
        '/inputs/select': DemoSelect,
        '/inputs/single-field-form': DemoSingleFieldForm.content,
      })}
    </fieldset>
  </div>
)

let routes = {
  '/inputs': {
    title: title('Demo input components'),
    description: 'Demonstrate component-based input fields',
    menuText: 'Inputs',
    menuMatchPrefix: true,
    node: content,
  },
  '/inputs/select': {
    title: title('Demo <Select/> components'),
    description:
      'Demonstrate building select and option elements with <Select/>',
    node: content,
  },
  '/inputs/single-field-form': {
    title: title('Demo single-field-form component creator'),
    description: 'Demonstrate per-field saving with realtime update',
    node: content,
  },
  ...DemoSingleFieldForm.routes,
} satisfies Routes

export default { routes }

The example code snippets below are simplified for illustration. You can click "Source Code of [page]" to see the complete source code.

<Select/> is a function component that construct native <select> and <option> elements.

It is intuitive to develop with native html <input>. However, populating the value of <select> and <option> is getting verbose because <select> doesn't apply the value from the value attribute and placeholder attributes.

Approach with <Select/> component

Example Source Code<form> <Select name="fruit" placeholder="select a fruit" value={record.fruit_id} options={fruitOptions} /> </form>
Preview

Approaches without component

Workaround of placeholder for <select>

A workaround to display placeholder for <select> is to put a disabled <option> with empty value.

Example Source Code<select autocomplete="off"> <option disabled selected value="" > select a fruit </option> <option>apple</option> <option>banana</option> <option>cherry</option> </select>
Preview

Workarounds of value for <select>

One approach is to set the value of select field with inline javascript. However this approach require client-side javascript to function. Also, the form id should be carefully picked to avoid name clash.

Example Source Code<form id="demoForm"> <select name="fruit"> <option>apple</option> <option>banana</option> <option>cherry</option> </select> </form> {Script(`demoForm.fruit.value=${JSON.stringify(record.fruit)}`)}
Preview

Another approach is to render each <option> conditionally (on server side). This version doesn't require form id and client-side javascript but it is rather verbose.

Example Source Code<form> <select name="fruit"> {mapArray(fruitNames, name => ( <option selected={name == record.fruit ? '' : undefined}> {name} </option> ))} </select> </form>
Preview

The verbosity adds up when the option text and option value are different.

Example Source Code<form> <select name="fruit"> {mapArray(fruitRows, fruit => ( <option value={fruit.id} selected={fruit.id == record.fruit_id ? '' : undefined} > {fruit.name} </option> ))} </select> </form>
Preview

The <Select/> component takes care of setting up the <option> based on the value attribute and the optional placeholder attribute without requiring client-side javascript. So you can enjoy React-like DX without running javascript on the client side.

Source Code of demo-inputs/demo-select.tsx
(import statements omitted for simplicity, click to expand)
import { mapArray } from '../../components/fragment.js'
import code from '../../components/inline-code.js'
import { Script } from '../../components/script.js'
import { Select } from '../../components/select.js'
import SourceCode from '../../components/source-code.js'
import { o } from '../../jsx/jsx.js'
let select = code('<select>')
let option = code('<option>')
let placeholder = code('placeholder')
let value = code('value')

let fruitNames = ['apple', 'banana', 'cherry']
let fruitOptions = fruitNames.map((text, index) => ({ value: index + 1, text }))
let fruitRows = fruitNames.map((name, index) => ({ id: index + 1, name }))

let record = { fruit: 'cherry', fruit_id: 3 }

let content = (
  <>
    <p>
      {code('<Select/>')} is a function component that construct native {select}{' '}
      and {option} elements.
    </p>

    <p>
      It is intuitive to develop with native html {code('<input>')}. However,
      populating the value of {select} and {option} is getting verbose because{' '}
      {select} doesn't apply the value from the {value} attribute and{' '}
      {placeholder} attributes.
    </p>

    <h2>Approach with {code('<Select/>')} component</h2>

    <div class="code-demo">
      <fieldset>
        <legend>Example Source Code</legend>
        <code class="language-tsx" style="padding: 0.5rem">
          {
            /* html */ `
<form>
  <Select
    name="fruit"
    placeholder="select a fruit"
    value={record.fruit_id}
    options={fruitOptions}
  />
</form>
`.trim()
          }
        </code>
      </fieldset>
      <fieldset>
        <legend>Preview</legend>
        <form>
          <Select
            name="fruit"
            placeholder="select a fruit"
            value={record.fruit_id}
            options={fruitOptions}
          />
        </form>
      </fieldset>
    </div>

    <h2>Approaches without component</h2>

    <h3>
      Workaround of {placeholder} for {select}
    </h3>
    <p>
      A workaround to display {placeholder} for {select} is to put a disabled{' '}
      {option} with empty value.
    </p>
    <div class="code-demo">
      <fieldset>
        <legend>Example Source Code</legend>
        <code class="language-tsx" style="padding: 0.5rem">
          {
            /* html */ `
<select autocomplete="off">
  <option disabled selected value="" >
    select a fruit
  </option>
  <option>apple</option>
  <option>banana</option>
  <option>cherry</option>
</select>
`.trim()
          }
        </code>
      </fieldset>
      <fieldset>
        <legend>Preview</legend>
        <select autocomplete="off">
          <option disabled selected value="">
            select a fruit
          </option>
          <option>apple</option>
          <option>banana</option>
          <option>cherry</option>
        </select>
      </fieldset>
    </div>

    <h3>
      Workarounds of {value} for {select}
    </h3>

    <p>
      One approach is to set the value of select field with inline javascript.
      However this approach require client-side javascript to function. Also,
      the form id should be carefully picked to avoid name clash.
    </p>
    <div class="code-demo">
      <fieldset>
        <legend>Example Source Code</legend>
        <code class="language-tsx" style="padding: 0.5rem">
          {
            /* html */ `
 <form id="demoForm">
  <select name="fruit">
    <option>apple</option>
    <option>banana</option>
    <option>cherry</option>
  </select>
</form>
{Script(\`demoForm.fruit.value=\${JSON.stringify(record.fruit)}\`)}
`.trim()
          }
        </code>
      </fieldset>
      <fieldset>
        <legend>Preview</legend>
        <form id="demoForm">
          <select name="fruit">
            <option>apple</option>
            <option>banana</option>
            <option>cherry</option>
          </select>
        </form>
        {Script(`demoForm.fruit.value=${JSON.stringify(record.fruit)}`)}
      </fieldset>
    </div>

    <p>
      Another approach is to render each {option} conditionally (on server
      side). This version doesn't require form id and client-side javascript but
      it is rather verbose.
    </p>
    <div class="code-demo">
      <fieldset>
        <legend>Example Source Code</legend>
        <code class="language-tsx" style="padding: 0.5rem">
          {
            /* html */ `
 <form>
  <select name="fruit">
    {mapArray(fruitNames, name => (
      <option selected={name == record.fruit ? '' : undefined}>
        {name}
      </option>
    ))}
  </select>
</form>
`.trim()
          }
        </code>
      </fieldset>
      <fieldset>
        <legend>Preview</legend>
        <form>
          <select name="fruit">
            {mapArray(fruitNames, name => (
              <option selected={name == record.fruit ? '' : undefined}>
                {name}
              </option>
            ))}
          </select>
        </form>
      </fieldset>
    </div>

    <p>
      The verbosity adds up when the option text and option value are different.
    </p>
    <div class="code-demo">
      <fieldset>
        <legend>Example Source Code</legend>
        <code class="language-tsx" style="padding: 0.5rem">
          {
            /* html */ `
 <form>
  <select name="fruit">
    {mapArray(fruitRows, fruit => (
      <option
        value={fruit.id}
        selected={fruit.id == record.fruit_id ? '' : undefined}
      >
        {fruit.name}
      </option>
    ))}
  </select>
</form>
`.trim()
          }
        </code>
      </fieldset>
      <fieldset>
        <legend>Preview</legend>
        <form>
          <select name="fruit">
            {mapArray(fruitRows, fruit => (
              <option
                value={fruit.id}
                selected={fruit.id == record.fruit_id ? '' : undefined}
              >
                {fruit.name}
              </option>
            ))}
          </select>
        </form>
      </fieldset>

      <p>
        The {code('<Select/>')} component takes care of setting up the {option}{' '}
        based on the {value} attribute and the optional {placeholder} attribute
        without requiring client-side javascript. So you can enjoy React-like DX
        without running javascript on the client side.
      </p>
    </div>

    <SourceCode page="demo-inputs/demo-select.tsx" />
  </>
)

export default content