The resulting UI needs to be accessible.
- Use semantic HTML where possible
- Adhere to accessibility standards, as explained in Accessibility.md
For consistency across all JavaScript files we use **standard **.
IntelliJ:
- Go to
Preferences | Editor | Code style | JavaScript
- Click
Set from...
- Select
JavaScript Standard Style
We use 2 spaces for one tab, per indentation.
IntelliJ:
- Go to
Editor > Code Style > JavaScript > Tabs and Indents
- Set all three fields to
2
- Avoid creating new files, when not necessary
- Create new files for jsx components which you want to reuse
- Don't create new files for another single use component of your layout code
- Create new files in their appropriate folder (look at the folder structure)
- Use
const foo = () => ..
instead offunction foo() {}
for everything- Prefer directly returning your result, over
const foo = () => { ... return ... }
- Prefer directly returning your result, over
- Use
_
(underscores) instead of camel case for functions - Use CamelCase for JSX components (to be discussed)
- Use
class="foo bar"
instead ofclassName={}
- Use
a
when changing the state of the web apps aka. changing the route - Use
button
when changing the state of the data aka. communicating with the back-end - Never place a
onClick
on adiv
or similar without reason (usebutton
instead) - Define values in groups:
const
foo = 1,
bar = 2
Please have a look at Using the API for detailed information.
API call:
const Instances = () => {
const
state = useContext(AppState),
{ params } = useRoute()
// call the api, access its result in a child component
void api.get_process_instances(state, params.definition_id)
// use the conditional (ternary) operator inside JSX code blocks
return !params?.selection_id
? (<table class="fade-in">
<thead>
<tr>
<th>ID</th>
<th>Start Time</th>
<th>State</th>
<th>Business Key</th>
</tr>
</thead>
<tbody>
<InstanceTableRows />
</tbody>
</table>)
: (<InstanceDetails />)
}
Naming instead of comments:
const ProcessDiagram = () => {
const
{ process_definition_diagram } = useContext(AppState),
{ params } = useRoute(),
// describe complex evaluations inside the scope of the function with a good name
show_diagram =
process_definition_diagram.value !== null &&
params.definition_id !== undefined
return <div id="preview" class="fade-in">
{show_diagram
? <ReactBpmn
diagramXML={process_definition_diagram.value.bpmn20Xml}
onLoading={null}
onShown={null}
onError={null} />
: 'Select Process Definition'}
</div>
}
Forms:
const CreateUserPage = () => {
// https://preactjs.com/guide/v10/forms/
const
state = useContext(AppState),
{ user_create, user_create_response } = state
const set_value = (k1, k2, v) => user_create.value[k1][k2] = v.currentTarget.value
const set_p_value = (k, v) => set_value('profile', k, v)
const set_c_value = (k, v) => set_value('credentials', k, v)
const on_submit = e => {
e.preventDefault()
console.log(user_create.value)
user_create_response.value = api.create_user(state)
// e.currentTarget.reset(); // Clear the inputs to prepare for the next submission
}
return <div>
<h2>Create New User</h2>
{(user_create_response.value !== undefined)
? user_create_response.value.success
? <p class="success">Successfully created new user.</p>
: <p class="error">Error: {user_create_response.value?.message}</p> : null}
<form onSubmit={on_submit}>
<label for="user-id">User ID</label>
<input id="user-id" type="text" onInput={(e) => set_p_value('id', e)} required />
<label for="password1">Password</label>
<input id="password1" type="password" onInput={(e) => set_c_value('password', e)} required />
<label for="password2"> Password (repeated)</label>
<input id="password2" type="password" onInput={(e) => set_c_value('password', e)} />
<label for="first-name"> First Name</label>
<input id="first-name" type="text" onInput={(e) => set_p_value('firstName', e)} required />
<label for="last-name">Last Name</label>
<input id="last-name" type="text" onInput={(e) => set_p_value('lastName', e)} required />
<label for="email">Email</label>
<input id="email" type="email" onInput={(e) => set_p_value('email', e)} required />
<div class="button-group">
<button type="submit">Create New User</button>
<a href="/admin/users" class="button secondary">Cancel</a>
</div>
</form>
</div>
}
Each endpoint has an identical structure:
// api.js
export const get_process_definitions = (state) =>
fetch(`${_url(state)}/process-definition/statistics`)
.then(response => response.json())
.then(json => state.process_definitions.value = json)
// state.js
const createAppState = () => {
const server = signal(localStorage.getItem("server") || JSON.parse(import.meta.env.VITE_BACKEND)[0])
const process_definitions = signal(null)
// ...
// add your new state signal here
return {
server,
process_definitions,
// ...
// make your new state signal available
}
}
- The JS name of the endpoint is always
{method}_{endpoint_name}
- Prepend the URL string by using the
${_url(state)}
pattern. We need to fetch the selected server URL from the environment variable (config) - Always assign the result to a state value
- Use the
export
keyword with the definition of your function, instead of exporting a map at the end. This is to prevent merge conflicts.
const url_params = (definition_id) =>
new URLSearchParams({
unfinished: true,
sortBy: 'startTime',
sortOrder: 'asc',
processDefinitionId: definition_id,
}).toString()
export const get_process_instances = (state, definition_id) =>
fetch(`${_url(state)}/history/process-instance?${url_params(definition_id)}`)
.then(response => response.json())
.then(json => (state.process_instances.value = json))
- In case you need params in your URL, use the
URLSearchParams
and keep it private (noexport
) - Place your params object before the corresponding endpoint function
- Use Signals instead of hooks
- Split code in multiple files
- Prefer classless styling to classes
- Prefer cascading styles with few additional classes instead of many single classes
- Create reusable CSS, refactor when necessary