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 select a fruit apple banana cherry 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