kibodo
Version:
Keyboard-only frontend framework
196 lines (168 loc) • 6.25 kB
text/typescript
import View from "./view";
import SelectEditView from "./select-edit";
import TextEditView from "./text-edit";
enum FieldType {
TEXT = 0,
CHECKBOX = 1,
SELECT = 2,
}
interface FieldSelectOption {
value: string;
label: string;
}
interface Field {
name: string;
label: string;
type: FieldType;
placeholder?: string;
value?: string | number | boolean;
readonly?: boolean;
options?: FieldSelectOption[];
}
class Form extends View {
fields: Field[];
currentFieldIndex: number;
onSave: ((values: Record<string, string | number | boolean>) => void) | null;
constructor(fields: Field[] = []) {
super();
this.fields = fields;
let minEditableIndex = 0;
for (let i = 0; i < this.fields.length; i++) {
if (this.fields[i].readonly) continue;
minEditableIndex = i;
break;
}
this.currentFieldIndex = minEditableIndex;
}
addField(field: Field): void {
this.fields.push(field);
this.render();
}
renderContent() {
const form = document.createElement('form');
form.className = 'form';
this.fields.forEach((field, index) => {
const fieldGroup = document.createElement('div');
fieldGroup.className = 'form-group';
fieldGroup.dataset.fieldIndex = index.toString();
if (index === this.currentFieldIndex) {
fieldGroup.classList.add('selected');
fieldGroup.focus();
}
if (field.readonly) {
fieldGroup.classList.add('readonly');
}
const label = document.createElement('label');
label.textContent = field.label || field.name;
fieldGroup.appendChild(label);
if (field.type === FieldType.CHECKBOX) {
const checkbox = document.createElement('div');
checkbox.className = 'field-value checkbox';
checkbox.textContent = field.value ? '[X]' : '[ ]';
checkbox.dataset.fieldName = field.name;
fieldGroup.appendChild(checkbox);
} else if (field.type === FieldType.SELECT) {
const value = document.createElement('div');
value.className = 'field-value';
const selectedOption = field.options?.find(option => option.value === field.value);
value.textContent = selectedOption?.label || field.placeholder || 'Select...';
fieldGroup.appendChild(value);
} else {
const value = document.createElement('div');
value.className = 'field-value';
value.textContent = field.value?.toString() || field.placeholder || 'Enter...';
fieldGroup.appendChild(value);
}
form.appendChild(fieldGroup);
});
const saveButton = document.createElement('button');
saveButton.className = 'btn save-button';
saveButton.textContent = 'Save';
saveButton.type = 'submit';
saveButton.dataset.fieldIndex = this.fields.length.toString();
if (this.currentFieldIndex === this.fields.length) {
saveButton.classList.add('selected');
}
form.appendChild(saveButton);
return form;
}
onUp = () => {
this.navigateField(-1);
}
onDown = () => {
this.navigateField(1);
}
navigateField(direction: number) {
const currentSelected = document.getElementsByClassName('selected');
for (let i = 0; i < currentSelected.length; i++) {
currentSelected[i].classList.remove('selected');
}
let newIndex = this.currentFieldIndex + direction;
const maxIndex = this.fields.length;
if (newIndex < 0) {
newIndex = maxIndex;
} else if (newIndex > maxIndex) {
newIndex = 0;
}
this.currentFieldIndex = newIndex;
if (newIndex === maxIndex) {
const saveButton = document.querySelector('.save-button');
if (saveButton) {
saveButton.classList.add('selected');
}
} else {
const fieldGroup = document.querySelector(`[data-field-index="${newIndex}"]`);
if (fieldGroup) {
fieldGroup.classList.add('selected');
}
}
}
onSubmit() { // TODO: Костыль какаой-то
if (this.currentFieldIndex === this.fields.length) {
this.save();
} else {
const field = this.fields[this.currentFieldIndex];
if (field && !field.readonly) {
this.editField(field);
}
}
}
editField(field: Field) {
if (field.type === FieldType.CHECKBOX) {
field.value = field.value ? false : true;
const checkbox = document.querySelector(`[data-field-name="${field.name}"]`);
if (checkbox) {
checkbox.textContent = field.value === true ? '[X]' : '[ ]';
}
} else if (field.type === FieldType.SELECT) {
window.app?.push(
new SelectEditView(field.name, field.options),
).then((value) => {
if (value === undefined || value === null) { return; }
field.value = value;
this.render();
});
} else {
window.app?.push(
new TextEditView(
{
field: field,
currentValue: field.value?.toString() || '',
}
)
).then((value) => {
if (value === undefined || value === null) { return; }
field.value = value;
this.render();
});
}
}
save() {
let values = {};
this.fields.forEach(field => {
values[field.name] = field.value;
});
this.onSave?.(values);
}
}
export { Form, FieldType };