Files
kintone-helper-extenstion/fields.js
2025-10-17 14:39:34 +08:00

258 lines
8.7 KiB
JavaScript

import {
FIELD_TYPES,
LAYOUT_TYPES,
OPTION_SORTABLE_TYPES,
EXCLUDED_GROUP_TYPES
} from './constants.js';
/**
* Sorts field options by index for fields that support options
* @param {Object} field - Field object
* @returns {Array} Sorted option labels
*/
const sortFieldOptions = (field) => {
if (!field.options) return [];
return Object.values(field.options)
.sort((a, b) => Number(a.index) - Number(b.index))
.map(option => option.label);
};
/**
* Checks if a field should have its options sorted
* @param {string} fieldType - Type of the field
* @returns {boolean} True if options should be sorted
*/
const shouldSortOptions = (fieldType) => OPTION_SORTABLE_TYPES.includes(fieldType);
/**
* Adds system status fields to the fields array if they are enabled
* @param {Array} properties - All property objects
* @param {Array} fields - Array to add fields to
*/
const addSystemStatusFields = (properties, fields) => {
const propertyList = Object.values(properties);
[FIELD_TYPES.STATUS, FIELD_TYPES.STATUS_ASSIGNEE, FIELD_TYPES.CATEGORY].forEach(type => {
const field = propertyList.find(f => f.type === type);
if (field && field.enabled) {
fields.push(field);
}
});
};
/**
* Processes fields in a regular row layout
* @param {Array} rowFields - Fields in the row
* @param {Object} properties - Property mapping
* @param {Array} fields - Array to add processed fields to
* @param {Array} spacers - Array to add spacer elements to
*/
const processRowFields = (rowFields, properties, fields, spacers) => {
for (const field of rowFields) {
if ([LAYOUT_TYPES.LABEL, LAYOUT_TYPES.HR].includes(field.type)) continue;
if (field.type === LAYOUT_TYPES.SPACER) {
spacers.push(field);
continue;
}
const fieldProperty = properties[field.code];
if ([FIELD_TYPES.SUBTABLE, FIELD_TYPES.GROUP].includes(fieldProperty.type)) continue;
const fieldToAdd = shouldSortOptions(fieldProperty.type)
? { ...fieldProperty, sortedOptions: sortFieldOptions(fieldProperty) }
: fieldProperty;
fields.push(fieldToAdd);
}
};
/**
* Processes subtable fields
* @param {Object} layout - Subtable layout object
* @param {Object} properties - Property mapping
* @param {Array} fields - Array to add processed fields to
*/
const processSubtableFields = (layout, properties, fields) => {
const tableCode = layout.code;
const tableField = properties[tableCode];
if (tableField.type !== FIELD_TYPES.SUBTABLE) return;
const tableFields = {};
layout.fields.forEach(({ code: fieldCode }) => {
const subField = tableField.fields[fieldCode];
const processedField = shouldSortOptions(subField.type)
? { ...subField, table: tableCode, sortedOptions: sortFieldOptions(subField) }
: { ...subField, table: tableCode };
tableFields[fieldCode] = processedField;
fields.push(processedField);
});
fields.push({ ...tableField, fields: tableFields });
};
/**
* Processes group fields
* @param {Object} layout - Group layout object
* @param {Object} properties - Property mapping
* @param {Array} fields - Array to add processed fields to
* @param {Array} spacers - Array to add spacer elements to
*/
const processGroupFields = (layout, properties, fields, spacers) => {
const groupCode = layout.code;
const groupField = properties[groupCode];
if (groupField.type !== FIELD_TYPES.GROUP) return;
const groupFields = {};
layout.layout.forEach(row => {
row.fields.forEach(field => {
if ([LAYOUT_TYPES.LABEL, LAYOUT_TYPES.HR].includes(field.type)) return;
if (field.type === LAYOUT_TYPES.SPACER) {
spacers.push({ ...field, group: groupCode });
return;
}
const groupSubField = properties[field.code];
if (EXCLUDED_GROUP_TYPES.includes(groupSubField.type)) return;
const processedField = shouldSortOptions(groupSubField.type)
? { ...groupSubField, group: groupCode, sortedOptions: sortFieldOptions(groupSubField) }
: { ...groupSubField, group: groupCode };
groupFields[field.code] = processedField;
fields.push(processedField);
});
});
fields.push({ ...groupField, fields: groupFields });
};
/**
* Marks lookup copy fields in the fields array
* @param {Array} fields - Array of fields to process
*/
const markLookupCopies = (fields) => {
const lookupCopyTypes = [
FIELD_TYPES.SINGLE_LINE_TEXT, FIELD_TYPES.NUMBER, FIELD_TYPES.MULTI_LINE_TEXT,
FIELD_TYPES.RICH_TEXT, FIELD_TYPES.LINK, FIELD_TYPES.CHECK_BOX,
FIELD_TYPES.RADIO_BUTTON, FIELD_TYPES.DROP_DOWN, FIELD_TYPES.MULTI_SELECT,
FIELD_TYPES.DATE, FIELD_TYPES.TIME, FIELD_TYPES.DATETIME,
FIELD_TYPES.USER_SELECT, FIELD_TYPES.ORGANIZATION_SELECT,
FIELD_TYPES.GROUP_SELECT, FIELD_TYPES.CALC, FIELD_TYPES.RECORD_NUMBER
];
fields.forEach(field => {
if (field.lookup && Array.isArray(field.lookup.fieldMappings)) {
field.lookup.fieldMappings.forEach(mapping => {
const targetField = fields.find(f =>
lookupCopyTypes.includes(f.type) && f.code === mapping.field
);
if (targetField) {
targetField.isLookupCopy = true;
}
});
}
});
};
/**
* Adds system fields that aren't already in the fields array
* @param {Array} properties - Property objects
* @param {Array} fields - Array to add fields to
*/
const addSystemFields = (properties, fields) => {
const propertyList = Object.values(properties);
const systemFieldTypes = [
FIELD_TYPES.RECORD_NUMBER,
FIELD_TYPES.CREATOR,
FIELD_TYPES.CREATED_TIME,
FIELD_TYPES.MODIFIER,
FIELD_TYPES.UPDATED_TIME
];
systemFieldTypes.forEach(type => {
const field = propertyList.find(f => f.type === type);
if (field && !fields.some(f => f.type === type)) {
fields.push(field);
}
});
};
export const kintonePrettyFields = {
/**
* Generates processed field and spacer arrays from form properties and layout
* @param {Object} properties - Field properties object
* @param {Array} layouts - Layout definitions
* @returns {Object} Object containing fields and spacers arrays
*/
generateFields: (properties, layouts) => {
const fields = [];
const spacers = [];
// Add system status fields first
addSystemStatusFields(properties, fields);
// Process each layout item
layouts.forEach(layout => {
switch (layout.type) {
case LAYOUT_TYPES.ROW:
processRowFields(layout.fields, properties, fields, spacers);
break;
case LAYOUT_TYPES.SUBTABLE:
processSubtableFields(layout, properties, fields);
break;
case LAYOUT_TYPES.GROUP:
processGroupFields(layout, properties, fields, spacers);
break;
}
});
// Mark lookup copy fields
markLookupCopies(fields);
// Add any missing system fields
addSystemFields(properties, fields);
return { fields, spacers };
},
isCategory: field => field.type === "CATEGORY",
isCheckBox: field => field.type === "CHECK_BOX",
isCreatedTime: field => field.type === "CREATED_TIME",
isCreator: field => field.type === "CREATOR",
isDate: field => field.type === "DATE",
isDatetime: field => field.type === "DATETIME",
isDropDown: field => field.type === "DROP_DOWN",
isFile: field => field.type === "FILE",
isGroup: field => field.type === "GROUP",
isGroupSelect: field => field.type === "GROUP_SELECT",
isInGroup: field => 'group' in field,
isInSubtable: field => 'table' in field,
isLink: field => field.type === "LINK",
isLookup: field => 'lookup' in field,
isLookupCopy: field => field.isLookupCopy === true,
isModifier: field => field.type === "MODIFIER",
isMultiLineText: field => field.type === "MULTI_LINE_TEXT",
isMultiSelect: field => field.type === "MULTI_SELECT",
isNotInSubtable: field => !('table' in field),
isNumber: field => field.type === "NUMBER",
isOrganizationSelect: field => field.type === "ORGANIZATION_SELECT",
isRadioButton: field => field.type === "RADIO_BUTTON",
isRecordNumber: field => field.type === "RECORD_NUMBER",
isReferenceTable: field => field.type === "REFERENCE_TABLE",
isRichText: field => field.type === "RICH_TEXT",
isSingleLineText: field => field.type === "SINGLE_LINE_TEXT",
isStatus: field => field.type === "STATUS",
isStatusAssignee: field => field.type === "STATUS_ASSIGNEE",
isSubtable: field => field.type === "SUBTABLE",
isTime: field => field.type === "TIME",
isUpdatedTime: field => field.type === "UPDATED_TIME",
isUserSelect: field => field.type === "USER_SELECT",
};