From 896294f55cd1c2f6485f13d8f67abdf7bab097b6 Mon Sep 17 00:00:00 2001 From: kodinkat Date: Tue, 3 Mar 2026 12:49:11 +0000 Subject: [PATCH 1/5] Refactor user_select handling in modular-list-bulk.js and shared-functions.js - Removed legacy typeahead implementation for user selection in favor of the new dt-users-connection component, enhancing maintainability and user experience. - Updated bulk edit field initialization to streamline user_select handling, ensuring compatibility with the new component structure. - Adjusted display logic for user selection to support both legacy and new formats, improving flexibility in data handling. - Enhanced SCSS to include styles for the new dt-users-connection component, ensuring consistent styling across the application. --- dt-assets/js/modular-list-bulk.js | 168 +++++++----------------------- dt-assets/js/shared-functions.js | 24 +---- dt-assets/scss/_list.scss | 1 + 3 files changed, 40 insertions(+), 153 deletions(-) diff --git a/dt-assets/js/modular-list-bulk.js b/dt-assets/js/modular-list-bulk.js index 7b055e334..27b3a69c2 100644 --- a/dt-assets/js/modular-list-bulk.js +++ b/dt-assets/js/modular-list-bulk.js @@ -1100,32 +1100,6 @@ return null; } - // Special case: user_select (uses typeahead, not web component) - if (fieldType === 'user_select') { - const fieldId = `bulk_${fieldKey}`; - const userInput = fieldWrapper.find(`.js-typeahead-${fieldId}`); - if (userInput.length > 0) { - const selectedUserId = userInput.data('selected-user-id'); - if (selectedUserId) { - return `user-${selectedUserId}`; - } - // Fallback: check typeahead instance - const typeaheadSelector = `.js-typeahead-${fieldId}`; - const typeaheadInstance = window.Typeahead?.[typeaheadSelector]; - if ( - typeaheadInstance && - typeaheadInstance.items && - typeaheadInstance.items.length > 0 - ) { - const selectedItem = typeaheadInstance.items[0]; - if (selectedItem && selectedItem.ID) { - return `user-${selectedItem.ID}`; - } - } - } - return null; - } - // Special case: communication_channel (dt-multi-text needs special formatting) if (fieldType === 'communication_channel') { const multiTextComponent = @@ -2128,98 +2102,10 @@ } function initializeBulkEditFieldHandlers(fieldKey, fieldType) { - // Special case: user_select uses typeahead (not a web component) - if (fieldType === 'user_select') { - const fieldId = `bulk_${fieldKey}`; - const userInput = $(`.js-typeahead-${fieldId}`); - - if (userInput.length) { - // Destroy existing typeahead instance if it exists (for restore scenarios) - const typeaheadSelector = `.js-typeahead-${fieldId}`; - if (window.Typeahead && window.Typeahead[typeaheadSelector]) { - try { - // Try to destroy the existing instance - if (window.Typeahead[typeaheadSelector].destroy) { - window.Typeahead[typeaheadSelector].destroy(); - } - delete window.Typeahead[typeaheadSelector]; - } catch (e) { - // If destroy fails, just delete the reference - delete window.Typeahead[typeaheadSelector]; - } - } - - // Initialize typeahead - $.typeahead({ - input: `.js-typeahead-${fieldId}`, - minLength: 0, - maxItem: 0, - accent: true, - searchOnFocus: true, - source: window.TYPEAHEADS.typeaheadUserSource(), - templateValue: '{{name}}', - template: function (query, item) { - return `
- - - ${window.SHAREDFUNCTIONS.escapeHTML(item.name)} - - ${item.status_color ? ` ` : ''} - ${ - item.update_needed && item.update_needed > 0 - ? ` - - ${window.SHAREDFUNCTIONS.escapeHTML(item.update_needed)} - ` - : '' - } -
`; - }, - dynamic: true, - hint: true, - emptyTemplate: window.SHAREDFUNCTIONS.escapeHTML( - window.wpApiShare.translations.no_records_found, - ), - callback: { - onClick: function (node, a, item, event) { - event.preventDefault(); - this.hideLayout(); - this.resetInput(); - - // Set the selected user value - const resultContainer = $(`#${fieldId}-result-container`); - resultContainer.html( - `${window.SHAREDFUNCTIONS.escapeHTML(item.name)}`, - ); - - // Store the selected user ID in data attributes for later collection - userInput.data('selected-user-id', item.ID); - userInput.data('selected-user-name', item.name); - resultContainer.data('selected-user-id', item.ID); - resultContainer.data('selected-user-name', item.name); - }, - onResult: function (node, query, result, resultCount) { - const resultContainer = $(`#${fieldId}-result-container`); - if (resultCount > 0) { - resultContainer.html( - `${resultCount} ${window.wpApiShare.translations.user_found || 'user(s) found'}`, - ); - } else { - resultContainer.html(''); - } - }, - onHideLayout: function () { - $(`#${fieldId}-result-container`).html(''); - }, - }, - }); - } - return; - } - - // For all web components: ComponentService.initialize() handles initialization - // The global dt:get-data listener handles data fetching - // No per-field-type initialization needed + // For all web components (including user_select via dt-users-connection), + // ComponentService.initialize() and the global dt:get-data listener handle + // initialization and data wiring. No per-field-type handlers are required + // at this time. } /** @@ -2361,20 +2247,33 @@ displayHtml += 'No option selected'; } } else if (fieldType === 'user_select') { - // user_select returns "user-{id}" format + // user_select returns "user-{id}" for payloads, but the dt-users-connection + // component exposes richer objects for display. Support both shapes. if (valuesToRemove) { - const userId = valuesToRemove.replace('user-', ''); - // Try to get user name from typeahead data or make a simple display - const fieldId = `bulk_${fieldKey}`; - const userInput = $(`.js-typeahead-${fieldId}`); - let userName = `User ${userId}`; - if (userInput.length > 0) { - const storedName = userInput.data('selected-user-name'); - if (storedName) { - userName = storedName; + let userId = null; + let userName = null; + + if (Array.isArray(valuesToRemove)) { + const first = valuesToRemove[0]; + if (first) { + userId = first.id || first.user_id || null; + userName = first.label || first.name || null; } + } else if (typeof valuesToRemove === 'object') { + userId = valuesToRemove.id || valuesToRemove.user_id || null; + userName = valuesToRemove.label || valuesToRemove.name || null; + } else if (typeof valuesToRemove === 'string') { + userId = valuesToRemove.replace('user-', ''); + } + + if (userId) { + if (!userName) { + userName = `User ${userId}`; + } + displayHtml += `${window.SHAREDFUNCTIONS.escapeHTML(userName)}`; + } else { + displayHtml += 'No user selected'; } - displayHtml += `${window.SHAREDFUNCTIONS.escapeHTML(userName)}`; } else { displayHtml += 'No user selected'; } @@ -2420,6 +2319,14 @@ if (component && component.value) { rawValueWithLabels = component.value; } + } else if (fieldType === 'user_select') { + const usersComponent = fieldWrapper.find('dt-users-connection')[0]; + if (usersComponent && usersComponent.value) { + const items = normalizeShareComponentItems(usersComponent.value); + if (items.length > 0) { + rawValueWithLabels = items; + } + } } let hasValues = false; @@ -2446,7 +2353,8 @@ fieldData.rawValueWithLabels = rawValueWithLabels; const displayValue = - fieldType === 'connection' && rawValueWithLabels + (fieldType === 'connection' || fieldType === 'user_select') && + rawValueWithLabels ? rawValueWithLabels : currentValue; const displayHtml = renderValuesToRemoveDisplay( diff --git a/dt-assets/js/shared-functions.js b/dt-assets/js/shared-functions.js index 53617ed07..e760a4fc3 100644 --- a/dt-assets/js/shared-functions.js +++ b/dt-assets/js/shared-functions.js @@ -1040,29 +1040,7 @@ window.SHAREDFUNCTIONS = { } case 'user_select': { - // user_select uses legacy typeahead (not a web component) - const fieldId = id; - return `
- -
-
-
- - - - - - -
-
-
-
`; + return ``; } case 'communication_channel': diff --git a/dt-assets/scss/_list.scss b/dt-assets/scss/_list.scss index ba1d59a96..89f4c40d3 100644 --- a/dt-assets/scss/_list.scss +++ b/dt-assets/scss/_list.scss @@ -691,6 +691,7 @@ table.js-list { dt-datetime label, dt-number label, dt-tags label, + dt-users-connection label, dt-user-select label, dt-connection label, dt-location label { From c63121907ab60ed02a176b8e5040b663ceca9a78 Mon Sep 17 00:00:00 2001 From: kodinkat Date: Tue, 3 Mar 2026 13:04:11 +0000 Subject: [PATCH 2/5] Enhance user_select handling in modular-list-bulk.js - Introduced a new function, normalizeUserSelectValue, to standardize user selection values for backend compatibility. - Updated bulk edit logic to utilize normalized user values for removal operations, improving data integrity and consistency. - Enhanced handling of various input formats for user selection, ensuring robust processing of legacy and new data structures. --- dt-assets/js/modular-list-bulk.js | 83 ++++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 2 deletions(-) diff --git a/dt-assets/js/modular-list-bulk.js b/dt-assets/js/modular-list-bulk.js index 27b3a69c2..5fe5ea7e6 100644 --- a/dt-assets/js/modular-list-bulk.js +++ b/dt-assets/js/modular-list-bulk.js @@ -822,6 +822,11 @@ // For boolean fields, include even if false (false is a valid value) if (fieldType === 'boolean') { updatePayload[fieldKey] = fieldValue === true; + } else if (fieldType === 'user_select') { + const normalizedUser = normalizeUserSelectValue(fieldValue); + if (normalizedUser !== null) { + updatePayload[fieldKey] = normalizedUser; + } } else { updatePayload[fieldKey] = fieldValue; } @@ -918,6 +923,73 @@ return Array.isArray(value) ? value : []; } + /** + * Normalize a user_select value to the "user-{id}" string format expected by + * the backend and conditional-removal logic. Accepts values from + * ComponentService.convertValue(dt-users-connection), dt-users-connection.value, + * or legacy shapes. + */ + function normalizeUserSelectValue(rawValue) { + if (rawValue === null || rawValue === undefined) { + return null; + } + + // Strings + if (typeof rawValue === 'string') { + const trimmed = rawValue.trim(); + if (!trimmed) { + return null; + } + if (trimmed.startsWith('user-')) { + return trimmed; + } + if (/^\d+$/.test(trimmed)) { + return `user-${trimmed}`; + } + return trimmed; + } + + // Arrays – use the first entry + if (Array.isArray(rawValue)) { + if (rawValue.length === 0) { + return null; + } + return normalizeUserSelectValue(rawValue[0]); + } + + // Objects + if (typeof rawValue === 'object') { + // Legacy stored shape { 'assigned-to': 'user-5', ... } + if ( + typeof rawValue['assigned-to'] === 'string' && + rawValue['assigned-to'].trim() + ) { + return normalizeUserSelectValue(rawValue['assigned-to']); + } + + // Common id fields + let possibleId = + rawValue.value || + rawValue.id || + rawValue.user_id || + (Array.isArray(rawValue.values) ? rawValue.values[0] : null); + + if (possibleId === null || possibleId === undefined) { + return null; + } + + if (typeof possibleId === 'string') { + return normalizeUserSelectValue(possibleId); + } + + if (typeof possibleId === 'number') { + return `user-${possibleId}`; + } + } + + return null; + } + /** * Update bulkEditSelectedFields for the share field from dt-users-connection value. * Called on initial value and on change; lives at module scope to avoid redefining per render. @@ -2330,6 +2402,7 @@ } let hasValues = false; + let normalizedUserValueToRemove = null; if ( currentValue !== null && currentValue !== undefined && @@ -2342,14 +2415,20 @@ ) { const values = currentValue?.values || currentValue; hasValues = Array.isArray(values) && values.length > 0; - } else if (fieldType === 'key_select' || fieldType === 'user_select') { + } else if (fieldType === 'key_select') { hasValues = true; + } else if (fieldType === 'user_select') { + normalizedUserValueToRemove = normalizeUserSelectValue(currentValue); + hasValues = !!normalizedUserValueToRemove; } } if (supportsSelectiveRemoval && hasValues) { fieldData.operation = 'remove'; - fieldData.valuesToRemove = currentValue; + fieldData.valuesToRemove = + fieldType === 'user_select' + ? normalizedUserValueToRemove + : currentValue; fieldData.rawValueWithLabels = rawValueWithLabels; const displayValue = From cb36761204896fc2fc5d59284713374d1bd6b3a6 Mon Sep 17 00:00:00 2001 From: kodinkat Date: Tue, 3 Mar 2026 13:16:15 +0000 Subject: [PATCH 3/5] Enhance user ID handling in modular-list-bulk.js - Added support for number type user IDs, converting them to the format for consistency. - Updated string handling to return null for invalid free-form strings, ensuring only valid payloads are processed for the backend user_select handler. - Improved overall robustness in user selection value normalization, aligning with recent enhancements in user_select handling. --- dt-assets/js/modular-list-bulk.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/dt-assets/js/modular-list-bulk.js b/dt-assets/js/modular-list-bulk.js index 5fe5ea7e6..9feca5de2 100644 --- a/dt-assets/js/modular-list-bulk.js +++ b/dt-assets/js/modular-list-bulk.js @@ -934,6 +934,12 @@ return null; } + // Numbers (ComponentService.convertValue for dt-users-connection in single + // mode returns a plain integer user ID, or 0/"" when empty) + if (typeof rawValue === 'number') { + return rawValue > 0 ? `user-${rawValue}` : null; + } + // Strings if (typeof rawValue === 'string') { const trimmed = rawValue.trim(); @@ -946,7 +952,9 @@ if (/^\d+$/.test(trimmed)) { return `user-${trimmed}`; } - return trimmed; + // Any other free-form string (e.g. display name) is not a valid payload + // for the backend user_select handler. + return null; } // Arrays – use the first entry From 619e2acc2187a0aedd353962ec57b2f309168023 Mon Sep 17 00:00:00 2001 From: kodinkat Date: Mon, 9 Mar 2026 12:16:53 +0000 Subject: [PATCH 4/5] Refactor user selection normalization in modular-list-bulk.js - Simplified the normalizeUserSelectValue function by removing legacy handling for arrays and objects, focusing on direct processing of user IDs. - Updated documentation to clarify expected input formats, enhancing understanding of the function's behavior. - Improved overall code readability and maintainability by streamlining the normalization logic. --- dt-assets/js/modular-list-bulk.js | 46 ++++--------------------------- 1 file changed, 5 insertions(+), 41 deletions(-) diff --git a/dt-assets/js/modular-list-bulk.js b/dt-assets/js/modular-list-bulk.js index 9feca5de2..d573c5500 100644 --- a/dt-assets/js/modular-list-bulk.js +++ b/dt-assets/js/modular-list-bulk.js @@ -925,9 +925,11 @@ /** * Normalize a user_select value to the "user-{id}" string format expected by - * the backend and conditional-removal logic. Accepts values from - * ComponentService.convertValue(dt-users-connection), dt-users-connection.value, - * or legacy shapes. + * the backend and conditional-removal logic. + * + * In practice we only see: + * - numbers from ComponentService.convertValue('DT-USERS-CONNECTION', ...) + * - strings like "user-5" or "5" from API/legacy data */ function normalizeUserSelectValue(rawValue) { if (rawValue === null || rawValue === undefined) { @@ -957,44 +959,6 @@ return null; } - // Arrays – use the first entry - if (Array.isArray(rawValue)) { - if (rawValue.length === 0) { - return null; - } - return normalizeUserSelectValue(rawValue[0]); - } - - // Objects - if (typeof rawValue === 'object') { - // Legacy stored shape { 'assigned-to': 'user-5', ... } - if ( - typeof rawValue['assigned-to'] === 'string' && - rawValue['assigned-to'].trim() - ) { - return normalizeUserSelectValue(rawValue['assigned-to']); - } - - // Common id fields - let possibleId = - rawValue.value || - rawValue.id || - rawValue.user_id || - (Array.isArray(rawValue.values) ? rawValue.values[0] : null); - - if (possibleId === null || possibleId === undefined) { - return null; - } - - if (typeof possibleId === 'string') { - return normalizeUserSelectValue(possibleId); - } - - if (typeof possibleId === 'number') { - return `user-${possibleId}`; - } - } - return null; } From b546e99367d9b0ecf0d099335fa06488e70c9869 Mon Sep 17 00:00:00 2001 From: kodinkat Date: Mon, 9 Mar 2026 12:42:21 +0000 Subject: [PATCH 5/5] Remove unused bulk edit field handler initialization in modular-list-bulk.js to streamline code and improve maintainability. --- dt-assets/js/modular-list-bulk.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/dt-assets/js/modular-list-bulk.js b/dt-assets/js/modular-list-bulk.js index d573c5500..4cac3fc6a 100644 --- a/dt-assets/js/modular-list-bulk.js +++ b/dt-assets/js/modular-list-bulk.js @@ -2139,19 +2139,9 @@ console.error('ComponentService initialization error:', e); } } - - // Initialize field-specific handlers if needed - initializeBulkEditFieldHandlers(fieldKey, fieldType); }); } - function initializeBulkEditFieldHandlers(fieldKey, fieldType) { - // For all web components (including user_select via dt-users-connection), - // ComponentService.initialize() and the global dt:get-data listener handle - // initialization and data wiring. No per-field-type handlers are required - // at this time. - } - /** * Field Clear/Restore Handlers */