Skip to main content
Glama
field-positioner.js9.26 kB
/** * Position Engine - Intelligent field positioning with page awareness * Handles append, prepend, after, before, index, and page-specific positioning */ export class PositionEngine { /** * Calculate the insertion index for a new field * @param {array} fields - Existing form fields * @param {object} positionConfig - Positioning configuration * @param {object} pagination - Form pagination settings * @returns {number} Index where field should be inserted */ calculatePosition(fields, positionConfig = {}, pagination = null) { const { mode = 'append', reference, page } = positionConfig; // Handle page-specific positioning for multi-page forms if (pagination && page) { return this.calculatePagePosition(fields, page, mode, reference, pagination); } // Handle non-paged positioning switch (mode) { case 'append': // Add to end of form return fields.length; case 'prepend': // Add to beginning of form return 0; case 'after': // Add after specific field return this.positionAfterField(fields, reference); case 'before': // Add before specific field return this.positionBeforeField(fields, reference); case 'index': // Insert at specific index return this.positionAtIndex(fields, reference); default: // Default to append return fields.length; } } /** * Calculate position for page-specific insertion */ calculatePagePosition(fields, pageNumber, mode, reference, pagination) { // Get page boundaries const pageBoundaries = this.getPageBoundaries(fields); // Validate page number const totalPages = pageBoundaries.length + 1; if (pageNumber < 1 || pageNumber > totalPages) { console.warn(`Page ${pageNumber} out of range (1-${totalPages}), defaulting to last page`); return fields.length; } // Get fields for specific page const pageFields = this.getFieldsForPage(fields, pageNumber, pageBoundaries); if (pageFields.length === 0) { // Empty page - find appropriate position if (pageNumber === 1) { // First page - add at beginning return 0; } else if (pageNumber <= pageBoundaries.length) { // Middle page - add after previous page break const pageBreakIndex = fields.indexOf(pageBoundaries[pageNumber - 2]); return pageBreakIndex + 1; } else { // Last page - add at end return fields.length; } } switch (mode) { case 'append': // Add to end of specified page const lastFieldOnPage = pageFields[pageFields.length - 1]; const lastFieldIndex = fields.indexOf(lastFieldOnPage); // Check if next field is a page break if (lastFieldIndex + 1 < fields.length && fields[lastFieldIndex + 1].type === 'page') { // Insert before the page break return lastFieldIndex + 1; } // Insert after last field on page return lastFieldIndex + 1; case 'prepend': // Add to beginning of specified page const firstFieldOnPage = pageFields[0]; return fields.indexOf(firstFieldOnPage); case 'after': // Add after specific field on this page const afterField = pageFields.find(f => f.id == reference); if (afterField) { return fields.indexOf(afterField) + 1; } // Field not found on page, append to page return this.calculatePagePosition(fields, pageNumber, 'append', null, pagination); case 'before': // Add before specific field on this page const beforeField = pageFields.find(f => f.id == reference); if (beforeField) { return fields.indexOf(beforeField); } // Field not found on page, prepend to page return this.calculatePagePosition(fields, pageNumber, 'prepend', null, pagination); default: // Default to append on page return this.calculatePagePosition(fields, pageNumber, 'append', null, pagination); } } /** * Get all page break fields (boundaries between pages) */ getPageBoundaries(fields) { return fields.filter(field => field.type === 'page'); } /** * Get all fields that belong to a specific page */ getFieldsForPage(fields, pageNumber, pageBoundaries = null) { if (!pageBoundaries) { pageBoundaries = this.getPageBoundaries(fields); } const pageFields = []; let currentPage = 1; for (let i = 0; i < fields.length; i++) { const field = fields[i]; // Skip page break fields themselves if (field.type === 'page') { currentPage++; continue; } // Add field if it's on the requested page if (currentPage === pageNumber) { pageFields.push(field); } // Stop if we've passed the requested page if (currentPage > pageNumber) { break; } } return pageFields; } /** * Determine which page a field belongs to */ getFieldPage(field, allFields, pagination = null) { const fieldIndex = allFields.indexOf(field); if (fieldIndex === -1) return null; let currentPage = 1; for (let i = 0; i < fieldIndex; i++) { if (allFields[i].type === 'page') { currentPage++; } } return currentPage; } /** * Position field after a reference field */ positionAfterField(fields, referenceFieldId) { if (!referenceFieldId) { // No reference, append to end return fields.length; } const afterIndex = fields.findIndex(f => f.id == referenceFieldId); if (afterIndex >= 0) { // Found reference field, insert after it return afterIndex + 1; } else { // Reference field not found, append to end console.warn(`Reference field ${referenceFieldId} not found, appending to end`); return fields.length; } } /** * Position field before a reference field */ positionBeforeField(fields, referenceFieldId) { if (!referenceFieldId) { // No reference, prepend to beginning return 0; } const beforeIndex = fields.findIndex(f => f.id == referenceFieldId); if (beforeIndex >= 0) { // Found reference field, insert before it return beforeIndex; } else { // Reference field not found, append to end console.warn(`Reference field ${referenceFieldId} not found, appending to end`); return fields.length; } } /** * Position field at specific index */ positionAtIndex(fields, index) { if (typeof index !== 'number') { // Invalid index, append to end return fields.length; } // Clamp index to valid range if (index < 0) { return 0; } else if (index > fields.length) { return fields.length; } else { return index; } } /** * Validate positioning configuration */ validatePositionConfig(positionConfig, fields) { const errors = []; const warnings = []; if (!positionConfig) { return { valid: true, errors, warnings }; } const { mode, reference, page } = positionConfig; // Validate mode const validModes = ['append', 'prepend', 'after', 'before', 'index']; if (mode && !validModes.includes(mode)) { errors.push(`Invalid position mode: ${mode}. Must be one of: ${validModes.join(', ')}`); } // Validate reference for modes that require it if ((mode === 'after' || mode === 'before') && !reference) { warnings.push(`Position mode '${mode}' specified without reference field ID`); } // Validate reference field exists if (reference && (mode === 'after' || mode === 'before')) { const refField = fields.find(f => f.id == reference); if (!refField) { warnings.push(`Reference field ${reference} not found in form`); } } // Validate page number if (page !== undefined) { if (typeof page !== 'number' || page < 1) { errors.push(`Invalid page number: ${page}. Must be a positive integer`); } // Check if page is within range const pageBreaks = this.getPageBoundaries(fields); const totalPages = pageBreaks.length + 1; if (page > totalPages) { warnings.push(`Page ${page} exceeds total pages (${totalPages})`); } } return { valid: errors.length === 0, errors, warnings }; } /** * Get position summary for logging/debugging */ getPositionSummary(fields, position, field) { const pageBreaks = this.getPageBoundaries(fields); const totalPages = pageBreaks.length + 1; const fieldPage = this.getFieldPage(field, fields); return { totalFields: fields.length, totalPages, insertedAt: position, onPage: fieldPage, afterField: position > 0 ? fields[position - 1]?.id : null, beforeField: position < fields.length ? fields[position]?.id : null }; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/GravityKit/gravity-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server