From 37395b434dc2561974f41c3cd26e9272071a1385 Mon Sep 17 00:00:00 2001 From: kodinkat Date: Mon, 16 Feb 2026 13:36:11 +0000 Subject: [PATCH 1/8] Enhance generational map functionality in DT_Groups - Added logic to filter fields for the create form, excluding the title field and hidden fields without custom display. - Integrated mapbox token retrieval for location fields. - Updated JavaScript to render fields dynamically based on the new field settings, ensuring proper handling of title and other fields. - Improved modal handling for adding child groups, including validation for the title field and focus management upon opening the modal. --- dt-groups/base-setup.php | 39 ++++++ dt-groups/genmap-tile.js | 291 +++++++++++++++++++++++++++++++++++---- 2 files changed, 300 insertions(+), 30 deletions(-) diff --git a/dt-groups/base-setup.php b/dt-groups/base-setup.php index ab16189b7..8336be9d6 100644 --- a/dt-groups/base-setup.php +++ b/dt-groups/base-setup.php @@ -1196,6 +1196,43 @@ public function scripts(){ } } + // Filter fields that should be shown in create form + // Note: title field is handled separately in JavaScript, so we exclude it here + $create_form_fields = []; + foreach ( $field_settings as $field_key => $field_setting ) { + // Skip title field - it's rendered separately in JavaScript + if ( $field_key === 'title' ) { + continue; + } + + // Skip hidden fields unless they have custom_display + if ( !empty( $field_setting['hidden'] ) && empty( $field_setting['custom_display'] ) ) { + continue; + } + // Skip fields explicitly set to not show in create form + if ( isset( $field_setting['in_create_form'] ) && $field_setting['in_create_form'] === false ) { + continue; + } + // Include fields that have in_create_form => true or are in the in_create_form array + if ( !empty( $field_setting['in_create_form'] ) ) { + if ( $field_setting['in_create_form'] === true || is_array( $field_setting['in_create_form'] ) ) { + $create_form_fields[ $field_key ] = $field_setting; + } + } + } + + // Add title field settings separately for JavaScript to use when rendering title explicitly + // We need title field settings available but not in the main fieldsToRender loop + if ( isset( $field_settings['title'] ) ) { + $create_form_fields['_title'] = $field_settings['title']; // Use _title as key to exclude from loop + } + + // Get mapbox token for location fields + $mapbox_key = ''; + if ( class_exists( 'DT_Mapbox_API' ) ) { + $mapbox_key = DT_Mapbox_API::get_key() ?? ''; + } + wp_localize_script( $genmap_script_handle, 'dtGroupGenmap', [ 'statusField' => [ 'key' => $status_key, @@ -1204,6 +1241,8 @@ public function scripts(){ ], 'groupTypes' => $group_type_labels, 'groupTypeIcons' => $group_type_icons, + 'fieldSettings' => $create_form_fields, + 'mapboxKey' => $mapbox_key, 'strings' => [ 'loading' => __( 'Loading map…', 'disciple_tools' ), 'error' => __( 'Unable to load generational map.', 'disciple_tools' ), diff --git a/dt-groups/genmap-tile.js b/dt-groups/genmap-tile.js index e7381f31a..bf8c3a710 100644 --- a/dt-groups/genmap-tile.js +++ b/dt-groups/genmap-tile.js @@ -627,14 +627,6 @@ html += ''; } - // Generation - if (data.content) { - html += '
'; - html += 'Generation:'; - html += `${window.lodash.escape(data.content)}`; - html += '
'; - } - // Actions html += '
'; @@ -2182,19 +2174,117 @@ } const modalStrings = window.dtGroupGenmap?.strings?.modal || {}; - const listHtml = ` - - - `; + )}" />`; + + // Create a copy of fieldSettings without title/_title/name to avoid duplication + // We only want to show title field, not name field + const fieldsToRender = {}; + Object.keys(fieldSettings).forEach((key) => { + // Exclude title, _title, and name (title is rendered separately, name is redundant) + if (key !== 'title' && key !== '_title' && key !== 'name') { + fieldsToRender[key] = fieldSettings[key]; + } + }); + + // Render fields using DT Web Components + // Always include title field first + // Check for _title (from PHP) or title (fallback) + const titleFieldSetting = fieldSettings._title || + fieldSettings.title || { + name: modalStrings.add_child_name_title || 'Name', + type: 'text', + }; + + if (window.SHAREDFUNCTIONS && window.SHAREDFUNCTIONS.renderField) { + const titleFieldHtml = window.SHAREDFUNCTIONS.renderField( + 'title', + titleFieldSetting, + fieldPrefix, + ); + if (titleFieldHtml) { + listHtml += `
${titleFieldHtml}
`; + } else { + // Fallback if renderField returns null + listHtml += ` + `; + } + } else { + // Fallback if web components not available + listHtml += ` + `; + } + + // Render other fields that have in_create_form => true + Object.keys(fieldsToRender).forEach((fieldKey) => { + // Double-check: skip title and name fields if they somehow still exist + if ( + fieldKey === 'title' || + fieldKey === 'name' || + fieldKey === '_title' + ) { + return; + } + + const fieldSetting = fieldsToRender[fieldKey]; + // Only render fields that are supported by renderField + if ( + window.SHAREDFUNCTIONS && + window.SHAREDFUNCTIONS.renderField && + fieldSetting && + fieldSetting.type + ) { + let fieldHtml = window.SHAREDFUNCTIONS.renderField( + fieldKey, + fieldSetting, + fieldPrefix, + ); - const buttonsHtml = ``; @@ -2215,29 +2305,169 @@ jQuery('#template_metrics_modal_title') .empty() .html(window.lodash.escape(title)); - jQuery(content).css('max-height', '300px'); + jQuery(content).css('max-height', '400px'); jQuery(content).css('overflow', 'auto'); jQuery(content).empty().html(listHtml); jQuery(modal).foundation('open'); + + // Focus first field after modal opens + setTimeout(() => { + const firstField = content.find('[name], [id]').first(); + if (firstField.length) { + firstField.focus(); + } + }, 100); } function handleAddChild() { - const postType = jQuery('#group_genmap_add_child_post_type').val(); - const parentId = jQuery('#group_genmap_add_child_post_id').val(); - const childTitle = jQuery('#group_genmap_add_child_name').val(); + const fieldPrefix = 'group_genmap_add_child_'; + const postType = jQuery(`#${fieldPrefix}post_type`).val(); + const parentId = jQuery(`#${fieldPrefix}post_id`).val(); + const modalContent = jQuery('#template_metrics_modal_content'); - if (!postType || !parentId || !childTitle) { + if (!postType || !parentId) { return; } + // Collect values from web components + const fields = {}; + const fieldSettings = window.dtGroupGenmap?.fieldSettings || {}; + + // Get title field value (required) + const titleField = modalContent.find(`#${fieldPrefix}title`)[0]; + if (titleField) { + if (titleField.tagName && titleField.tagName.startsWith('DT-')) { + // Web component - get value directly + if (titleField.value) { + fields.title = titleField.value; + } + } else { + // Fallback for regular input + const titleValue = jQuery(titleField).val(); + if (titleValue) { + fields.title = titleValue; + } + } + } + + // Validate title is present + if ( + !fields.title || + (typeof fields.title === 'string' && fields.title.trim() === '') + ) { + alert('Name is required'); + return; + } + + // Collect values from all other web components + modalContent + .find( + 'dt-text, dt-textarea, dt-number, dt-toggle, dt-date, dt-single-select, dt-multi-select-button-group, dt-tags, dt-connection, dt-location-map, dt-multi-text', + ) + .each(function () { + const component = this; + const fieldKey = + component.name || component.id?.replace(fieldPrefix, ''); + + // Skip hidden fields and system fields + if ( + !fieldKey || + fieldKey === 'post_type' || + fieldKey === 'post_id' || + fieldKey === 'title' || + fieldKey === 'name' + ) { + return; + } + + const fieldSetting = fieldSettings[fieldKey]; + if (!fieldSetting) { + return; + } + + // Get value from component + if ( + component.value === undefined || + component.value === null || + component.value === '' + ) { + return; + } + + let value = component.value; + + // Convert value using ComponentService if available + if (window.DtWebComponents && window.DtWebComponents.ComponentService) { + try { + value = window.DtWebComponents.ComponentService.convertValue( + component.tagName, + value, + ); + } catch (e) { + console.warn('Error converting value for field', fieldKey, e); + // Continue with original value if conversion fails + } + } + + // Format value based on field type for API + const fieldType = fieldSetting.type; + + switch (fieldType) { + case 'key_select': + // key_select: ComponentService returns the key, use directly + fields[fieldKey] = value; + break; + case 'multi_select': + // multi_select: ComponentService returns array, format for API + if (Array.isArray(value) && value.length > 0) { + fields[fieldKey] = { values: value.map((v) => ({ value: v })) }; + } + break; + case 'connection': + // connection: ComponentService returns array of IDs, format for API + if (Array.isArray(value) && value.length > 0) { + fields[fieldKey] = { values: value.map((v) => ({ value: v })) }; + } else if (value) { + fields[fieldKey] = { values: [{ value: value }] }; + } + break; + case 'communication_channel': + // communication_channel: ComponentService returns array of objects + if (Array.isArray(value) && value.length > 0) { + fields[fieldKey] = value; + } + break; + case 'tags': + // tags: ComponentService returns array, format for API + if (Array.isArray(value) && value.length > 0) { + fields[fieldKey] = { values: value.map((v) => ({ value: v })) }; + } else if (value) { + fields[fieldKey] = { values: [{ value: value }] }; + } + break; + default: + // For text, textarea, number, boolean, date, etc., use value directly + fields[fieldKey] = value; + break; + } + }); + + // Add parent connection + fields.parent_groups = { + values: [{ value: parentId }], + }; + + // Add additional meta for tracking + const createPayload = { + ...fields, + additional_meta: { + created_from: parentId, + add_connection: 'child_groups', + }, + }; + if (window.API && window.API.create_post) { - window.API.create_post(postType, { - title: childTitle, - additional_meta: { - created_from: parentId, - add_connection: 'child_groups', - }, - }) + window.API.create_post(postType, createPayload) .then((newPost) => { jQuery('#template_metrics_modal').foundation('close'); // Refresh the page to show the new child @@ -2290,7 +2520,8 @@ 'open.zf.reveal', '#template_metrics_modal[data-reveal]', function () { - jQuery('#group_genmap_add_child_name').focus(); + // Focus on title field (name field is no longer used) + jQuery('#group_genmap_add_child_title').focus(); }, ); From 9290d908c3bb4800b6e4b0c4d3601e1c39f177ef Mon Sep 17 00:00:00 2001 From: kodinkat Date: Mon, 16 Feb 2026 13:45:42 +0000 Subject: [PATCH 2/8] Refactor whitespace in base-setup.php for improved readability - Removed unnecessary whitespace around conditional checks and comments in the DT_Groups_Base class. - Enhanced code clarity without altering functionality. --- dt-groups/base-setup.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dt-groups/base-setup.php b/dt-groups/base-setup.php index 8336be9d6..935a0f65f 100644 --- a/dt-groups/base-setup.php +++ b/dt-groups/base-setup.php @@ -1204,7 +1204,7 @@ public function scripts(){ if ( $field_key === 'title' ) { continue; } - + // Skip hidden fields unless they have custom_display if ( !empty( $field_setting['hidden'] ) && empty( $field_setting['custom_display'] ) ) { continue; @@ -1220,7 +1220,7 @@ public function scripts(){ } } } - + // Add title field settings separately for JavaScript to use when rendering title explicitly // We need title field settings available but not in the main fieldsToRender loop if ( isset( $field_settings['title'] ) ) { From 38a666c88042e6aa06bbcb324b3e3eef4dc116ca Mon Sep 17 00:00:00 2001 From: kodinkat Date: Wed, 25 Feb 2026 12:13:00 +0000 Subject: [PATCH 3/8] Enhance group generational map functionality and UI - Added new labels for modal and details sections to improve accessibility and user experience. - Updated JavaScript to utilize dynamic strings for various UI elements, ensuring consistency across the application. - Adjusted CSS to allow dropdowns in modals to extend outside, preventing overflow clipping. - Improved error handling messages for better clarity when creating child groups. --- dt-groups/base-setup.php | 11 +++++ dt-groups/genmap-d3.css | 5 +++ dt-groups/genmap-tile.js | 95 +++++++++++++++++++++++----------------- 3 files changed, 72 insertions(+), 39 deletions(-) diff --git a/dt-groups/base-setup.php b/dt-groups/base-setup.php index 935a0f65f..7fa17baad 100644 --- a/dt-groups/base-setup.php +++ b/dt-groups/base-setup.php @@ -1247,14 +1247,25 @@ public function scripts(){ 'loading' => __( 'Loading map…', 'disciple_tools' ), 'error' => __( 'Unable to load generational map.', 'disciple_tools' ), 'empty' => __( 'No child groups to display.', 'disciple_tools' ), + 'chart_aria' => __( 'Group generational map', 'disciple_tools' ), 'details' => [ 'open' => __( 'Open', 'disciple_tools' ), 'add' => __( 'Add', 'disciple_tools' ), + 'close' => __( 'Close', 'disciple_tools' ), + 'type' => __( 'Type', 'disciple_tools' ), + 'status' => __( 'Status', 'disciple_tools' ), + 'members' => __( 'Members', 'disciple_tools' ), + 'assigned' => __( 'Assigned', 'disciple_tools' ), + 'expand' => __( 'Expand', 'disciple_tools' ), + 'collapse' => __( 'Collapse', 'disciple_tools' ), ], 'modal' => [ 'add_child_title' => __( 'Add Child To', 'disciple_tools' ), 'add_child_name_title' => __( 'Name', 'disciple_tools' ), 'add_child_but' => __( 'Add Child', 'disciple_tools' ), + 'name_required' => __( 'Name is required', 'disciple_tools' ), + 'error_creating_child' => __( 'Error creating child group: %s', 'disciple_tools' ), + 'unknown_error' => __( 'Unknown error', 'disciple_tools' ), ], ], 'recordUrlBase' => trailingslashit( site_url() ), diff --git a/dt-groups/genmap-d3.css b/dt-groups/genmap-d3.css index da5d10ad6..7c55f4194 100644 --- a/dt-groups/genmap-d3.css +++ b/dt-groups/genmap-d3.css @@ -396,3 +396,8 @@ outline: 2px solid #eed936; outline-offset: 2px; } + +/* Add Child modal: allow location dropdown to extend outside (avoid clipping) */ +#template_metrics_modal_content .form-field-location { + overflow: visible; +} diff --git a/dt-groups/genmap-tile.js b/dt-groups/genmap-tile.js index 18370fdf0..eee2138f5 100644 --- a/dt-groups/genmap-tile.js +++ b/dt-groups/genmap-tile.js @@ -677,13 +677,13 @@ // Build HTML with header containing title and close button let html = '
'; html += `

${window.lodash.escape(data.name || '')}

`; - html += ''; + html += ``; html += '
'; // Group type with icon if (groupTypeLabel) { html += '
'; - html += 'Type:'; + html += `${window.lodash.escape(detailsStrings.type || 'Type')}:`; if (groupTypeIcon) { html += `${window.lodash.escape(groupTypeLabel)}`; } else { @@ -696,7 +696,7 @@ if (status) { const statusColor = data.statusColor || '#3f729b'; html += '
'; - html += 'Status:'; + html += `${window.lodash.escape(detailsStrings.status || 'Status')}:`; html += `${window.lodash.escape(status)}`; html += '
'; } @@ -717,8 +717,10 @@ (nodeData._children && nodeData._children.length > 0) ) { const isCollapsed = nodeData.data.collapsed || false; - const collapseText = isCollapsed ? 'Expand' : 'Collapse'; - html += ``; + const collapseText = isCollapsed + ? (detailsStrings.expand || 'Expand') + : (detailsStrings.collapse || 'Collapse'); + html += ``; } html += '
'; @@ -1985,7 +1987,7 @@ style="width: 100%; height: 100%;">
`; @@ -2095,27 +2097,36 @@ let detailsHtml = '
'; detailsHtml += '
'; + const detailsLabels = window.dtGroupGenmap?.strings?.details || {}; if (data.group_status && data.group_status.label) { detailsHtml += - '

Status: ' + + '

' + + window.lodash.escape(detailsLabels.status || 'Status') + + ': ' + window.lodash.escape(data.group_status.label) + '

'; } if (data.group_type && data.group_type.label) { detailsHtml += - '

Type: ' + + '

' + + window.lodash.escape(detailsLabels.type || 'Type') + + ': ' + window.lodash.escape(data.group_type.label) + '

'; } if (data.member_count !== undefined) { detailsHtml += - '

Members: ' + + '

' + + window.lodash.escape(detailsLabels.members || 'Members') + + ': ' + window.lodash.escape(data.member_count) + '

'; } if (data.assigned_to && data.assigned_to.display) { detailsHtml += - '

Assigned: ' + + '

' + + window.lodash.escape(detailsLabels.assigned || 'Assigned') + + ': ' + window.lodash.escape(data.assigned_to.display) + '

'; } @@ -2250,31 +2261,14 @@ fieldPrefix, ); - // Override mapbox token for location fields if available from dtGroupGenmap - if ( - fieldHtml && - (fieldSetting.type === 'location' || - fieldSetting.type === 'location_meta') - ) { - const mapboxKey = window.dtGroupGenmap?.mapboxKey || ''; - // Replace mapbox-token attribute if present - if (mapboxKey) { - fieldHtml = fieldHtml.replace( - /mapbox-token="[^"]*"/, - `mapbox-token="${window.lodash.escape(mapboxKey)}"`, - ); - // If mapbox-token attribute doesn't exist, add it - if (!fieldHtml.includes('mapbox-token=')) { - fieldHtml = fieldHtml.replace( - /(]*)/, - `$1 mapbox-token="${window.lodash.escape(mapboxKey)}"`, - ); - } - } - } - if (fieldHtml) { - listHtml += `
${fieldHtml}
`; + const isLocation = + fieldSetting.type === 'location' || + fieldSetting.type === 'location_meta'; + const fieldClass = isLocation + ? 'form-field form-field-location' + : 'form-field'; + listHtml += `
${fieldHtml}
`; } } }); @@ -2301,10 +2295,21 @@ .empty() .html(window.lodash.escape(title)); jQuery(content).css('max-height', '400px'); - jQuery(content).css('overflow', 'auto'); + jQuery(content).css('overflow-y', 'auto'); jQuery(content).empty().html(listHtml); jQuery(modal).foundation('open'); + // Set mapbox token on location components after insertion (avoids fragile HTML string replace) + const mapboxKey = window.dtGroupGenmap?.mapboxKey || ''; + if (mapboxKey) { + content.find('dt-location-map').each(function () { + this.setAttribute('mapbox-token', mapboxKey); + }); + } + + // Allow location dropdown to extend outside modal (avoid overflow clipping) + content.css('overflow-x', 'visible').css('overflow-y', 'auto'); + // Focus first field after modal opens setTimeout(() => { const firstField = content.find('[name], [id]').first(); @@ -2350,10 +2355,15 @@ !fields.title || (typeof fields.title === 'string' && fields.title.trim() === '') ) { - alert('Name is required'); + const msg = + window.dtGroupGenmap?.strings?.modal?.name_required || 'Name is required'; + alert(msg); return; } + const submitBtn = jQuery(`#${fieldPrefix}but`); + submitBtn.attr('disabled', true).addClass('loading'); + // Collect values from all other web components modalContent .find( @@ -2464,17 +2474,24 @@ if (window.API && window.API.create_post) { window.API.create_post(postType, createPayload) .then((newPost) => { + submitBtn.attr('disabled', false).removeClass('loading'); jQuery('#template_metrics_modal').foundation('close'); // Refresh the page to show the new child window.location.reload(); }) .catch(function (error) { + submitBtn.attr('disabled', false).removeClass('loading'); console.error(error); - alert( - 'Error creating child group: ' + (error.message || 'Unknown error'), - ); + const template = + window.dtGroupGenmap?.strings?.modal?.error_creating_child || + 'Error creating child group: %s'; + const unknownErr = + window.dtGroupGenmap?.strings?.modal?.unknown_error || 'Unknown error'; + const msg = (template && template.replace('%s', error.message || unknownErr)) || ('Error creating child group: ' + (error.message || unknownErr)); + alert(msg); }); } else { + submitBtn.attr('disabled', false).removeClass('loading'); console.error('window.API.create_post is not available'); } } From 2784fd1ca9ebf738bf174051129b066d17dfc953 Mon Sep 17 00:00:00 2001 From: kodinkat Date: Wed, 25 Feb 2026 12:29:23 +0000 Subject: [PATCH 4/8] Refactor group management logic and improve user experience - Added logic to skip 'user_select' fields in the Add Child flow to streamline the process. - Cleaned up JavaScript code for better readability and consistency, including dynamic string handling. - Enhanced error handling for parent ID validation to ensure proper data types and prevent NaN issues. - Updated focus handling for modal fields to improve accessibility and user interaction. --- dt-groups/base-setup.php | 5 +++++ dt-groups/genmap-tile.js | 37 ++++++++++++++----------------------- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/dt-groups/base-setup.php b/dt-groups/base-setup.php index 7fa17baad..a1249d3e8 100644 --- a/dt-groups/base-setup.php +++ b/dt-groups/base-setup.php @@ -1205,6 +1205,11 @@ public function scripts(){ continue; } + // Skip user_select (e.g. assigned_to) - not part of Add Child flow; uses legacy typeahead, not collected + if ( isset( $field_setting['type'] ) && $field_setting['type'] === 'user_select' ) { + continue; + } + // Skip hidden fields unless they have custom_display if ( !empty( $field_setting['hidden'] ) && empty( $field_setting['custom_display'] ) ) { continue; diff --git a/dt-groups/genmap-tile.js b/dt-groups/genmap-tile.js index eee2138f5..239303ebc 100644 --- a/dt-groups/genmap-tile.js +++ b/dt-groups/genmap-tile.js @@ -718,8 +718,8 @@ ) { const isCollapsed = nodeData.data.collapsed || false; const collapseText = isCollapsed - ? (detailsStrings.expand || 'Expand') - : (detailsStrings.collapse || 'Collapse'); + ? detailsStrings.expand || 'Expand' + : detailsStrings.collapse || 'Collapse'; html += ``; } @@ -2309,23 +2309,15 @@ // Allow location dropdown to extend outside modal (avoid overflow clipping) content.css('overflow-x', 'visible').css('overflow-y', 'auto'); - - // Focus first field after modal opens - setTimeout(() => { - const firstField = content.find('[name], [id]').first(); - if (firstField.length) { - firstField.focus(); - } - }, 100); } function handleAddChild() { const fieldPrefix = 'group_genmap_add_child_'; const postType = jQuery(`#${fieldPrefix}post_type`).val(); - const parentId = jQuery(`#${fieldPrefix}post_id`).val(); + const parentId = parseInt(jQuery(`#${fieldPrefix}post_id`).val(), 10); const modalContent = jQuery('#template_metrics_modal_content'); - if (!postType || !parentId) { + if (!postType || !parentId || Number.isNaN(parentId)) { return; } @@ -2356,7 +2348,8 @@ (typeof fields.title === 'string' && fields.title.trim() === '') ) { const msg = - window.dtGroupGenmap?.strings?.modal?.name_required || 'Name is required'; + window.dtGroupGenmap?.strings?.modal?.name_required || + 'Name is required'; alert(msg); return; } @@ -2457,12 +2450,7 @@ } }); - // Add parent connection - fields.parent_groups = { - values: [{ value: parentId }], - }; - - // Add additional meta for tracking + // Parent connection is set via additional_meta (API overwrites connection from it) const createPayload = { ...fields, additional_meta: { @@ -2486,8 +2474,9 @@ window.dtGroupGenmap?.strings?.modal?.error_creating_child || 'Error creating child group: %s'; const unknownErr = - window.dtGroupGenmap?.strings?.modal?.unknown_error || 'Unknown error'; - const msg = (template && template.replace('%s', error.message || unknownErr)) || ('Error creating child group: ' + (error.message || unknownErr)); + window.dtGroupGenmap?.strings?.modal?.unknown_error || + 'Unknown error'; + const msg = template.replace('%s', error.message || unknownErr); alert(msg); }); } else { @@ -2532,8 +2521,10 @@ 'open.zf.reveal', '#template_metrics_modal[data-reveal]', function () { - // Focus on title field (name field is no longer used) - jQuery('#group_genmap_add_child_title').focus(); + const titleField = jQuery('#group_genmap_add_child_title'); + if (titleField.length) { + titleField[0].focus(); + } }, ); From ea9b87984957f8ad358306a87775c03f05efa608 Mon Sep 17 00:00:00 2001 From: kodinkat Date: Wed, 25 Feb 2026 12:47:36 +0000 Subject: [PATCH 5/8] Refactor title field handling in group management - Updated PHP to pass title field settings as a dedicated key instead of including it in the main fieldsToRender loop. - Adjusted JavaScript to utilize the new titleFieldSetting key for rendering, improving clarity and reducing redundancy. - Enhanced comments for better understanding of the title field rendering process. --- dt-groups/base-setup.php | 8 +++---- dt-groups/genmap-tile.js | 50 +++++++++++++++++----------------------- 2 files changed, 24 insertions(+), 34 deletions(-) diff --git a/dt-groups/base-setup.php b/dt-groups/base-setup.php index a1249d3e8..e2db724f8 100644 --- a/dt-groups/base-setup.php +++ b/dt-groups/base-setup.php @@ -1226,11 +1226,8 @@ public function scripts(){ } } - // Add title field settings separately for JavaScript to use when rendering title explicitly - // We need title field settings available but not in the main fieldsToRender loop - if ( isset( $field_settings['title'] ) ) { - $create_form_fields['_title'] = $field_settings['title']; // Use _title as key to exclude from loop - } + // Title field is rendered separately in JS; pass its settings as a dedicated key (not in fieldSettings loop). + $title_field_setting = isset( $field_settings['title'] ) ? $field_settings['title'] : null; // Get mapbox token for location fields $mapbox_key = ''; @@ -1247,6 +1244,7 @@ public function scripts(){ 'groupTypes' => $group_type_labels, 'groupTypeIcons' => $group_type_icons, 'fieldSettings' => $create_form_fields, + 'titleFieldSetting' => $title_field_setting, 'mapboxKey' => $mapbox_key, 'strings' => [ 'loading' => __( 'Loading map…', 'disciple_tools' ), diff --git a/dt-groups/genmap-tile.js b/dt-groups/genmap-tile.js index 239303ebc..f476fccd8 100644 --- a/dt-groups/genmap-tile.js +++ b/dt-groups/genmap-tile.js @@ -658,6 +658,7 @@ * @returns {string} HTML content */ function buildPopoverContent(nodeData) { + // Generation number is intentionally not shown in the popover (see issue #2878). const data = nodeData.data; const strings = window.dtGroupGenmap?.strings || {}; const detailsStrings = strings.details || {}; @@ -2192,24 +2193,19 @@ postId, )}" />`; - // Create a copy of fieldSettings without title/_title/name to avoid duplication - // We only want to show title field, not name field + // fieldSettings contains only in_create_form fields (title excluded in PHP). Title is rendered from titleFieldSetting. const fieldsToRender = {}; Object.keys(fieldSettings).forEach((key) => { - // Exclude title, _title, and name (title is rendered separately, name is redundant) - if (key !== 'title' && key !== '_title' && key !== 'name') { + if (key !== 'title' && key !== 'name') { fieldsToRender[key] = fieldSettings[key]; } }); - // Render fields using DT Web Components - // Always include title field first - // Check for _title (from PHP) or title (fallback) - const titleFieldSetting = fieldSettings._title || - fieldSettings.title || { - name: modalStrings.add_child_name_title || 'Name', - type: 'text', - }; + // Title field is passed as a separate top-level key from PHP (titleFieldSetting) + const titleFieldSetting = window.dtGroupGenmap?.titleFieldSetting || { + name: modalStrings.add_child_name_title || 'Name', + type: 'text', + }; if (window.SHAREDFUNCTIONS && window.SHAREDFUNCTIONS.renderField) { const titleFieldHtml = window.SHAREDFUNCTIONS.renderField( @@ -2237,25 +2233,23 @@ } // Render other fields that have in_create_form => true + const hasRenderField = !!( + window.SHAREDFUNCTIONS && window.SHAREDFUNCTIONS.renderField + ); + if (Object.keys(fieldsToRender).length > 0 && !hasRenderField) { + console.warn( + 'Genmapper Add Child: SHAREDFUNCTIONS.renderField is unavailable; in_create_form fields other than title will not be rendered.', + ); + } Object.keys(fieldsToRender).forEach((fieldKey) => { - // Double-check: skip title and name fields if they somehow still exist - if ( - fieldKey === 'title' || - fieldKey === 'name' || - fieldKey === '_title' - ) { + if (fieldKey === 'title' || fieldKey === 'name') { return; } const fieldSetting = fieldsToRender[fieldKey]; // Only render fields that are supported by renderField - if ( - window.SHAREDFUNCTIONS && - window.SHAREDFUNCTIONS.renderField && - fieldSetting && - fieldSetting.type - ) { - let fieldHtml = window.SHAREDFUNCTIONS.renderField( + if (hasRenderField && fieldSetting && fieldSetting.type) { + const fieldHtml = window.SHAREDFUNCTIONS.renderField( fieldKey, fieldSetting, fieldPrefix, @@ -2306,9 +2300,7 @@ this.setAttribute('mapbox-token', mapboxKey); }); } - - // Allow location dropdown to extend outside modal (avoid overflow clipping) - content.css('overflow-x', 'visible').css('overflow-y', 'auto'); + // Location dropdown visibility is handled by #template_metrics_modal_content .form-field-location { overflow: visible } in genmap-d3.css } function handleAddChild() { @@ -2317,7 +2309,7 @@ const parentId = parseInt(jQuery(`#${fieldPrefix}post_id`).val(), 10); const modalContent = jQuery('#template_metrics_modal_content'); - if (!postType || !parentId || Number.isNaN(parentId)) { + if (!postType || !parentId) { return; } From 5636b48363d59375da53764f8d7e406f3789c168 Mon Sep 17 00:00:00 2001 From: kodinkat Date: Wed, 25 Feb 2026 13:11:31 +0000 Subject: [PATCH 6/8] Update in_create_form logic for field inclusion in group creation - Refactored the condition for including fields in the create form to clarify the handling of the in_create_form setting. - Improved comments to enhance understanding of the logic related to post type inclusion in the create form array. --- dt-groups/base-setup.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/dt-groups/base-setup.php b/dt-groups/base-setup.php index e2db724f8..d70a528cc 100644 --- a/dt-groups/base-setup.php +++ b/dt-groups/base-setup.php @@ -1218,9 +1218,12 @@ public function scripts(){ if ( isset( $field_setting['in_create_form'] ) && $field_setting['in_create_form'] === false ) { continue; } - // Include fields that have in_create_form => true or are in the in_create_form array + // Include fields that have in_create_form => true or list this post type in the array if ( !empty( $field_setting['in_create_form'] ) ) { - if ( $field_setting['in_create_form'] === true || is_array( $field_setting['in_create_form'] ) ) { + $in_form = $field_setting['in_create_form']; + if ( $in_form === true ) { + $create_form_fields[ $field_key ] = $field_setting; + } elseif ( is_array( $in_form ) && in_array( $this->post_type, $in_form, true ) ) { $create_form_fields[ $field_key ] = $field_setting; } } From f3cd31523322e54ff92a3523d74d1ed3a7a6c1a8 Mon Sep 17 00:00:00 2001 From: kodinkat Date: Mon, 9 Mar 2026 10:49:22 +0000 Subject: [PATCH 7/8] Enhance Add Child modal for Genmapper: Allow location dropdown to extend outside modal boundaries and adjust overflow settings for improved usability. Implemented class management for modal closure to maintain styling integrity. --- dt-groups/genmap-d3.css | 8 ++++++-- dt-groups/genmap-tile.js | 15 ++++++++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/dt-groups/genmap-d3.css b/dt-groups/genmap-d3.css index 7c55f4194..2f00d7469 100644 --- a/dt-groups/genmap-d3.css +++ b/dt-groups/genmap-d3.css @@ -397,7 +397,11 @@ outline-offset: 2px; } -/* Add Child modal: allow location dropdown to extend outside (avoid clipping) */ -#template_metrics_modal_content .form-field-location { +/* Add Child modal (Genmapper): allow location dropdown to extend outside (avoid clipping) */ +#template_metrics_modal.genmap-add-child-modal { + overflow: visible; +} + +#template_metrics_modal.genmap-add-child-modal #template_metrics_modal_content .form-field-location { overflow: visible; } diff --git a/dt-groups/genmap-tile.js b/dt-groups/genmap-tile.js index f476fccd8..22c8ae6d3 100644 --- a/dt-groups/genmap-tile.js +++ b/dt-groups/genmap-tile.js @@ -2283,13 +2283,17 @@ return; } + // Mark this usage so CSS can safely relax overflow rules without affecting other metrics modals + modal.addClass('genmap-add-child-modal'); + modal.css('overflow', 'visible'); + jQuery(modalButtons).empty().html(buttonsHtml); jQuery('#template_metrics_modal_title') .empty() .html(window.lodash.escape(title)); jQuery(content).css('max-height', '400px'); - jQuery(content).css('overflow-y', 'auto'); + jQuery(content).css('overflow-y', 'visible'); jQuery(content).empty().html(listHtml); jQuery(modal).foundation('open'); @@ -2509,6 +2513,15 @@ handleAddChild(); }); + // When the shared metrics modal closes, clear any Genmapper-specific modal classes + jQuery(document).on( + 'closed.zf.reveal', + '#template_metrics_modal[data-reveal]', + function () { + jQuery('#template_metrics_modal').removeClass('genmap-add-child-modal'); + }, + ); + jQuery(document).on( 'open.zf.reveal', '#template_metrics_modal[data-reveal]', From 9b9fa9f397e537c83d7dec3528dd077c264874b6 Mon Sep 17 00:00:00 2001 From: kodinkat Date: Mon, 9 Mar 2026 11:01:18 +0000 Subject: [PATCH 8/8] Refactor Genmap Add Child functionality: Introduced a constant for the child field prefix to improve code maintainability. Updated field prefix usage in multiple locations for consistency. Enhanced button state management by switching from to for disabling buttons, ensuring better compatibility across browsers. --- dt-groups/genmap-tile.js | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/dt-groups/genmap-tile.js b/dt-groups/genmap-tile.js index 22c8ae6d3..8f3e2f884 100644 --- a/dt-groups/genmap-tile.js +++ b/dt-groups/genmap-tile.js @@ -6,6 +6,7 @@ const MAX_CANVAS_HEIGHT = 320; const MIN_CANVAS_HEIGHT = 220; const LAYOUT_STORAGE_KEY = 'group_genmap_layout'; + const GENMAP_ADD_CHILD_FIELD_PREFIX = 'group_genmap_add_child_'; // Node dimensions constants const NODE_WIDTH = 72; // Increased from 60px to prevent text clipping @@ -2182,7 +2183,7 @@ const modalStrings = window.dtGroupGenmap?.strings?.modal || {}; const fieldSettings = window.dtGroupGenmap?.fieldSettings || {}; - const fieldPrefix = 'group_genmap_add_child_'; + const fieldPrefix = GENMAP_ADD_CHILD_FIELD_PREFIX; // Build hidden inputs for post type and parent ID let listHtml = ` @@ -2196,6 +2197,7 @@ // fieldSettings contains only in_create_form fields (title excluded in PHP). Title is rendered from titleFieldSetting. const fieldsToRender = {}; Object.keys(fieldSettings).forEach((key) => { + // Skip title (handled separately) and legacy "name" field if present to avoid duplicating the label. if (key !== 'title' && key !== 'name') { fieldsToRender[key] = fieldSettings[key]; } @@ -2242,6 +2244,7 @@ ); } Object.keys(fieldsToRender).forEach((fieldKey) => { + // Safety guard in case title/name ever slip into fieldsToRender. if (fieldKey === 'title' || fieldKey === 'name') { return; } @@ -2308,7 +2311,7 @@ } function handleAddChild() { - const fieldPrefix = 'group_genmap_add_child_'; + const fieldPrefix = GENMAP_ADD_CHILD_FIELD_PREFIX; const postType = jQuery(`#${fieldPrefix}post_type`).val(); const parentId = parseInt(jQuery(`#${fieldPrefix}post_id`).val(), 10); const modalContent = jQuery('#template_metrics_modal_content'); @@ -2351,7 +2354,7 @@ } const submitBtn = jQuery(`#${fieldPrefix}but`); - submitBtn.attr('disabled', true).addClass('loading'); + submitBtn.prop('disabled', true).addClass('loading'); // Collect values from all other web components modalContent @@ -2360,8 +2363,11 @@ ) .each(function () { const component = this; - const fieldKey = - component.name || component.id?.replace(fieldPrefix, ''); + const rawId = component.id || ''; + const idWithoutPrefix = rawId.startsWith(fieldPrefix) + ? rawId.slice(fieldPrefix.length) + : rawId; + const fieldKey = component.name || idWithoutPrefix; // Skip hidden fields and system fields if ( @@ -2458,13 +2464,13 @@ if (window.API && window.API.create_post) { window.API.create_post(postType, createPayload) .then((newPost) => { - submitBtn.attr('disabled', false).removeClass('loading'); + submitBtn.prop('disabled', false).removeClass('loading'); jQuery('#template_metrics_modal').foundation('close'); // Refresh the page to show the new child window.location.reload(); }) .catch(function (error) { - submitBtn.attr('disabled', false).removeClass('loading'); + submitBtn.prop('disabled', false).removeClass('loading'); console.error(error); const template = window.dtGroupGenmap?.strings?.modal?.error_creating_child || @@ -2476,7 +2482,7 @@ alert(msg); }); } else { - submitBtn.attr('disabled', false).removeClass('loading'); + submitBtn.prop('disabled', false).removeClass('loading'); console.error('window.API.create_post is not available'); } }