feat(personas): introduce new keytag custom field implementation#34625
feat(personas): introduce new keytag custom field implementation#34625
Conversation
### Summary This commit adds a new implementation for the keytag custom field, enhancing the user experience by allowing dynamic updates based on the name field input. ### Changes - **New File**: Added `keytag_custom_field_new.vtl` which includes JavaScript functions for camelizing the name input and updating the keytag field accordingly. - **Old Implementation**: The previous keytag custom field logic has been preserved in `keytag_custom_field_old.vtl` for backward compatibility. - **Integration**: Updated `keytag_custom_field.vtl` to conditionally parse the new or old implementation based on the `isNewEditModeEnabled` flag. ### Benefits This new approach improves the usability of the keytag field by allowing real-time updates and editing capabilities, enhancing the overall functionality of the personas feature.
…dering to support new edit mode ### Summary This commit refactors the file browser and tag storage field rendering templates to conditionally include new implementations based on the flag. ### Changes - **File Browser**: Removed old JavaScript and HTML structure from , now conditionally parses either or . - **Tag Storage Field**: Similar changes made in , parsing either or . ### Benefits This update streamlines the rendering logic for both components, allowing for easier maintenance and improved user experience in the new edit mode.
There was a problem hiding this comment.
Pull request overview
This pull request migrates multiple custom fields to the new Custom Field API, implementing a conditional rendering pattern based on the edit mode. The primary focus is on the persona keytag custom field (as mentioned in issue #34450), but it also includes migrations for tag storage and file browser fields, plus a refactoring of shared dropdown styles.
Changes:
- Migrated persona keytag custom field to new Custom Field API with modern JavaScript and styling
- Migrated tag storage field to new Custom Field API with dropdown implementation
- Migrated file browser field to new Custom Field API with modal integration
- Refactored shared dropdown CSS from template_custom_field_new.vtl to native-field.component.scss
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 14 comments.
Show a summary per file
| File | Description |
|---|---|
| keytag_custom_field.vtl | Main switcher that conditionally loads new/old implementation |
| keytag_custom_field_old.vtl | Preserved legacy Dojo-based keytag implementation |
| keytag_custom_field_new.vtl | New DotCustomFieldApi-based keytag implementation |
| tag_storage_field_creation.vtl | Main switcher for tag storage field |
| tag_storage_field_creation_old.vtl | Preserved legacy Dojo-based tag storage dropdown |
| tag_storage_field_creation_new.vtl | New custom dropdown with DotCustomFieldApi integration |
| file_browser_field_render.vtl | Main switcher for file browser field |
| file_browser_field_render_old.vtl | Preserved legacy Dojo-based file browser |
| file_browser_field_render_new.vtl | New modal-based file browser implementation |
| template_custom_field_new.vtl | Removed redundant CSS styles (moved to SCSS) |
| native-field.component.scss | Added shared dropdown/select styles for custom fields |
dotCMS/src/main/webapp/WEB-INF/velocity/static/htmlpage_assets/template_custom_field_new.vtl
Show resolved
Hide resolved
dotCMS/src/main/webapp/WEB-INF/velocity/static/content/file_browser_field_render_new.vtl
Show resolved
Hide resolved
dotCMS/src/main/webapp/WEB-INF/velocity/static/personas/keytag_custom_field_new.vtl
Show resolved
Hide resolved
dotCMS/src/main/webapp/WEB-INF/velocity/static/personas/keytag_custom_field_new.vtl
Show resolved
Hide resolved
dotCMS/src/main/webapp/WEB-INF/velocity/static/tag/tag_storage_field_creation.vtl
Outdated
Show resolved
Hide resolved
dotCMS/src/main/webapp/WEB-INF/velocity/static/tag/tag_storage_field_creation.vtl
Show resolved
Hide resolved
dotCMS/src/main/webapp/WEB-INF/velocity/static/content/file_browser_field_render_new.vtl
Show resolved
Hide resolved
dotCMS/src/main/webapp/WEB-INF/velocity/static/personas/keytag_custom_field_old.vtl
Show resolved
Hide resolved
dotCMS/src/main/webapp/WEB-INF/velocity/static/tag/tag_storage_field_creation_old.vtl
Show resolved
Hide resolved
dotCMS/src/main/webapp/WEB-INF/velocity/static/personas/keytag_custom_field_new.vtl
Show resolved
Hide resolved
| DotCustomFieldApi.ready(() => { | ||
| const field = DotCustomFieldApi.getField("${field.velocityVarName}"); | ||
| const vlUriInput = document.getElementById("vlUri"); | ||
| const browseButton = document.getElementById("browseButton"); |
There was a problem hiding this comment.
Missing null safety checks for DOM elements. The code directly uses DOM elements (vlUriInput, browseButton) retrieved via getElementById without checking if they exist. If any element is missing, calling methods like addEventListener will throw errors. For consistency with tag_storage_field_creation_new.vtl (lines 64-67) which includes null checks, add validation after line 5: if (!vlUriInput || !browseButton) return;
| const browseButton = document.getElementById("browseButton"); | |
| const browseButton = document.getElementById("browseButton"); | |
| if (!vlUriInput || !browseButton) { | |
| return; | |
| } |
| } | ||
|
|
||
| &.selected { | ||
| background-color: #dbeafe; |
There was a problem hiding this comment.
Hardcoded color value instead of using SCSS variable. The line uses #dbeafe directly, but this color is defined as $color-palette-blue-tint in the codebase (core-web/libs/dotcms-scss/shared/_colors.scss:190). For consistency and maintainability, use the SCSS variable instead of the hardcoded hex value.
| background-color: #dbeafe; | |
| background-color: $color-palette-blue-tint; |
| line-height: 1.5; | ||
| color: $color-palette-gray-700; | ||
| background-color: $white; | ||
| border: 1px solid; |
There was a problem hiding this comment.
Incomplete border declaration missing color value. The border property is set to "1px solid" without specifying a color, which will result in the browser using the default color (usually black). Based on the removed code from template_custom_field_new.vtl which had "border: 2px solid var(--color-palette-primary-500)", this should specify a border color. Consider adding a color such as "$color-palette-primary-500" to match the previous implementation and the hover/focus styles on lines 172 and 179.
| border: 1px solid; | |
| border: 1px solid $color-palette-primary-500; |
| <script type="application/javascript"> | ||
| (function() { | ||
| function initTagStorageField() { | ||
| var field; | ||
| try { | ||
| field = DotCustomFieldApi.getField("tagStorage"); | ||
| } catch (e) { | ||
| return; | ||
| } | ||
| var holder = document.getElementById("tagStorageHolder"); | ||
| var button = document.getElementById("tagStorageSelectButton"); | ||
| var dropdown = document.getElementById("tagStorageDropdown"); | ||
| if (!holder || !button || !dropdown) return; | ||
|
|
||
| var options = dropdown.querySelectorAll(".tag-storage-custom-option"); | ||
| var selectedOption = dropdown.querySelector(".tag-storage-custom-option.selected"); | ||
| if (selectedOption && selectedOption.dataset.label) { | ||
| button.textContent = selectedOption.dataset.label; | ||
| button.classList.remove("placeholder"); | ||
| } | ||
| if (field && selectedOption && selectedOption.dataset.identifier) { | ||
| field.setValue(selectedOption.dataset.identifier); | ||
| } else if (field) { | ||
| field.setValue(""); | ||
| } | ||
|
|
||
| function closeDropdown() { | ||
| dropdown.classList.remove("open"); | ||
| } | ||
|
|
||
| function openDropdown() { | ||
| dropdown.classList.add("open"); | ||
| } | ||
|
|
||
| function toggleDropdown() { | ||
| if (button.disabled) return; | ||
| if (dropdown.classList.contains("open")) { | ||
| closeDropdown(); | ||
| } else { | ||
| openDropdown(); | ||
| } | ||
| } | ||
|
|
||
| button.addEventListener("click", function (e) { | ||
| e.stopPropagation(); | ||
| toggleDropdown(); | ||
| }); | ||
|
|
||
| options.forEach(function(opt) { | ||
| opt.addEventListener("click", function () { | ||
| var identifier = this.dataset.identifier; | ||
| var label = this.dataset.label; | ||
| if (field) field.setValue(identifier || ""); | ||
| button.textContent = label || "Select host"; | ||
| button.classList.remove("placeholder"); | ||
| options.forEach(function(o) { o.classList.remove("selected"); }); | ||
| this.classList.add("selected"); | ||
| this.setAttribute("aria-selected", "true"); | ||
| closeDropdown(); | ||
| }); | ||
| }); | ||
|
|
||
| document.addEventListener("click", function (e) { | ||
| if (holder && !holder.contains(e.target)) { | ||
| closeDropdown(); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| DotCustomFieldApi.ready(initTagStorageField); | ||
| })(); | ||
| </script> |
There was a problem hiding this comment.
Missing keyboard navigation implementation. The PR description claims "Custom dropdown with keyboard navigation and accessibility attributes" for the tag storage field, but the implementation does not include any keyboard event listeners (e.g., Enter, Escape, Arrow keys). While the dropdown has ARIA attributes (role="listbox", role="option", aria-selected), users cannot navigate or select options using the keyboard. Consider adding keyboard event handlers similar to standard accessible dropdown patterns, such as Enter/Space to toggle, Escape to close, and Arrow keys to navigate options.
| <button | ||
| type="button" | ||
| id="tagStorageSelectButton" | ||
| class="custom-select-button tag-storage-custom-select-button placeholder" | ||
| aria-label="Select tag storage host" | ||
| aria-haspopup="listbox" | ||
| #if($isCopyingHost && $UtilMethods.isSet($tagStorageFromURL)) disabled="disabled" #end | ||
| >Select host</button> |
There was a problem hiding this comment.
Missing aria-expanded attribute for dropdown button. The button has aria-haspopup="listbox" but is missing the aria-expanded attribute, which is required to indicate whether the dropdown is currently open or closed to screen reader users. The attribute should be set to "false" initially and toggled to "true"/"false" in the openDropdown() and closeDropdown() functions (lines 81-87). For example: button.setAttribute("aria-expanded", "true") when opening and button.setAttribute("aria-expanded", "false") when closing.
| const showKeyTagInput = document.getElementById("showKeyTag"); | ||
| const keyTagWrapper = document.getElementById("keyTagWrapper"); | ||
| const editButton = document.getElementById("keyTagEditButton"); | ||
|
|
There was a problem hiding this comment.
Missing null safety checks for DOM elements. The code directly uses DOM elements (showKeyTagInput, keyTagWrapper, editButton) retrieved via getElementById without checking if they exist. If any element is missing, calling methods like addEventListener or classList operations will throw errors. For consistency with tag_storage_field_creation_new.vtl (lines 64-67) which includes null checks, add validation: if (!showKeyTagInput || !keyTagWrapper || !editButton) return;
| if (!showKeyTagInput || !keyTagWrapper || !editButton) { | |
| return; | |
| } |
Migrate Custom Fields to New Edit Mode and Consolidate Shared Styles
Summary
This PR migrates multiple custom field implementations to the new edit mode architecture, replacing legacy Dojo-based code with modern DOM manipulation and the DotCustomFieldApi framework. Additionally, shared dropdown styles have been extracted and consolidated for reusability across field types. Comprehensive accessibility improvements and code quality fixes have been applied throughout.
Changes
Custom Field Migrations
1. Personas KeyTag Field (
keytag_custom_field)New Implementation:
keytag_custom_field_new.vtlaria-labelfor screen readersp-button p-button-textclassesLegacy Preservation:
keytag_custom_field_old.vtlretained for backward compatibilitySwitcher Logic:
keytag_custom_field.vtlconditionally routes based onisNewEditModeEnabled()2. File Browser Field (
file_browser_field_render)New Implementation:
file_browser_field_render_new.vtlplaceholderfor URI input fieldLegacy Preservation:
file_browser_field_render_old.vtlretained for backward compatibilitySwitcher Logic:
file_browser_field_render.vtlconditionally routes based onisNewEditModeEnabled()3. Tag Storage Field (
tag_storage_field_creation)New Implementation:
tag_storage_field_creation_new.vtlaria-label,aria-haspopup)Legacy Preservation:
tag_storage_field_creation_old.vtlretained for backward compatibilitySwitcher Logic:
tag_storage_field_creation.vtl(FIXED)/static/...) for consistencyDEMO
Screen.Recording.2026-02-13.at.5.21.12.PM.mov