Skip to content

Commit c5a8e71

Browse files
authored
#2895 - Bulk Edit - User Select Typeahead (#2899)
* 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. * 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. * 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. * 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. * Remove unused bulk edit field handler initialization in modular-list-bulk.js to streamline code and improve maintainability. * Simplify * npm run build
1 parent 8256256 commit c5a8e71

8 files changed

Lines changed: 2432 additions & 2127 deletions

File tree

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,33 @@
1-
import { E as s, C as e, w as D, B as o, D as i, a as l, b as n, d as r, e as c, f as u, u as p, A as C, g as M, x as T, h as m, i as x, j as S, y as g, k as B, t as d, r as h, l as v, m as A, n as L, o as b, p as f, z as y, q as F, c as j, v as k } from "./index-B5P35fmQ.js";
1+
import { E as e, C as s, x as D, B as o, D as l, a as i, b as n, d as r, e as c, f as p, u, w as C, A as M, g as T, h as m, i as x, j as S, y as d, k as g, t as B, r as h, l as v, m as A, n as b, o as F, p as L, z as f, q as y, c as U, v as j } from "./index-DpLFOrwQ.js";
22
export {
3-
s as ApiService,
4-
e as ComponentService,
3+
e as ApiService,
4+
s as ComponentService,
55
D as DtAlert,
66
o as DtBase,
7-
i as DtButton,
8-
l as DtChurchHealthCircle,
7+
l as DtButton,
8+
i as DtChurchHealthCircle,
99
n as DtConnection,
1010
r as DtCopyText,
1111
c as DtDate,
12-
u as DtDatetime,
13-
p as DtFormBase,
14-
C as DtIcon,
15-
M as DtLabel,
16-
T as DtList,
12+
p as DtDatetime,
13+
u as DtFileUpload,
14+
C as DtFormBase,
15+
M as DtIcon,
16+
T as DtLabel,
1717
m as DtLocation,
1818
x as DtLocationMap,
1919
S as DtMapModal,
20-
g as DtModal,
21-
B as DtMultiSelect,
22-
d as DtMultiSelectButtonGroup,
20+
d as DtModal,
21+
g as DtMultiSelect,
22+
B as DtMultiSelectButtonGroup,
2323
h as DtMultiText,
2424
v as DtNumberField,
2525
A as DtSingleSelect,
26-
L as DtTags,
27-
b as DtText,
28-
f as DtTextArea,
29-
y as DtTile,
30-
F as DtToggle,
31-
j as DtUsersConnection,
32-
k as version
26+
b as DtTags,
27+
F as DtText,
28+
L as DtTextArea,
29+
f as DtTile,
30+
y as DtToggle,
31+
U as DtUsersConnection,
32+
j as version
3333
};

dt-assets/build/components/index.js

Lines changed: 1184 additions & 974 deletions
Large diffs are not rendered by default.

dt-assets/build/components/index.umd.cjs

Lines changed: 1176 additions & 966 deletions
Large diffs are not rendered by default.

dt-assets/build/css/light.min.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dt-assets/build/css/style.min.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dt-assets/js/modular-list-bulk.js

Lines changed: 48 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -822,6 +822,11 @@
822822
// For boolean fields, include even if false (false is a valid value)
823823
if (fieldType === 'boolean') {
824824
updatePayload[fieldKey] = fieldValue === true;
825+
} else if (fieldType === 'user_select') {
826+
const normalizedUser = normalizeUserSelectValue(fieldValue);
827+
if (normalizedUser !== null) {
828+
updatePayload[fieldKey] = normalizedUser;
829+
}
825830
} else {
826831
updatePayload[fieldKey] = fieldValue;
827832
}
@@ -918,6 +923,18 @@
918923
return Array.isArray(value) ? value : [];
919924
}
920925

926+
function normalizeUserSelectValue(rawValue) {
927+
if (!rawValue) return null;
928+
if (typeof rawValue === 'number') {
929+
return rawValue > 0 ? `user-${rawValue}` : null;
930+
}
931+
const str = String(rawValue).trim();
932+
if (!str) return null;
933+
if (str.startsWith('user-')) return str;
934+
if (/^\d+$/.test(str)) return `user-${str}`;
935+
return null;
936+
}
937+
921938
/**
922939
* Update bulkEditSelectedFields for the share field from dt-users-connection value.
923940
* Called on initial value and on change; lives at module scope to avoid redefining per render.
@@ -1100,32 +1117,6 @@
11001117
return null;
11011118
}
11021119

1103-
// Special case: user_select (uses typeahead, not web component)
1104-
if (fieldType === 'user_select') {
1105-
const fieldId = `bulk_${fieldKey}`;
1106-
const userInput = fieldWrapper.find(`.js-typeahead-${fieldId}`);
1107-
if (userInput.length > 0) {
1108-
const selectedUserId = userInput.data('selected-user-id');
1109-
if (selectedUserId) {
1110-
return `user-${selectedUserId}`;
1111-
}
1112-
// Fallback: check typeahead instance
1113-
const typeaheadSelector = `.js-typeahead-${fieldId}`;
1114-
const typeaheadInstance = window.Typeahead?.[typeaheadSelector];
1115-
if (
1116-
typeaheadInstance &&
1117-
typeaheadInstance.items &&
1118-
typeaheadInstance.items.length > 0
1119-
) {
1120-
const selectedItem = typeaheadInstance.items[0];
1121-
if (selectedItem && selectedItem.ID) {
1122-
return `user-${selectedItem.ID}`;
1123-
}
1124-
}
1125-
}
1126-
return null;
1127-
}
1128-
11291120
// Special case: communication_channel (dt-multi-text needs special formatting)
11301121
if (fieldType === 'communication_channel') {
11311122
const multiTextComponent =
@@ -1532,7 +1523,8 @@
15321523
fieldType === 'connection' ||
15331524
fieldType === 'location' ||
15341525
fieldType === 'location_meta' ||
1535-
fieldType === 'tags'
1526+
fieldType === 'tags' ||
1527+
fieldType === 'user_select'
15361528
) {
15371529
return;
15381530
}
@@ -2121,107 +2113,9 @@
21212113
console.error('ComponentService initialization error:', e);
21222114
}
21232115
}
2124-
2125-
// Initialize field-specific handlers if needed
2126-
initializeBulkEditFieldHandlers(fieldKey, fieldType);
21272116
});
21282117
}
21292118

2130-
function initializeBulkEditFieldHandlers(fieldKey, fieldType) {
2131-
// Special case: user_select uses typeahead (not a web component)
2132-
if (fieldType === 'user_select') {
2133-
const fieldId = `bulk_${fieldKey}`;
2134-
const userInput = $(`.js-typeahead-${fieldId}`);
2135-
2136-
if (userInput.length) {
2137-
// Destroy existing typeahead instance if it exists (for restore scenarios)
2138-
const typeaheadSelector = `.js-typeahead-${fieldId}`;
2139-
if (window.Typeahead && window.Typeahead[typeaheadSelector]) {
2140-
try {
2141-
// Try to destroy the existing instance
2142-
if (window.Typeahead[typeaheadSelector].destroy) {
2143-
window.Typeahead[typeaheadSelector].destroy();
2144-
}
2145-
delete window.Typeahead[typeaheadSelector];
2146-
} catch (e) {
2147-
// If destroy fails, just delete the reference
2148-
delete window.Typeahead[typeaheadSelector];
2149-
}
2150-
}
2151-
2152-
// Initialize typeahead
2153-
$.typeahead({
2154-
input: `.js-typeahead-${fieldId}`,
2155-
minLength: 0,
2156-
maxItem: 0,
2157-
accent: true,
2158-
searchOnFocus: true,
2159-
source: window.TYPEAHEADS.typeaheadUserSource(),
2160-
templateValue: '{{name}}',
2161-
template: function (query, item) {
2162-
return `<div class="assigned-to-row" dir="auto">
2163-
<span>
2164-
<span class="avatar"><img style="vertical-align: text-bottom" src="{{avatar}}"/></span>
2165-
${window.SHAREDFUNCTIONS.escapeHTML(item.name)}
2166-
</span>
2167-
${item.status_color ? `<span class="status-square" style="background-color: ${window.SHAREDFUNCTIONS.escapeHTML(item.status_color)};">&nbsp;</span>` : ''}
2168-
${
2169-
item.update_needed && item.update_needed > 0
2170-
? `<span>
2171-
<img style="height: 12px;" src="${window.SHAREDFUNCTIONS.escapeHTML(window.wpApiShare.template_dir)}/dt-assets/images/broken.svg"/>
2172-
<span style="font-size: 14px">${window.SHAREDFUNCTIONS.escapeHTML(item.update_needed)}</span>
2173-
</span>`
2174-
: ''
2175-
}
2176-
</div>`;
2177-
},
2178-
dynamic: true,
2179-
hint: true,
2180-
emptyTemplate: window.SHAREDFUNCTIONS.escapeHTML(
2181-
window.wpApiShare.translations.no_records_found,
2182-
),
2183-
callback: {
2184-
onClick: function (node, a, item, event) {
2185-
event.preventDefault();
2186-
this.hideLayout();
2187-
this.resetInput();
2188-
2189-
// Set the selected user value
2190-
const resultContainer = $(`#${fieldId}-result-container`);
2191-
resultContainer.html(
2192-
`<span class="selected-result">${window.SHAREDFUNCTIONS.escapeHTML(item.name)}</span>`,
2193-
);
2194-
2195-
// Store the selected user ID in data attributes for later collection
2196-
userInput.data('selected-user-id', item.ID);
2197-
userInput.data('selected-user-name', item.name);
2198-
resultContainer.data('selected-user-id', item.ID);
2199-
resultContainer.data('selected-user-name', item.name);
2200-
},
2201-
onResult: function (node, query, result, resultCount) {
2202-
const resultContainer = $(`#${fieldId}-result-container`);
2203-
if (resultCount > 0) {
2204-
resultContainer.html(
2205-
`${resultCount} ${window.wpApiShare.translations.user_found || 'user(s) found'}`,
2206-
);
2207-
} else {
2208-
resultContainer.html('');
2209-
}
2210-
},
2211-
onHideLayout: function () {
2212-
$(`#${fieldId}-result-container`).html('');
2213-
},
2214-
},
2215-
});
2216-
}
2217-
return;
2218-
}
2219-
2220-
// For all web components: ComponentService.initialize() handles initialization
2221-
// The global dt:get-data listener handles data fetching
2222-
// No per-field-type initialization needed
2223-
}
2224-
22252119
/**
22262120
* Field Clear/Restore Handlers
22272121
*/
@@ -2361,19 +2255,16 @@
23612255
displayHtml += '<em>No option selected</em>';
23622256
}
23632257
} else if (fieldType === 'user_select') {
2364-
// user_select returns "user-{id}" format
2365-
if (valuesToRemove) {
2366-
const userId = valuesToRemove.replace('user-', '');
2367-
// Try to get user name from typeahead data or make a simple display
2368-
const fieldId = `bulk_${fieldKey}`;
2369-
const userInput = $(`.js-typeahead-${fieldId}`);
2370-
let userName = `User ${userId}`;
2371-
if (userInput.length > 0) {
2372-
const storedName = userInput.data('selected-user-name');
2373-
if (storedName) {
2374-
userName = storedName;
2375-
}
2376-
}
2258+
const item = Array.isArray(valuesToRemove)
2259+
? valuesToRemove[0]
2260+
: valuesToRemove;
2261+
const userId =
2262+
item?.id ||
2263+
item?.user_id ||
2264+
(typeof item === 'string' ? item.replace('user-', '') : null);
2265+
const userName =
2266+
item?.label || item?.name || (userId ? `User ${userId}` : null);
2267+
if (userName) {
23772268
displayHtml += `<span class="label" style="opacity: 0.6; margin-right: 5px; margin-bottom: 5px; display: inline-block;">${window.SHAREDFUNCTIONS.escapeHTML(userName)}</span>`;
23782269
} else {
23792270
displayHtml += '<em>No user selected</em>';
@@ -2420,9 +2311,18 @@
24202311
if (component && component.value) {
24212312
rawValueWithLabels = component.value;
24222313
}
2314+
} else if (fieldType === 'user_select') {
2315+
const usersComponent = fieldWrapper.find('dt-users-connection')[0];
2316+
if (usersComponent && usersComponent.value) {
2317+
const items = normalizeShareComponentItems(usersComponent.value);
2318+
if (items.length > 0) {
2319+
rawValueWithLabels = items;
2320+
}
2321+
}
24232322
}
24242323

24252324
let hasValues = false;
2325+
let normalizedUserValueToRemove = null;
24262326
if (
24272327
currentValue !== null &&
24282328
currentValue !== undefined &&
@@ -2435,18 +2335,25 @@
24352335
) {
24362336
const values = currentValue?.values || currentValue;
24372337
hasValues = Array.isArray(values) && values.length > 0;
2438-
} else if (fieldType === 'key_select' || fieldType === 'user_select') {
2338+
} else if (fieldType === 'key_select') {
24392339
hasValues = true;
2340+
} else if (fieldType === 'user_select') {
2341+
normalizedUserValueToRemove = normalizeUserSelectValue(currentValue);
2342+
hasValues = !!normalizedUserValueToRemove;
24402343
}
24412344
}
24422345

24432346
if (supportsSelectiveRemoval && hasValues) {
24442347
fieldData.operation = 'remove';
2445-
fieldData.valuesToRemove = currentValue;
2348+
fieldData.valuesToRemove =
2349+
fieldType === 'user_select'
2350+
? normalizedUserValueToRemove
2351+
: currentValue;
24462352
fieldData.rawValueWithLabels = rawValueWithLabels;
24472353

24482354
const displayValue =
2449-
fieldType === 'connection' && rawValueWithLabels
2355+
(fieldType === 'connection' || fieldType === 'user_select') &&
2356+
rawValueWithLabels
24502357
? rawValueWithLabels
24512358
: currentValue;
24522359
const displayHtml = renderValuesToRemoveDisplay(
@@ -2483,7 +2390,6 @@
24832390
console.error('ComponentService initialization error:', e);
24842391
}
24852392
}
2486-
initializeBulkEditFieldHandlers(fieldKey, fieldType);
24872393
});
24882394
});
24892395
}

dt-assets/js/shared-functions.js

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,29 +1040,7 @@ window.SHAREDFUNCTIONS = {
10401040
}
10411041

10421042
case 'user_select': {
1043-
// user_select uses legacy typeahead (not a web component)
1044-
const fieldId = id;
1045-
return `<div id="${fieldKey}" class="${fieldId} dt_user_select">
1046-
<var id="${fieldId}-result-container" class="result-container ${fieldId}-result-container"></var>
1047-
<div id="${fieldId}_t" name="form-${fieldId}" class="scrollable-typeahead">
1048-
<div class="typeahead__container" style="margin-bottom: 0">
1049-
<div class="typeahead__field">
1050-
<span class="typeahead__query">
1051-
<input class="js-typeahead-${fieldId} input-height" dir="auto"
1052-
name="${fieldId}[query]" placeholder="${window.SHAREDFUNCTIONS.escapeHTML(window.wpApiShare?.translations?.search_users || 'Search Users')}"
1053-
data-field_type="user_select"
1054-
data-field="${fieldKey}"
1055-
autocomplete="off">
1056-
</span>
1057-
<span class="typeahead__button">
1058-
<button type="button" class="search_${fieldKey} typeahead__image_button input-height" data-id="${fieldKey}">
1059-
<img src="${window.SHAREDFUNCTIONS.escapeHTML(window.wpApiShare.template_dir)}/dt-assets/images/chevron_down.svg"/>
1060-
</button>
1061-
</span>
1062-
</div>
1063-
</div>
1064-
</div>
1065-
</div>`;
1043+
return `<dt-users-connection ${baseAttrs} single></dt-users-connection>`;
10661044
}
10671045

10681046
case 'communication_channel':

dt-assets/scss/_list.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,7 @@ table.js-list {
691691
dt-datetime label,
692692
dt-number label,
693693
dt-tags label,
694+
dt-users-connection label,
694695
dt-user-select label,
695696
dt-connection label,
696697
dt-location label {

0 commit comments

Comments
 (0)