Skip to main content

dot-ui-vtl-migration

Migrates VTL (Velocity Template Language) custom field templates from the legacy DotCMS Dojo/Dijit API to the modern DotCustomFieldApi. Use this skill whenever a user asks to migrate, update, or convert a VTL file, custom field template, or dotCMS field that uses any of: DotCustomFieldApi.get(), DotCustomFieldApi.set(), DotCustomFieldApi.onChangeField(), dojo.ready(), dojo.byId(), dijit.byId(), dijit.form.*, dojoType attributes, or any Dojo/Dijit pattern. Also trigger when the user pastes a VTL snippet and asks "what needs to change" or "can you update this". If in doubt, use this skill.

الانتقال إلى التثبيت

معلومات المصدر

المستودع
dotCMS/core
آخر نشاط في المصدر
١٨ سبتمبر ٢٠٢٦ في ٢٠:١٣
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٩٧٠
التفرعات
٤٨٦

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

مستكشف الملفات
2 ملفات

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
dot-ui-vtl-migration
owner
@dotcms/falcon
status
active
description
Migrates VTL (Velocity Template Language) custom field templates from the legacy DotCMS Dojo/Dijit API to the modern DotCustomFieldApi. Use this skill whenever a user asks to migrate, update, or convert a VTL file, custom field template, or dotCMS field that uses any of: DotCustomFieldApi.get(), DotCustomFieldApi.set(), DotCustomFieldApi.onChangeField(), dojo.ready(), dojo.byId(), dijit.byId(), dijit.form.*, dojoType attributes, or any Dojo/Dijit pattern. Also trigger when the user pastes a VTL snippet and asks "what needs to change" or "can you update this". If in doubt, use this skill.
# VTL Migration: Legacy API → DotCustomFieldApi You are migrating DotCMS VTL custom field templates from Dojo/Dijit-era APIs to the modern `DotCustomFieldApi`. The goal is **identical functionality with modern, clean code** and **semantic styling with DaisyUI**. For the full migration rules, all code examples, the DaisyUI styling section, and the step-by-step checklist, read `references/migration-guide.md`. ## The Core API Swap (Quick Reference) | Old (deprecated) | New | |---|---| | `DotCustomFieldApi.get('id')` | `DotCustomFieldApi.getField('id').getValue()` | | `DotCustomFieldApi.set('id', val)` | `DotCustomFieldApi.getField('id').setValue(val)` | | `DotCustomFieldApi.onChangeField('id', cb)` | `DotCustomFieldApi.getField('id').onChange(cb)` | | Manual DOM show/hide of a field | `DotCustomFieldApi.getField('id').show()` / `.hide()` | | Manual DOM enable/disable of a field | `DotCustomFieldApi.getField('id').enable()` / `.disable()` | | Manual checks against dijit validation state | `DotCustomFieldApi.getField('id').getValidationState()` | | No legacy equivalent | `DotCustomFieldApi.getField('id').onValidationChange(cb)` | | `dojo.ready(fn)` | `DotCustomFieldApi.ready(fn)` | | `dojo.byId('el')` | `document.getElementById('el')` | | `dijit.byId('id')` | `DotCustomFieldApi.getField('id')` | | `dijit.form.*` widgets | Native HTML + **DaisyUI** classes (`input`, `btn`, `select`, etc.) | | `dojoType="..."` attribute | Remove; use semantic HTML + DaisyUI components | | `class="dijit*"` classes | Remove; use **DaisyUI** component classes instead | | Inline styles / ad-hoc CSS | **DaisyUI** component classes + Tailwind utilities (see guide) | | `onclick="fn()"` inline handlers | `addEventListener('click', fn)` | ## Process 1. **Read the entire file** to understand all functionality before touching anything. 2. **Identify deprecated patterns** — scan for the patterns in the table above. 3. **Wrap everything in `DotCustomFieldApi.ready()`** — all field access must live inside this callback. 4. **Store field references once** — call `getField()` once per field at the top of `ready()`, then reuse the reference. 5. **Migrate each pattern** — follow the migration rules in `references/migration-guide.md`. 6. **Apply DaisyUI for styling** — use DaisyUI component classes (`btn`, `input`, `select`, `modal`, `link`, etc.) and Tailwind utilities instead of inline styles or ad-hoc CSS; see “Styling with DaisyUI” in the guide. 7. **Preserve VTL variables** — `${fieldId}`, `$maxChar`, `$variableName`, and server-side context variables (`$inode`, `$identifier`, `$lang`, `$contentlet`, `$structure`, `$field`) are server-side; never change them. 8. **Output three files** — see the File Output Pattern below. ## File Output Pattern Every migration produces **three files**. You must write all three — not just the migrated version. Given an original file at `/static/personas/keytag_custom_field.vtl`, the three outputs are: --- ### File 1 — `keytag_custom_field_old.vtl` The **original file content, completely unchanged**. Copy it verbatim — every deprecated API call, every dijit class, every dojo.ready. This is the fallback for the legacy editor. --- ### File 2 — `keytag_custom_field_new.vtl` The **fully migrated file** with all deprecated patterns replaced per the migration rules. --- ### File 3 — `keytag_custom_field.vtl` (replaces the original) The **conditional router** — this file takes the name of the original and delegates to `_new` or `_old` based on which edit mode is active: ```vtl #if( $structures.isNewEditModeEnabled() ) #parse('/static/personas/keytag_custom_field_new.vtl') #else #parse('/static/personas/keytag_custom_field_old.vtl') #end ``` The `#parse` paths must use the **full server path** of the file, not just the filename. Use the same directory as the original file. --- This pattern lets both legacy and new edit modes coexist safely — old editor users continue using the deprecated code, new editor users get the modernized version. **When the user gives you a file path**, derive all three filenames automatically. If you only receive file contents without a path, ask for the filename and its server path before outputting. ## Non-Negotiables - All `getField()` calls must be inside `DotCustomFieldApi.ready()` - Never use `DotCustomFieldApi.get()` or `DotCustomFieldApi.set()` (the old short forms) - VTL variables stay exactly as-is - Business logic stays exactly as-is — only the API calls and styling approach change - Dijit CSS classes (any `class="dijit*"`) must be removed - **Styling:** Prefer DaisyUI component classes + Tailwind utilities; keep custom CSS only when the guide says so - Translate non-English comments to English - Server-side VTL variables (`$inode`, `$identifier`, `$lang`, `$contentlet`, `$structure`, `$field`) are resolved at render time — do not confuse them with `DotCustomFieldApi` JavaScript APIs ## Key Patterns to Know **Field reference lifecycle:** ```js DotCustomFieldApi.ready(() => { // Get once, reuse everywhere const titleField = DotCustomFieldApi.getField('title'); const urlField = DotCustomFieldApi.getField('url'); // Read const current = titleField.getValue() || ''; // Write urlField.setValue(slugify(current)); // Watch titleField.onChange((value) => { urlField.setValue(slugify(value)); }); }); ``` **Field visibility and state control:** ```js DotCustomFieldApi.ready(() => { const mediaField = DotCustomFieldApi.getField('media'); const mediaFileField = DotCustomFieldApi.getField('mediafile'); // Show/hide based on current value if (mediaField.getValue() === 'upload') { mediaFileField.show(); } else { mediaFileField.hide(); } // React to changes mediaField.onChange((value) => { if (value === 'upload') { mediaFileField.show(); } else { mediaFileField.hide(); } }); // Enable/disable a field mediaFileField.disable(); // blocks editing, applies disabled styling mediaFileField.enable(); // restores interactivity }); ``` **Reacting to validation state (required, errors, touched):** ```html <style> /* Self-contained: legacy iframe pages do NOT load DaisyUI, so we ship the rule with the template. */ #slugInput.is-invalid { border-color: #ef4444; outline-color: #ef4444; } </style> <script type="module"> DotCustomFieldApi.ready(() => { const field = DotCustomFieldApi.getField('urlTitle'); const input = document.getElementById('slugInput'); const applyValidation = (state) => { // Only show the error after the user (or Save) has marked the control as touched — // mirrors how Angular's built-in fields paint the red border. const showError = state.invalid && state.touched; input.classList.toggle('is-invalid', showError); }; // onValidationChange emits the initial state synchronously, so a separate // getValidationState() call up front is redundant. The bridge auto-cleans // on form destroy, so the unsubscribe return value can be ignored here. field.onValidationChange(applyValidation); }); </script> ``` > Use a self-contained `is-invalid` class with inline `<style>` instead of DaisyUI's `input-error`. The legacy iframe page (`legacy-custom-field.jsp`) does NOT load DaisyUI or Tailwind, so an `input-error` toggle would silently produce no visual feedback there. In iframe mode the callback also never fires (the Dojo bridge's `onValidationChange` is a no-op) — the legacy editor has its own validation surface. See Rule 13 in `references/migration-guide.md` for the full gotchas list. `state` shape: `{ valid, invalid, touched, dirty, errors }` (mirrors Angular's `AbstractControl`). `errors` is `null` when valid, otherwise a record like `{ required: true }`. **Multiple onChange for the same field** → combine into one handler: ```js // Old: two separate onChangeField calls for 'title' // New: one onChange that does both titleField.onChange((value) => { updateURL(value); updateFriendlyName(value); }); ``` **Native dialog with DaisyUI modal** (replaces `dojoType="dijit.Dialog"`): ```html <button type="button" id="openModalButton" class="btn btn-primary">Open modal</button> <dialog id="myDialog" class="modal"> <div class="modal-box"> <h3 class="font-bold text-lg">Hello!</h3> <p class="py-4">Press ESC key or click the button below to close</p> <div class="modal-action"> <form method="dialog"> <button type="submit" class="btn">Close</button> </form> </div> </div> </dialog> <script> const myDialog = document.getElementById('myDialog'); const openModalButton = document.getElementById('openModalButton'); openModalButton?.addEventListener('click', () => { myDialog?.showModal(); }); </script> ``` **Styling (DaisyUI):** Buttons → `btn`, `btn-primary`, `btn-ghost`, `btn-sm`. Inputs → `input input-bordered`. Selects → `select select-bordered`. Links → `link link-primary`. Use Tailwind for layout (`flex`, `gap`, `w-full`). Full reference in `references/migration-guide.md` → “Styling with DaisyUI”. ## Available Velocity Context Variables Custom field templates can use **server-side VTL variables** injected by dotCMS when the field is rendered. These are resolved on the server before HTML reaches the browser — they are **not** available in JavaScript and must not be confused with `DotCustomFieldApi`. | Variable | Type | Description | |---|---|---| | `$inode` | `String` | The contentlet's inode (version ID) | | `$identifier` | `String` | The contentlet's persistent identifier | | `$lang` | `long` | The contentlet's language ID | | `$contentlet` | `Contentlet` | The full Contentlet object | | `$structure` | `ContentType` | The content type (structure) | | `$field` | `Field` | The current field being rendered | **Availability:** - `$structure` and `$field` are always available when the custom field is rendered. - `$inode`, `$identifier`, `$lang`, and `$contentlet` are populated only when **editing an existing contentlet** (when an inode is known). On new content, those four variables are empty/unset. - Both the new editor (REST API component mode and iframe mode) and the legacy editor expose the same variables. **Example — display context variables in the template:** ```html <p> <strong>inode:</strong> $inode </p> <p> <strong>identifier:</strong> $identifier </p> <p> <strong>lang:</strong> $lang </p> <p> <strong>contentlet:</strong> $contentlet </p> <p> <strong>structure:</strong> $structure </p> <p> <strong>field:</strong> $field </p> ``` **Example — guard for new vs existing content:** ```html #if($utilMethods.isSet($inode)) <input type="hidden" id="contentInode" value="$inode" /> #else <p class="text-sm text-base-content/70">Save the content first to access inode-specific features.</p> #end ``` For full details, availability rules, and practical examples → read `references/migration-guide.md` → “Server-Side Velocity Context Variables”. ## Before Outputting Verify the migration passes this checklist (details in `references/migration-guide.md`): **Three-file output:** - [ ] `_old.vtl` — original file content, completely unchanged (deprecated code preserved intentionally) - [ ] `_new.vtl` — fully migrated content - [ ] Router file (original filename) — contains only the `#if( $structures.isNewEditModeEnabled() )` block with correct full-path `#parse` directives pointing to `_new` and `_old` **Migrated file (`_new.vtl`):** - [ ] No `DotCustomFieldApi.get()` or `.set()` or `.onChangeField()` remaining - [ ] No `dojo.*` or `dijit.*` references remaining - [ ] No `dojoType` attributes remaining - [ ] No `dijit*` CSS classes remaining - [ ] Styling uses DaisyUI components where applicable (buttons, inputs, selects, modals, links) and Tailwind for layout; no inline styles unless necessary - [ ] All field access inside `DotCustomFieldApi.ready()` - [ ] All `getField()` calls stored in variables and reused - [ ] Field visibility uses `field.show()` / `field.hide()` instead of manual DOM manipulation - [ ] Field state uses `field.enable()` / `field.disable()` instead of manual DOM attribute changes - [ ] VTL variables unchanged - [ ] Business logic unchanged For complete rules, **DaisyUI styling section**, all migration examples (character counter, title field, slug generator, dialogs, file browser), and edge cases → read `references/migration-guide.md`.
عرض على GitHub