From 2bbab66a1bb03f08db0d831ca486a4a4b59d068a Mon Sep 17 00:00:00 2001 From: Nicolas Molina Monroy Date: Wed, 19 Nov 2025 16:04:52 -0400 Subject: [PATCH 01/11] Refactor URL slug generation to use updated DotCustomFieldApi methods - Replaced deprecated DotCustomFieldApi.set() calls with field.setValue() for setting the URL slug. - Updated the retrieval of the saved value to use field.getValue() for consistency. - Cleaned up the code by removing unnecessary comments and ensuring proper formatting. --- vtls/vanillla/url-title.vtl | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/vtls/vanillla/url-title.vtl b/vtls/vanillla/url-title.vtl index 0d6ded7..d174f1b 100644 --- a/vtls/vanillla/url-title.vtl +++ b/vtls/vanillla/url-title.vtl @@ -1,4 +1,3 @@ -#** Slug Generator Custom Field V2 *# @@ -73,10 +75,5 @@ type="text" id="slugInput" onkeyup="handleInput()" - class="dijitTextBox" - style="background:#FAFAFA" /> - \ No newline at end of file +
\ No newline at end of file From 20f0caa73fef50773d100a21fb131c0e24c3cd1b Mon Sep 17 00:00:00 2001 From: Nicolas Molina Monroy Date: Wed, 19 Nov 2025 16:14:54 -0400 Subject: [PATCH 02/11] Refactor title custom field to use DotCustomFieldApi for improved functionality - Updated the title custom field script to utilize DotCustomFieldApi for retrieving and setting values. - Replaced dojo-based event handling with native JavaScript event listeners for better performance and readability. - Enhanced URL slug generation logic to ensure it only triggers when the URL field is empty. - Cleaned up the code structure for improved maintainability. --- vtls/vanillla/title_custom_field.vtl | 39 ++++++++++++---------------- vtls/vanillla/url-title.vtl | 1 + 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/vtls/vanillla/title_custom_field.vtl b/vtls/vanillla/title_custom_field.vtl index bb0a6dc..4e7a5c6 100644 --- a/vtls/vanillla/title_custom_field.vtl +++ b/vtls/vanillla/title_custom_field.vtl @@ -1,30 +1,25 @@ diff --git a/vtls/vanillla/url-title.vtl b/vtls/vanillla/url-title.vtl index d174f1b..b82e6f0 100644 --- a/vtls/vanillla/url-title.vtl +++ b/vtls/vanillla/url-title.vtl @@ -1,3 +1,4 @@ +#** Slug Generator Custom Field V2 *# \ No newline at end of file diff --git a/vtls/vanillla/title_custom_field.vtl b/vtls/vanillla/title_custom_field.vtl index 4e7a5c6..db10bca 100644 --- a/vtls/vanillla/title_custom_field.vtl +++ b/vtls/vanillla/title_custom_field.vtl @@ -5,7 +5,7 @@ DotCustomFieldApi.ready(() =>{ const urlField = DotCustomFieldApi.getField('url'); const friendlyNameField = DotCustomFieldApi.getField('friendlyName'); - const titleBox = document.getElementById('titleBox'); + const titleBox = document.getElementById('${fieldId}-titleBox'); titleBox.value = titleField.getValue() || ''; titleBox.addEventListener('change', () =>{ @@ -23,4 +23,4 @@ DotCustomFieldApi.ready(() =>{ }); - \ No newline at end of file + \ No newline at end of file diff --git a/vtls/vanillla/url-title.vtl b/vtls/vanillla/url-title.vtl index b82e6f0..56cb8a6 100644 --- a/vtls/vanillla/url-title.vtl +++ b/vtls/vanillla/url-title.vtl @@ -1,9 +1,8 @@ #** Slug Generator Custom Field V2 *# \ No newline at end of file diff --git a/vtls/vanillla/text-count.vtl b/vtls/vanillla/text-count.vtl index b9b5c41..2bf9543 100644 --- a/vtls/vanillla/text-count.vtl +++ b/vtls/vanillla/text-count.vtl @@ -5,10 +5,8 @@
Recommended Max $maxChar characters
- \ No newline at end of file From 2dd08c945e47a190cf9bbe74b03ba407c1592d83 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Monroy Date: Fri, 28 Nov 2025 13:54:13 -0400 Subject: [PATCH 05/11] Enhance URL slug suggestion functionality in url-title.vtl - Introduced a new applySuggestion function to streamline the process of applying suggested slugs. - Refactored showSuggestion to dynamically create and append suggestion links instead of using inline onclick attributes. - Improved event handling by adding event listeners for better performance and maintainability. - Updated field retrieval to use more descriptive variable names for clarity. --- vtls/vanillla/url-title.vtl | 50 ++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/vtls/vanillla/url-title.vtl b/vtls/vanillla/url-title.vtl index 56cb8a6..2552cd6 100644 --- a/vtls/vanillla/url-title.vtl +++ b/vtls/vanillla/url-title.vtl @@ -18,6 +18,16 @@ .replace(/[^a-z0-9]+/g, '-') .replace(/^-+|-+$/g, ''); + const applySuggestion = (slug) => { + const input = document.getElementById(SLUG_INPUT); + input.value = slug; + currentValue = slug; + isLocked = true; + const field = DotCustomFieldApi.getField(TARGET_FIELD); + field.setValue(slug); + document.getElementById(SUGGESTION_DIV).style.display = 'none'; + }; + const showSuggestion = newSlug => { const suggestion = document.getElementById(SUGGESTION_DIV); @@ -26,22 +36,22 @@ return; } - suggestion.innerHTML = ` - - Use: ${newSlug} - - `; + // Limpiar contenido previo + suggestion.innerHTML = ''; + + // Crear el enlace programáticamente + const link = document.createElement('a'); + link.textContent = `Use: ${newSlug}`; + + // Agregar event listener en lugar de onclick + link.addEventListener('click', (e) => { + e.preventDefault(); + applySuggestion(newSlug); + }); + + suggestion.appendChild(link); suggestion.style.display = 'block'; - }; - - const applySuggestion = slug => { - const input = document.getElementById(SLUG_INPUT); - input.value = slug; - currentValue = slug; - isLocked = true; - const field = DotCustomFieldApi.getField(TARGET_FIELD); - field.setValue(slug); - document.getElementById(SUGGESTION_DIV).style.display = 'none'; + suggestion.style.cursor = 'pointer'; }; const handleInput = () => { @@ -56,25 +66,25 @@ DotCustomFieldApi.ready(() => { const input = document.getElementById(SLUG_INPUT); - const field = DotCustomFieldApi.getField('urlTitle'); - const savedValue = field.getValue(); + const urlTitleField = DotCustomFieldApi.getField('urlTitle'); + const savedValue = urlTitleField.getValue(); if (savedValue) { input.value = savedValue; currentValue = savedValue; } - const sourceField = DotCustomFieldApi.getField('title'); - sourceField.onChange(value => { + const titleField = DotCustomFieldApi.getField('title'); + titleField.onChange(value => { const newSlug = slugifyText(value); showSuggestion(newSlug); }); + input.addEventListener('keyup', handleInput); });
\ No newline at end of file From 7e7081b93e1df02ed54f2551da46209d04246343 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Monroy Date: Fri, 28 Nov 2025 14:07:33 -0400 Subject: [PATCH 06/11] Remove text-count.js.vtl file and update title_custom_field.vtl script tag for consistency --- vtls/vanillla/text-count.js.vtl | 27 --------------------------- vtls/vanillla/title_custom_field.vtl | 2 +- 2 files changed, 1 insertion(+), 28 deletions(-) delete mode 100644 vtls/vanillla/text-count.js.vtl diff --git a/vtls/vanillla/text-count.js.vtl b/vtls/vanillla/text-count.js.vtl deleted file mode 100644 index 2b85407..0000000 --- a/vtls/vanillla/text-count.js.vtl +++ /dev/null @@ -1,27 +0,0 @@ -
-
- 50 characters -
-
Recommended Max 50 characters
-
- - - \ No newline at end of file diff --git a/vtls/vanillla/title_custom_field.vtl b/vtls/vanillla/title_custom_field.vtl index db10bca..edf3aeb 100644 --- a/vtls/vanillla/title_custom_field.vtl +++ b/vtls/vanillla/title_custom_field.vtl @@ -1,4 +1,4 @@ - - \ No newline at end of file + \ No newline at end of file From d9e2ffa44c168471d71ce4fb6918e9076b3517cd Mon Sep 17 00:00:00 2001 From: Nicolas Molina Monroy Date: Tue, 2 Dec 2025 16:53:10 -0400 Subject: [PATCH 08/11] Add migration documentation and refactor YouTube search functionality - Introduced migration.md to provide guidelines for transitioning to the new DotCustomFieldApi. - Refactored youtube-search.vtl to utilize DotCustomFieldApi for field management, enhancing maintainability and readability. - Updated event handling to use native JavaScript methods and improved variable naming for clarity. - Enhanced video preview functionality by dynamically creating and appending elements instead of using inline attributes. --- migration.md | 502 +++++++++++++++++++++++++++++++ vtls/vanillla/youtube-search.vtl | 301 +++++++++++++----- 2 files changed, 721 insertions(+), 82 deletions(-) create mode 100644 migration.md diff --git a/migration.md b/migration.md new file mode 100644 index 0000000..85661aa --- /dev/null +++ b/migration.md @@ -0,0 +1,502 @@ +--- +name: vtl-migration +--- + +You are an expert Senior Frontend Developer with deep knowledge in JavaScript, HTML, CSS, and VTL (Velocity Template Language). Your role is to provide comprehensive, actionable VTL migrations that use the new DotCustomFieldApi and refactor legacy code to use the modern API. + +The new DotCustomFieldApi provides a cleaner, more maintainable approach to working with custom fields. Here is a basic example: + +```js +const titleField = DotCustomFieldApi.getField('variableName'); + +titleField.getValue(); +titleField.setValue('new value'); +titleField.onChange(value => { + console.log(value); +}); +``` + +## Quick Reference + +| Action | Old API (Deprecated) | New API | +|--------|---------------------|---------| +| Get field value | `DotCustomFieldApi.get('fieldId')` | `DotCustomFieldApi.getField('fieldId').getValue()` | +| Set field value | `DotCustomFieldApi.set('fieldId', value)` | `DotCustomFieldApi.getField('fieldId').setValue(value)` | +| Watch changes | `DotCustomFieldApi.onChangeField('fieldId', callback)` | `DotCustomFieldApi.getField('fieldId').onChange(callback)` | +| Form widgets | `dijit.form.*` | **Remove entirely** - use native HTML elements with DotCustomFieldApi | + +## Migration Rules + +Follow these rules when migrating VTL custom fields to use the new DotCustomFieldApi: + +### 1. Use `DotCustomFieldApi.getField('variableName')` to get the field. + +**Always use** `DotCustomFieldApi.getField('variableName')` to get a field reference. This returns a field object that provides methods like `getValue()`, `setValue()`, and `onChange()`. + +**Avoid:** `DotCustomFieldApi.get('variableName')` - this is the deprecated pattern. + +### 2. Use `field.getValue()` to get the value of the field. + +**Always use** `field.getValue()` method after getting a field reference. This method returns the current value of the field. + +**Old way (deprecated):** +```js +const textEntered = DotCustomFieldApi.get('${fieldId}'); +``` + +**New way:** +```js +const field = DotCustomFieldApi.getField('${fieldId}'); +const textEntered = field.getValue(); +``` + +### 3. Use `field.setValue('new value')` to set the value of the field. + +**Always use** `field.setValue('new value')` to update a field's value. This method automatically triggers change events and updates the UI. + +**Old way (deprecated):** +```js +DotCustomFieldApi.set('${fieldId}', 'new value'); +``` + +**New way:** +```js +const field = DotCustomFieldApi.getField('${fieldId}'); +field.setValue('new value'); +``` + +### 4. Use `field.onChange(value => { ... })` to watch for changes. + +**Always use** `field.onChange()` to subscribe to field value changes. This provides a cleaner API than the global change handler. + +**Old way (deprecated):** +```js +DotCustomFieldApi.onChangeField('${fieldId}', (value) => { + console.log(value); +}); +``` + +**New way:** +```js +const field = DotCustomFieldApi.getField('${fieldId}'); +field.onChange(value => { + console.log(value); +}); +``` + +### 5. Don't use dijit.form API + +**Never use dijit.form API.** Always use the new DotCustomFieldApi and refactor legacy dijit code to use the modern API. Remove all dependencies on Dojo/Dijit form widgets. + +### 6. Always wrap field access in `DotCustomFieldApi.ready(() => { ... })`. + +**Always wrap** your field access code inside `DotCustomFieldApi.ready()` to ensure the API is fully initialized before use. This prevents race conditions and ensures fields are available. + +**Required pattern:** +```js +DotCustomFieldApi.ready(() => { + // All field access code goes here + const field = DotCustomFieldApi.getField('${fieldId}'); + field.getValue(); +}); +``` + +### 6. Use semantic HTML elements without custom classes + +**Always use** semantic HTML elements without custom classes and inline styles. This provides a cleaner API and makes the code more maintainable. + +**Old way (deprecated):** +```html + + +``` + +**New way:** +```html + +
+``` + +## Best Practices + +### Field ID Naming +- Always prefix custom element IDs with `${fieldId}` to avoid conflicts (e.g., `${fieldId}-slugInput` instead of `slugInput`) + +### Event Handling +- Prefer `addEventListener()` over inline event handlers (e.g., avoid `onclick="..."` attributes) +- Use event delegation when appropriate + +### Code Organization +- Keep DOM manipulation separate from field API logic +- Initialize field references once inside `DotCustomFieldApi.ready()` +- Use meaningful variable names for field references + +### Error Handling +- Always check if field values exist before using them (use `|| ''` or `|| defaultValue`) +- Handle edge cases where fields might not be available + +## Complete Migration Examples + +Here are complete examples showing the migration from old patterns to the new DotCustomFieldApi: + +### Example 1: Character Counter Field + +#### Old way + +text-count.vtl +```html + + +
+
+ $maxChar characters +
+
Recommended Max $maxChar characters
+
+ + +``` + +### New way + +text-count.vtl +```html +
+
+ $maxChar characters +
+
Recommended Max $maxChar characters
+
+ + +``` + +### Example 2: Title Field with Auto-generated URL and Friendly Name + +#### Old way (using dijit.form API) + +title_custom_field.vtl +```html + + +``` + +### New way (using DotCustomFieldApi) + +title_custom_field.vtl +```html + + +``` + +### Example 3: Slug Generator with Suggestions + +#### Old way + +slug-generator.vtl +```html +#** Slug Generator Custom Field V2 *# + + + + +``` +### New way + +slug-generator.vtl +```html + +#** Slug Generator Custom Field V2 *# + + + +
+``` \ No newline at end of file diff --git a/vtls/vanillla/youtube-search.vtl b/vtls/vanillla/youtube-search.vtl index 0260f22..6afe4fc 100644 --- a/vtls/vanillla/youtube-search.vtl +++ b/vtls/vanillla/youtube-search.vtl @@ -1,30 +1,72 @@ + DotCustomFieldApi.ready(() => { + // Get field references + titleField = DotCustomFieldApi.getField('title'); + authorField = DotCustomFieldApi.getField('author'); + idField = DotCustomFieldApi.getField('id'); + lengthField = DotCustomFieldApi.getField('length'); + thumbnailSmallField = DotCustomFieldApi.getField('thumbnailSmall'); + thumbnailLargeField = DotCustomFieldApi.getField('thumbnailLarge'); + publishedField = DotCustomFieldApi.getField('published'); + urlField = DotCustomFieldApi.getField('url'); + // Initialize video preview if thumbnailLarge has a value + const thumbnailLargeValue = thumbnailLargeField.getValue(); + if(thumbnailLargeValue && thumbnailLargeValue !== ""){ + const urlValue = urlField.getValue(); + if(urlValue && urlValue !== "") { + showVideoPreview(urlValue); + } + } + + // Add event listener to search button + const searchButton = document.getElementById('${fieldId}-searchButton'); + if(searchButton) { + searchButton.addEventListener('click', searchClicked); + } + + // Add event listener to search input for Enter key + const searchInput = document.getElementById('${fieldId}-inputSearchStrId'); + if(searchInput) { + searchInput.addEventListener('keypress', function(e) { + if(e.key === 'Enter') { + searchClicked(); + } + }); + } + }); +
- - + +
-
+
- \ No newline at end of file From cd7195776b3f621acf80d0518955a44bff2b7b25 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Monroy Date: Wed, 3 Dec 2025 11:38:13 -0400 Subject: [PATCH 09/11] Refactor VTL files to remove Dojo dependencies and enhance functionality - Updated text-count.vtl to remove dijit classes and use native HTML elements, improving maintainability. - Refactored url-title.vtl to eliminate dojoType attributes and streamline variable naming for clarity. - Enhanced youtube-search.vtl by replacing deprecated patterns with the new DotCustomFieldApi, improving event handling and user experience. - Ensured all field accesses are wrapped in DotCustomFieldApi.ready() to prevent race conditions and maintain functionality. --- migration.md | 598 ++++++++++++++++++++++++++++--- vtls/vanillla/text-count.vtl | 23 +- vtls/vanillla/url-title.vtl | 9 +- vtls/vanillla/youtube-search.vtl | 335 ++++++++++++----- 4 files changed, 825 insertions(+), 140 deletions(-) diff --git a/migration.md b/migration.md index 85661aa..7632695 100644 --- a/migration.md +++ b/migration.md @@ -2,20 +2,40 @@ name: vtl-migration --- -You are an expert Senior Frontend Developer with deep knowledge in JavaScript, HTML, CSS, and VTL (Velocity Template Language). Your role is to provide comprehensive, actionable VTL migrations that use the new DotCustomFieldApi and refactor legacy code to use the modern API. +You are an expert Senior Frontend Developer with deep knowledge in JavaScript, HTML, CSS, and VTL (Velocity Template Language). Your role is to migrate VTL custom field templates from legacy APIs to the new DotCustomFieldApi, ensuring functionality is preserved while modernizing the codebase. + +## Your Mission + +When migrating VTL files: +1. **Preserve all existing functionality** - the migrated code must behave identically to the original +2. **Preserve all VTL variables** (e.g., `${fieldId}`, `$maxChar`) - these are server-side and should remain unchanged +3. **Preserve all business logic** - only update API calls, not the logic itself +4. **Maintain code structure** - keep functions, variable names, and organization when possible +5. **Improve code quality** - remove deprecated patterns while maintaining readability + +## The New DotCustomFieldApi The new DotCustomFieldApi provides a cleaner, more maintainable approach to working with custom fields. Here is a basic example: ```js -const titleField = DotCustomFieldApi.getField('variableName'); - -titleField.getValue(); -titleField.setValue('new value'); -titleField.onChange(value => { - console.log(value); +DotCustomFieldApi.ready(() => { + const field = DotCustomFieldApi.getField('variableName'); + + // Get value + const value = field.getValue(); + + // Set value + field.setValue('new value'); + + // Watch for changes + field.onChange(value => { + console.log(value); + }); }); ``` +**IMPORTANT:** Always wrap all field access code inside `DotCustomFieldApi.ready()` to ensure the API is initialized. + ## Quick Reference | Action | Old API (Deprecated) | New API | @@ -41,12 +61,12 @@ Follow these rules when migrating VTL custom fields to use the new DotCustomFiel **Old way (deprecated):** ```js -const textEntered = DotCustomFieldApi.get('${fieldId}'); +const textEntered = DotCustomFieldApi.get('variableName'); ``` **New way:** ```js -const field = DotCustomFieldApi.getField('${fieldId}'); +const field = DotCustomFieldApi.getField('variableName'); const textEntered = field.getValue(); ``` @@ -56,12 +76,12 @@ const textEntered = field.getValue(); **Old way (deprecated):** ```js -DotCustomFieldApi.set('${fieldId}', 'new value'); +DotCustomFieldApi.set('variableName', 'new value'); ``` **New way:** ```js -const field = DotCustomFieldApi.getField('${fieldId}'); +const field = DotCustomFieldApi.getField('variableName'); field.setValue('new value'); ``` @@ -71,24 +91,20 @@ field.setValue('new value'); **Old way (deprecated):** ```js -DotCustomFieldApi.onChangeField('${fieldId}', (value) => { +DotCustomFieldApi.onChangeField('variableName', (value) => { console.log(value); }); ``` **New way:** ```js -const field = DotCustomFieldApi.getField('${fieldId}'); +const field = DotCustomFieldApi.getField('variableName'); field.onChange(value => { console.log(value); }); ``` -### 5. Don't use dijit.form API - -**Never use dijit.form API.** Always use the new DotCustomFieldApi and refactor legacy dijit code to use the modern API. Remove all dependencies on Dojo/Dijit form widgets. - -### 6. Always wrap field access in `DotCustomFieldApi.ready(() => { ... })`. +### 5. Always wrap field access in `DotCustomFieldApi.ready(() => { ... })`. **Always wrap** your field access code inside `DotCustomFieldApi.ready()` to ensure the API is fully initialized before use. This prevents race conditions and ensures fields are available. @@ -96,56 +112,497 @@ field.onChange(value => { ```js DotCustomFieldApi.ready(() => { // All field access code goes here - const field = DotCustomFieldApi.getField('${fieldId}'); + const field = DotCustomFieldApi.getField('variableName'); field.getValue(); }); ``` -### 6. Use semantic HTML elements without custom classes +**Replace:** `dojo.ready()` → `DotCustomFieldApi.ready()` + +**Old way (deprecated):** +```js +dojo.ready(function() { + // code here +}); +``` + +**New way:** +```js +DotCustomFieldApi.ready(() => { + // code here +}); +``` + +### 6. Remove all Dojo/Dijit dependencies + +**Never use dijit.form API or any Dojo APIs.** Always use the new DotCustomFieldApi and native HTML/JavaScript. Remove all dependencies on Dojo/Dijit. + +**Remove these patterns:** +- `dojo.ready()` → Replace with `DotCustomFieldApi.ready()` +- `dojo.byId()` → Replace with `document.getElementById()` +- `dojo.require()` → Remove entirely +- `dijit.byId()` → Replace with field references from `DotCustomFieldApi.getField()` +- `dijit.form.*` → Replace with native HTML elements + +**Old way (deprecated):** +```js +dojo.ready(function() { + var url = dijit.byId('url'); + if(url && url.get('value').trim()==='') { + url.set('value', 'new-value'); + } +}); +``` + +**New way:** +```js +DotCustomFieldApi.ready(() => { + const urlField = DotCustomFieldApi.getField('url'); + const urlValue = urlField.getValue() || ''; + if(urlValue.trim() === '') { + urlField.setValue('new-value'); + } +}); +``` + +### 7. Remove Dijit CSS classes + +Remove all dijit CSS classes from HTML elements. They are not needed with native HTML elements. -**Always use** semantic HTML elements without custom classes and inline styles. This provides a cleaner API and makes the code more maintainable. +**Remove these classes:** +- `class="dijitTextBox"` +- `class="dijitPlaceHolder"` +- `class="dijitSelect"` +- `class="dijitButton"` +- `class="dijitDropDownButton"` +- `class="dijitDialog"` +- Any other class starting with `dijit` + +**Keep:** +- Custom CSS classes (non-dijit classes) +- Inline styles +- Style tags with custom CSS **Old way (deprecated):** ```html +``` + +**New way:** +```html + +``` + +**Preserve custom styles:** +```html + ``` +Remove dijit css classes from the code because they are not needed, here some classes as example: + +- class="dijitTextBox" +- class="dijitPlaceHolder" +- class="dijitSelect" +- class="dijitButton" +- dijitDropDownButton + +**Old way (deprecated):** +```html + +``` + **New way:** ```html -
``` -## Best Practices +Keep the inline styles and the classes without changes. + +**Old way** +```html + + +``` + +**New way: Keep the inline styles and the classes in a style tag.** +```html + + +``` + +### 8. Remove dojoType attributes and use semantic HTML + +Remove all `dojoType` attributes and use native HTML elements instead. This provides a cleaner API and makes the code more maintainable. + +**Common dojoType patterns to replace:** + +| Old (Deprecated) | New | +|-----------------|-----| +| `` | `` | +| `` | `` | +| `` or native HTML select | +| `
` | `` | +| `` | `` | +| `
` | `` | + +**Old way (deprecated):** +```html + + +``` + +**New way:** +```html + + +``` -### Field ID Naming -- Always prefix custom element IDs with `${fieldId}` to avoid conflicts (e.g., `${fieldId}-slugInput` instead of `slugInput`) +### 9. Native Dialog Implementation -### Event Handling -- Prefer `addEventListener()` over inline event handlers (e.g., avoid `onclick="..."` attributes) -- Use event delegation when appropriate +For elements with `dojoType="dijit.Dialog"`, use the native HTML `` element with CSS and JavaScript. + +**Complete dialog example:** + +```html + + + +
+

Dialog Title

+

Dialog content goes here

+ +
+
+ + + + +``` + +### 10. Event Handling + +**Always use `addEventListener()` instead of inline event handlers.** + +**Old way (deprecated):** +```html + + +``` + +**New way:** +```html + + + + +``` + +## Best Practices ### Code Organization - Keep DOM manipulation separate from field API logic -- Initialize field references once inside `DotCustomFieldApi.ready()` -- Use meaningful variable names for field references +- Initialize field references once inside `DotCustomFieldApi.ready()` and reuse them +- Use meaningful variable names for field references (e.g., `titleField`, `urlField`) +- Group related field references together +- Define helper functions outside of `DotCustomFieldApi.ready()` when they don't need immediate field access + +**Good pattern:** +```js +// Helper functions defined outside +function slugifyText(text) { + return text.toLowerCase().trim().replace(/[^a-z0-9]+/g, '-'); +} + +DotCustomFieldApi.ready(() => { + // Field references initialized once + const titleField = DotCustomFieldApi.getField('title'); + const urlField = DotCustomFieldApi.getField('url'); + + // Reuse field references + titleField.onChange(value => { + urlField.setValue(slugifyText(value)); + }); +}); +``` ### Error Handling - Always check if field values exist before using them (use `|| ''` or `|| defaultValue`) - Handle edge cases where fields might not be available +- Check for null/undefined values before calling methods +- Use optional chaining or null checks when accessing DOM elements + +**Safe pattern:** +```js +DotCustomFieldApi.ready(() => { + const field = DotCustomFieldApi.getField('fieldName'); + const value = field.getValue() || ''; // Default to empty string + + const element = document.getElementById('myElement'); + if (element) { + element.textContent = value; + } +}); +``` + +### What to Preserve + +**DO NOT change:** +- VTL variables like `${fieldId}`, `$maxChar`, `$variableName` - these are server-side +- Business logic and algorithms - only update API calls +- CSS classes that are NOT dijit classes +- Inline styles +- HTML structure unless removing dojoType/dijit attributes +- Comments (translate to English if in another language) +- Function names and variable names (unless they reference deprecated APIs) + +**DO change:** +- API method calls (get/set/onChangeField → getField/getValue/setValue/onChange) +- dojo.ready → DotCustomFieldApi.ready +- dojo.byId → document.getElementById +- dijit.byId → DotCustomFieldApi.getField +- Remove dojoType attributes +- Remove dijit CSS classes +- Replace inline event handlers with addEventListener when possible + +## Step-by-Step Migration Checklist + +Follow this checklist for each VTL file you migrate: + +1. **Identify deprecated patterns** + - [ ] Search for `DotCustomFieldApi.get(` → Replace with `getField().getValue()` + - [ ] Search for `DotCustomFieldApi.set(` → Replace with `getField().setValue()` + - [ ] Search for `DotCustomFieldApi.onChangeField(` → Replace with `getField().onChange()` + - [ ] Search for `dojo.ready` → Replace with `DotCustomFieldApi.ready()` + - [ ] Search for `dojo.byId` → Replace with `document.getElementById()` + - [ ] Search for `dijit.byId` → Replace with `DotCustomFieldApi.getField()` + - [ ] Search for `dojoType=` → Remove attribute, update HTML element + - [ ] Search for `class="dijit` → Remove dijit classes + - [ ] Search for `onclick=`, `onkeyup=`, etc. → Replace with `addEventListener()` + +2. **Wrap field access** + - [ ] Ensure all `DotCustomFieldApi.getField()` calls are inside `DotCustomFieldApi.ready()` + - [ ] Initialize field references once and reuse them + +3. **Update API calls** + - [ ] Replace all `DotCustomFieldApi.get('fieldName')` with `getField('fieldName').getValue()` + - [ ] Replace all `DotCustomFieldApi.set('fieldName', value)` with `getField('fieldName').setValue(value)` + - [ ] Replace all `DotCustomFieldApi.onChangeField('fieldName', callback)` with `getField('fieldName').onChange(callback)` + +4. **Remove Dojo/Dijit** + - [ ] Remove all `dojo.require()` statements + - [ ] Replace `dojo.ready()` with `DotCustomFieldApi.ready()` + - [ ] Replace `dojo.byId()` with `document.getElementById()` + - [ ] Replace `dijit.byId()` with `DotCustomFieldApi.getField()` + +5. **Update HTML elements** + - [ ] Remove all `dojoType` attributes + - [ ] Update `
` to `` + - [ ] Remove all `class="dijit*"` classes + - [ ] Preserve custom CSS classes and inline styles + +6. **Update event handlers** + - [ ] Move inline event handlers to `addEventListener()` calls + - [ ] Ensure event listeners are attached inside `DotCustomFieldApi.ready()` + +7. **Verify preservation** + - [ ] All VTL variables (`${fieldId}`, `$variable`) remain unchanged + - [ ] Business logic is unchanged + - [ ] Functionality remains identical + - [ ] CSS styles (non-dijit) are preserved + +## Common Pitfalls + +### ❌ DON'T: Call getField() multiple times for the same field +```js +// BAD: Inefficient and error-prone +DotCustomFieldApi.ready(() => { + DotCustomFieldApi.getField('title').setValue('New Title'); + DotCustomFieldApi.getField('title').getValue(); // Called twice +}); +``` + +### ✅ DO: Store field reference and reuse it +```js +// GOOD: Efficient and clean +DotCustomFieldApi.ready(() => { + const titleField = DotCustomFieldApi.getField('title'); + titleField.setValue('New Title'); + const value = titleField.getValue(); +}); +``` + +### ❌ DON'T: Access fields outside DotCustomFieldApi.ready() +```js +// BAD: Race condition, may fail +const field = DotCustomFieldApi.getField('title'); +field.setValue('value'); +``` + +### ✅ DO: Always wrap in ready() +```js +// GOOD: Safe and reliable +DotCustomFieldApi.ready(() => { + const field = DotCustomFieldApi.getField('title'); + field.setValue('value'); +}); +``` + +### ❌ DON'T: Forget to handle null/undefined values +```js +// BAD: May cause errors +const value = field.getValue(); +const length = value.length; // Error if value is null/undefined +``` + +### ✅ DO: Provide defaults +```js +// GOOD: Safe handling +const value = field.getValue() || ''; +const length = value.length; +``` + +### ❌ DON'T: Mix old and new APIs +```js +// BAD: Inconsistent +DotCustomFieldApi.ready(() => { + const field = DotCustomFieldApi.getField('title'); + DotCustomFieldApi.set('url', 'value'); // Old API +}); +``` + +### ✅ DO: Use new API consistently +```js +// GOOD: Consistent +DotCustomFieldApi.ready(() => { + const titleField = DotCustomFieldApi.getField('title'); + const urlField = DotCustomFieldApi.getField('url'); + urlField.setValue('value'); // New API +}); +``` + +## Special Cases + +### Handling Multiple onChange Handlers + +If the original code has multiple `onChangeField` calls for the same field, combine them in a single `onChange` handler: + +**Old way:** +```js +DotCustomFieldApi.onChangeField('title', (value) => { + updateURL(value); +}); +DotCustomFieldApi.onChangeField('title', (value) => { + updateFriendlyName(value); +}); +``` + +**New way:** +```js +DotCustomFieldApi.ready(() => { + const titleField = DotCustomFieldApi.getField('title'); + titleField.onChange(value => { + updateURL(value); + updateFriendlyName(value); + }); +}); +``` + +### Preserving Initial Values + +When you need to preserve and check initial values: + +```js +DotCustomFieldApi.ready(() => { + const field = DotCustomFieldApi.getField('fieldName'); + const initialValue = field.getValue() || ''; + + // Store initial value for comparison + let previousValue = initialValue; + + field.onChange(value => { + if (value !== previousValue) { + // Value changed + previousValue = value; + } + }); +}); +``` ## Complete Migration Examples @@ -212,6 +669,27 @@ text-count.vtl text-count.vtl ```html + +
$maxChar characters @@ -219,7 +697,7 @@ text-count.vtl
Recommended Max $maxChar characters
- - + ``` ### Example 3: Slug Generator with Suggestions @@ -321,7 +799,6 @@ DotCustomFieldApi.ready(() => { slug-generator.vtl ```html -#** Slug Generator Custom Field V2 *# @@ -258,18 +411,22 @@
- +
-
+
-
-
-
+ +
+

Select a Video

+ +
+
+
\ No newline at end of file From ee1f0bd145c7a8337d2b155f9f94656603f8f1e7 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Monroy Date: Mon, 29 Dec 2025 09:08:30 -0500 Subject: [PATCH 10/11] Enhance VTL files by replacing Dojo with DotCustomFieldApi and improving dialog functionality - Updated migration.md to include new File and Page Browser Dialog examples using DotCustomFieldApi. - Refactored cachettl_custom_field.vtl to eliminate Dojo dependencies and utilize native JavaScript for field management. - Improved template_custom_field.vtl by adding a fetchTemplates function and enhancing template selection handling. - Replaced deprecated Dojo dialog implementations in og-preview.vtl with native HTML dialog elements and improved event handling. - Refactored redirect_custom_field.vtl to streamline page selection and update redirect URL handling using DotCustomFieldApi. --- migration.md | 58 ++++++ vtls/tools/cachettl_custom_field.vtl | 32 +-- vtls/tools/template_custom_field.vtl | 273 +++++++++++++++++--------- vtls/vanillla/example.vtl | 43 ++++ vtls/vanillla/og-preview.vtl | 133 +++++++++---- vtls/vanillla/text-count.vtl | 2 +- vtls/vanillla/title_custom_field.vtl | 4 +- vtls/widget/redirect.vtl | 75 +++++++ vtls/widget/redirect_custom_field.vtl | 72 ++++--- 9 files changed, 521 insertions(+), 171 deletions(-) create mode 100644 vtls/vanillla/example.vtl create mode 100644 vtls/widget/redirect.vtl diff --git a/migration.md b/migration.md index 7632695..404f0ad 100644 --- a/migration.md +++ b/migration.md @@ -373,6 +373,64 @@ For elements with `dojoType="dijit.Dialog"`, use the native HTML `` elem ``` +### 11. File and Page Browser Dialog + +```html + +``` + +**Old way (deprecated):** +```html + +
+``` + +```html + +``` + ## Best Practices ### Code Organization diff --git a/vtls/tools/cachettl_custom_field.vtl b/vtls/tools/cachettl_custom_field.vtl index 39549a6..c38240a 100644 --- a/vtls/tools/cachettl_custom_field.vtl +++ b/vtls/tools/cachettl_custom_field.vtl @@ -1,17 +1,19 @@ - - \ No newline at end of file + \ No newline at end of file diff --git a/vtls/tools/template_custom_field.vtl b/vtls/tools/template_custom_field.vtl index 477fd02..e06ee14 100644 --- a/vtls/tools/template_custom_field.vtl +++ b/vtls/tools/template_custom_field.vtl @@ -3,8 +3,6 @@ const hostId = "$request.getSession().getAttribute('CMS_SELECTED_HOST_ID')"; const defaultTemplateName = '$config.getStringProperty("DEFAULT_TEMPLATE_NAME", "System Template")'; // try to preload the default template. -dojo.require("dotcms.dojo.data.TemplateReadStore"); - // UTILS /** @@ -64,19 +62,57 @@ function fetchTemplateImage(templateId) { } }) .catch((error) => { - const imageEl = dojo.byId("templateThumbnailHolder"); - imageEl.src = "/html/images/shim.gif"; - imageEl.style.border = "0px"; + const imageEl = document.getElementById("templateThumbnailHolder"); + if (imageEl) { + imageEl.src = "/html/images/shim.gif"; + imageEl.style.border = "0px"; + } }); }; +/** +* Fetches templates from the API +* +*/ +function fetchTemplates(queryParams) { + const { hostId, filter = '', page = 1, perPage = 15, allSiteLabel = true } = queryParams; + + const urlParams = new URLSearchParams(); + urlParams.set('per_page', perPage); + urlParams.set('page', page); + urlParams.set('filter', filter); + + if (hostId) { + urlParams.set('host', hostId); + } + + const url = `/api/v1/templates/?${urlParams.toString()}`; + + return fetch(url) + .then(response => response.json()) + .then(data => { + // Check for first page and add the all site template, so it does not break the pagination + if (allSiteLabel && page === 1) { + const ALL_SITE_TEMPLATE = { + title: 'All Sites', + fullTitle: 'All Sites', + htmlTitle: '
-- All Sites ---
', + identifier: '0', + inode: '0' + }; + data.entity.unshift(ALL_SITE_TEMPLATE); + } + return data.entity; + }); +} + /** * Callback function to handle the template fetch * */ -const onTemplateFetchComplete = function(templates, currentRequest) { - const templateId = dojo.byId("template").value; - const isTemplateValid = templateId && templateId != "0"; +const onTemplateFetchComplete = function(templates, templateField) { + const templateId = templateField.getValue() || ''; + const isTemplateValid = templateId && templateId != "0"; if(!templates || templates.length === 0){ return; @@ -92,25 +128,46 @@ const onTemplateFetchComplete = function(templates, currentRequest) { const { identifier, fullTitle } = template; - // We set the values directly into the components because setting it directly into`templateSelect` fires another load operation. - dojo.byId("currentTemplateId").value = identifier; - dojo.byId("template").value = identifier; - dijit.byId('templateSel').set("displayedValue", fullTitle); + // We set the values directly into the components + const currentTemplateIdElement = document.getElementById("currentTemplateId"); + if (currentTemplateIdElement) { + currentTemplateIdElement.value = identifier; + } + templateField.setValue(identifier); + + const templateSelect = document.getElementById("templateSel"); + if (templateSelect) { + templateSelect.value = identifier; + // Update displayed value if there's a display element + const displayElement = document.getElementById("templateSelDisplay"); + if (displayElement) { + displayElement.textContent = fullTitle; + } + } + fetchTemplateImage(identifier); }; -function resetTemplateSelection() { - const templateSel = dijit.byId("templateSel"); - templateSel.set("value",""); - templateSel.filter(); - dojo.byId("template").value= ''; - dojo.byId("templateSel").value = ""; - dojo.byId("currentTemplateId").value = ""; - dojo.byId("templateThumbnailHolder").src = "/html/images/shim.gif"; - dojo.byId("templateThumbnailHolder").style.border = '0px'; +function resetTemplateSelection(templateField) { + const templateSelect = document.getElementById("templateSel"); + if (templateSelect) { + templateSelect.value = ""; + } + + templateField.setValue(''); + + const currentTemplateIdElement = document.getElementById("currentTemplateId"); + if (currentTemplateIdElement) { + currentTemplateIdElement.value = ""; + } + + const imageEl = document.getElementById("templateThumbnailHolder"); + if (imageEl) { + imageEl.src = "/html/images/shim.gif"; + imageEl.style.border = '0px'; + } } - /** * Get the template callback * @@ -118,7 +175,11 @@ function resetTemplateSelection() { function getTemplateCallBack(data) { const imageInode = data.identifier; const imageExtension = data.extension; - const imageEl=dojo.byId("templateThumbnailHolder"); + const imageEl = document.getElementById("templateThumbnailHolder"); + + if (!imageEl) { + return; + } if (isInodeSet(imageInode)) { imageEl.src = "/dA/" + imageInode + "/250w"; @@ -128,86 +189,116 @@ function getTemplateCallBack(data) { imageEl.src = "/html/images/shim.gif"; imageEl.style.border = '0px'; } - } -dojo.ready(function(){ - const templateId = dojo.byId("template").value; - const isTemplateValid = templateId && templateId != "0"; +/** +* Creates and populates the template select dropdown +* +*/ +function populateTemplateSelect(templates, selectedValue) { + const templateSelect = document.getElementById("templateSel"); + if (!templateSelect) { + return; + } - const currentTemplateIdElement = dojo.byId("currentTemplateId"); - currentTemplateIdElement.value = templateId; + // Clear existing options + templateSelect.innerHTML = ''; - const templateStore = new dotcms.dojo.data.TemplateReadStore({ - hostId: '', - templateSelected: templateId, - allSiteLabel: true, + templates.forEach(template => { + const option = document.createElement('option'); + option.value = template.identifier; + option.textContent = template.fullTitle; + option.setAttribute('data-html-title', template.htmlTitle || template.fullTitle); + if (template.identifier === selectedValue) { + option.selected = true; + } + templateSelect.appendChild(option); }); +} - const templateSelect = new dijit.form.FilteringSelect({ - id:"templateSel", - name:"templateSel", - style:"width:350px;", - onChange: templateChanged, - store: templateStore, - searchDelay: 300, - pageSize: 15, - autoComplete: false, - ignoreCase: true, - labelType:"html", - searchType:"html", - labelAttr: "htmlTitle", - searchAttr: "fullTitle", - value: templateId, - invalidMessage: '$text.get("Invalid-option-selected")', - },"templateHolder"); - - if (isTemplateValid){ - fetchTemplateImage(templateId); - - const templateSel = dijit.byId("templateSel"); - templateSel.set("value", templateId); - } - - const templateFetchParams = { - query: { - fullTitle: '*' - }, - queryOptions: {}, - start: 0, - count: 15, - onComplete: onTemplateFetchComplete - }; - - function handleAllSiteClick() { - templateStore.hostId = "*"; - templateStore.allSiteLabel=false; - resetTemplateSelection(); - } - - /** - * Handles the template change event - * - */ - function templateChanged() { - const templateSel = dijit.byId("templateSel"); - const value = templateSel?.get('value'); - - if(!value) { - resetTemplateSelection(); - return; +/** +* Handles the template change event +* +*/ +function handleTemplateChange(templateField, currentHostId, setAllSiteLabel) { + const templateSelect = document.getElementById("templateSel"); + if (!templateSelect) { + return; + } + + const value = templateSelect.value; + + if(!value) { + resetTemplateSelection(templateField); + return; + } + + if(value == "0") { + // Handle all sites selection + if (setAllSiteLabel) { + setAllSiteLabel(false); } + resetTemplateSelection(templateField); + return; + } - if(value == "0") { - handleAllSiteClick(); - return; + templateField.setValue(value); + fetchTemplateImage(value); +} + +DotCustomFieldApi.ready(() => { + const templateField = DotCustomFieldApi.getField('template'); + const templateId = templateField.getValue() || ''; + const isTemplateValid = templateId && templateId != "0"; + + const currentTemplateIdElement = document.getElementById("currentTemplateId"); + if (currentTemplateIdElement) { + currentTemplateIdElement.value = templateId; + } + + let currentHostId = hostId || ''; + let allSiteLabel = true; + + // Create template select element if it doesn't exist + let templateSelect = document.getElementById("templateSel"); + if (!templateSelect) { + const templateHolder = document.getElementById("templateHolder"); + if (templateHolder) { + templateSelect = document.createElement('select'); + templateSelect.id = "templateSel"; + templateSelect.name = "templateSel"; + templateSelect.style.width = "350px"; + templateHolder.appendChild(templateSelect); } + } + + if (templateSelect) { + // Add change event listener + templateSelect.addEventListener('change', () => { + handleTemplateChange(templateField, currentHostId, (value) => { + allSiteLabel = value; + }); + }); - dojo.byId("template").value=value; - fetchTemplateImage(value); + // Add input event for search/filtering (if we want to add search functionality) + // For now, we'll use a simple select dropdown } - templateStore.fetch(templateFetchParams); + // Fetch initial templates + fetchTemplates({ + hostId: currentHostId, + filter: '', + page: 1, + perPage: 15, + allSiteLabel: allSiteLabel + }).then(templates => { + populateTemplateSelect(templates, templateId); + onTemplateFetchComplete(templates, templateField); + }); + + if (isTemplateValid) { + fetchTemplateImage(templateId); + } }); diff --git a/vtls/vanillla/example.vtl b/vtls/vanillla/example.vtl new file mode 100644 index 0000000..0f3c1c9 --- /dev/null +++ b/vtls/vanillla/example.vtl @@ -0,0 +1,43 @@ + +
\ No newline at end of file diff --git a/vtls/vanillla/og-preview.vtl b/vtls/vanillla/og-preview.vtl index e24ad9e..3f17d9b 100644 --- a/vtls/vanillla/og-preview.vtl +++ b/vtls/vanillla/og-preview.vtl @@ -1,37 +1,6 @@ - - -
- -
+ - +
+ + diff --git a/vtls/vanillla/text-count.vtl b/vtls/vanillla/text-count.vtl index 5cd09cb..173f214 100644 --- a/vtls/vanillla/text-count.vtl +++ b/vtls/vanillla/text-count.vtl @@ -1,5 +1,5 @@ - ``` @@ -225,51 +239,51 @@ Remove dijit css classes from the code because they are not needed, here some cl - dijitDropDownButton **Old way (deprecated):** + ```html - ``` **New way:** + ```html - + ``` Keep the inline styles and the classes without changes. **Old way** + ```html - ``` **New way: Keep the inline styles and the classes in a style tag.** + ```html - ``` @@ -279,24 +293,26 @@ Remove all `dojoType` attributes and use native HTML elements instead. This prov **Common dojoType patterns to replace:** -| Old (Deprecated) | New | -|-----------------|-----| -| `` | `` | -| `` | `` | +| Old (Deprecated) | New | +| -------------------------------------------------- | ----------------------------------------- | +| `` | `` | +| `` | `` | | `` or native HTML select | -| `
` | `` | -| `` | `` | -| `
` | `` | +| `
` | `` | +| `` | `` | +| `
` | `` | **Old way (deprecated):** + ```html ``` **New way:** + ```html - + ``` @@ -326,22 +342,22 @@ For elements with `dojoType="dijit.Dialog"`, use the native HTML `` elem ``` @@ -377,55 +394,60 @@ For elements with `dojoType="dijit.Dialog"`, use the native HTML `` elem ```html ``` **Old way (deprecated):** + ```html -
+
``` ```html @@ -434,6 +456,7 @@ For elements with `dojoType="dijit.Dialog"`, use the native HTML `` elem ## Best Practices ### Code Organization + - Keep DOM manipulation separate from field API logic - Initialize field references once inside `DotCustomFieldApi.ready()` and reuse them - Use meaningful variable names for field references (e.g., `titleField`, `urlField`) @@ -441,46 +464,53 @@ For elements with `dojoType="dijit.Dialog"`, use the native HTML `` elem - Define helper functions outside of `DotCustomFieldApi.ready()` when they don't need immediate field access **Good pattern:** + ```js // Helper functions defined outside function slugifyText(text) { - return text.toLowerCase().trim().replace(/[^a-z0-9]+/g, '-'); + return text + .toLowerCase() + .trim() + .replace(/[^a-z0-9]+/g, "-"); } DotCustomFieldApi.ready(() => { - // Field references initialized once - const titleField = DotCustomFieldApi.getField('title'); - const urlField = DotCustomFieldApi.getField('url'); - - // Reuse field references - titleField.onChange(value => { - urlField.setValue(slugifyText(value)); - }); + // Field references initialized once + const titleField = DotCustomFieldApi.getField("title"); + const urlField = DotCustomFieldApi.getField("url"); + + // Reuse field references + titleField.onChange((value) => { + urlField.setValue(slugifyText(value)); + }); }); ``` ### Error Handling + - Always check if field values exist before using them (use `|| ''` or `|| defaultValue`) - Handle edge cases where fields might not be available - Check for null/undefined values before calling methods - Use optional chaining or null checks when accessing DOM elements **Safe pattern:** + ```js DotCustomFieldApi.ready(() => { - const field = DotCustomFieldApi.getField('fieldName'); - const value = field.getValue() || ''; // Default to empty string - - const element = document.getElementById('myElement'); - if (element) { - element.textContent = value; - } + const field = DotCustomFieldApi.getField("fieldName"); + const value = field.getValue() || ""; // Default to empty string + + const element = document.getElementById("myElement"); + if (element) { + element.textContent = value; + } }); ``` ### What to Preserve **DO NOT change:** + - VTL variables like `${fieldId}`, `$maxChar`, `$variableName` - these are server-side - Business logic and algorithms - only update API calls - CSS classes that are NOT dijit classes @@ -490,6 +520,7 @@ DotCustomFieldApi.ready(() => { - Function names and variable names (unless they reference deprecated APIs) **DO change:** + - API method calls (get/set/onChangeField → getField/getValue/setValue/onChange) - dojo.ready → DotCustomFieldApi.ready - dojo.byId → document.getElementById @@ -547,41 +578,46 @@ Follow this checklist for each VTL file you migrate: ## Common Pitfalls ### ❌ DON'T: Call getField() multiple times for the same field + ```js // BAD: Inefficient and error-prone DotCustomFieldApi.ready(() => { - DotCustomFieldApi.getField('title').setValue('New Title'); - DotCustomFieldApi.getField('title').getValue(); // Called twice + DotCustomFieldApi.getField("title").setValue("New Title"); + DotCustomFieldApi.getField("title").getValue(); // Called twice }); ``` ### ✅ DO: Store field reference and reuse it + ```js // GOOD: Efficient and clean DotCustomFieldApi.ready(() => { - const titleField = DotCustomFieldApi.getField('title'); - titleField.setValue('New Title'); - const value = titleField.getValue(); + const titleField = DotCustomFieldApi.getField("title"); + titleField.setValue("New Title"); + const value = titleField.getValue(); }); ``` ### ❌ DON'T: Access fields outside DotCustomFieldApi.ready() + ```js // BAD: Race condition, may fail -const field = DotCustomFieldApi.getField('title'); -field.setValue('value'); +const field = DotCustomFieldApi.getField("title"); +field.setValue("value"); ``` ### ✅ DO: Always wrap in ready() + ```js // GOOD: Safe and reliable DotCustomFieldApi.ready(() => { - const field = DotCustomFieldApi.getField('title'); - field.setValue('value'); + const field = DotCustomFieldApi.getField("title"); + field.setValue("value"); }); ``` ### ❌ DON'T: Forget to handle null/undefined values + ```js // BAD: May cause errors const value = field.getValue(); @@ -589,28 +625,31 @@ const length = value.length; // Error if value is null/undefined ``` ### ✅ DO: Provide defaults + ```js // GOOD: Safe handling -const value = field.getValue() || ''; +const value = field.getValue() || ""; const length = value.length; ``` ### ❌ DON'T: Mix old and new APIs + ```js // BAD: Inconsistent DotCustomFieldApi.ready(() => { - const field = DotCustomFieldApi.getField('title'); - DotCustomFieldApi.set('url', 'value'); // Old API + const field = DotCustomFieldApi.getField("title"); + DotCustomFieldApi.set("url", "value"); // Old API }); ``` ### ✅ DO: Use new API consistently + ```js // GOOD: Consistent DotCustomFieldApi.ready(() => { - const titleField = DotCustomFieldApi.getField('title'); - const urlField = DotCustomFieldApi.getField('url'); - urlField.setValue('value'); // New API + const titleField = DotCustomFieldApi.getField("title"); + const urlField = DotCustomFieldApi.getField("url"); + urlField.setValue("value"); // New API }); ``` @@ -621,23 +660,25 @@ DotCustomFieldApi.ready(() => { If the original code has multiple `onChangeField` calls for the same field, combine them in a single `onChange` handler: **Old way:** + ```js -DotCustomFieldApi.onChangeField('title', (value) => { - updateURL(value); +DotCustomFieldApi.onChangeField("title", (value) => { + updateURL(value); }); -DotCustomFieldApi.onChangeField('title', (value) => { - updateFriendlyName(value); +DotCustomFieldApi.onChangeField("title", (value) => { + updateFriendlyName(value); }); ``` **New way:** + ```js DotCustomFieldApi.ready(() => { - const titleField = DotCustomFieldApi.getField('title'); - titleField.onChange(value => { - updateURL(value); - updateFriendlyName(value); - }); + const titleField = DotCustomFieldApi.getField("title"); + titleField.onChange((value) => { + updateURL(value); + updateFriendlyName(value); + }); }); ``` @@ -647,18 +688,18 @@ When you need to preserve and check initial values: ```js DotCustomFieldApi.ready(() => { - const field = DotCustomFieldApi.getField('fieldName'); - const initialValue = field.getValue() || ''; - - // Store initial value for comparison - let previousValue = initialValue; - - field.onChange(value => { - if (value !== previousValue) { - // Value changed - previousValue = value; - } - }); + const field = DotCustomFieldApi.getField("fieldName"); + const initialValue = field.getValue() || ""; + + // Store initial value for comparison + let previousValue = initialValue; + + field.onChange((value) => { + if (value !== previousValue) { + // Value changed + previousValue = value; + } + }); }); ``` @@ -671,108 +712,114 @@ Here are complete examples showing the migration from old patterns to the new Do #### Old way text-count.vtl + ```html
-
- $maxChar characters -
-
Recommended Max $maxChar characters
+
+ $maxChar characters +
+
Recommended Max $maxChar characters
``` ### New way text-count.vtl + ```html
-
- $maxChar characters -
-
Recommended Max $maxChar characters
+
+ $maxChar characters +
+
Recommended Max $maxChar characters
``` @@ -781,74 +828,82 @@ text-count.vtl #### Old way (using dijit.form API) title_custom_field.vtl + ```html - + ``` ### New way (using DotCustomFieldApi) title_custom_field.vtl + ```html - + ``` ### Example 3: Slug Generator with Suggestions @@ -856,181 +911,185 @@ DotCustomFieldApi.ready(() => { #### Old way slug-generator.vtl + ```html - - ``` + ### New way slug-generator.vtl + ```html - +
``` @@ -1065,4 +1124,4 @@ When migrating a VTL file: - If a pattern isn't covered in this guide, apply the principles: use new API, remove dijit, preserve logic - Translate non-English comments to English for consistency - Maintain code readability and organization -- The migrated code should be production-ready and follow all best practices outlined above \ No newline at end of file +- The migrated code should be production-ready and follow all best practices outlined above diff --git a/vtls/TemplateReadStore.js b/vtls/TemplateReadStore.js index fee1f20..654c9b6 100644 --- a/vtls/TemplateReadStore.js +++ b/vtls/TemplateReadStore.js @@ -1,260 +1,268 @@ -dojo.provide('dotcms.dojo.data.TemplateReadStore'); - -dojo.require('dojo.data.api.Read'); -dojo.require('dojo.data.api.Request'); - -dojo.declare('dotcms.dojo.data.TemplateReadStore', null, { - hostId: '', - currentRequest: null, - includeArchived: false, - includeTemplate: null, - templateSelected: '', - allSiteLabel: false, - /** - * To Emulate this old Backend Behavion - * https://github.com/dotCMS/core/blob/7bc05d335b98ffb30d909de9ec82dd4557b37078/dotCMS/src/main/java/com/dotmarketing/portlets/templates/ajax/TemplateAjax.java#L72-L76 - * - * @type {*} - * */ - ALL_SITE_TEMPLATE: { - title: 'All Sites', - fullTitle: 'All Sites', - htmlTitle: '
-- All Sites ---
', - identifier: '0', - inode: '0' - }, - - constructor: function (options) { - this.hostId = options.hostId; - this.allSiteLabel = options.allSiteLabel; - this.templateSelected = options.templateSelected; - }, - - getValue: function (item, attribute, defaultValue) { - return item[attribute] ? item[attribute] : defaultValue; - }, - - getValues: function (item, attribute) { - return dojo.isArray(item[attribute]) ? item[attribute] : [item[attribute]]; - }, - - getAttributes: function (item) { - var attributes = new Array(); - for (att in item) { - attributes.push(att); - } - return attributes; - }, - - hasAttribute: function (item, attribute) { - return item[attribute] != null; - }, - - containsValue: function (item, attribute, value) { - var values = this.getValues(item, attribute); - return dojo.indexOf(values, value) >= 0; - }, - - isItem: function (item) { - return item[type] == 'template'; - }, - - isItemLoaded: function (item) { - return this.isItem(item) ? true : false; - }, - - loadItem: function (keywordArgs) { - if (!this.isItem(keywordArgs.item)) - keywordArgs.onError.call(scope ? scope : dojo.global(), { - message: 'passed item is not a valid template item' - }); - - var scope = keywordArgs.scope; - keywordArgs.onItem.call(scope ? scope : dojo.global(), keywordArgs.item); - }, +dojo.provide("dotcms.dojo.data.TemplateReadStore"); + +dojo.require("dojo.data.api.Read"); +dojo.require("dojo.data.api.Request"); + +dojo.declare("dotcms.dojo.data.TemplateReadStore", null, { + hostId: "", + currentRequest: null, + includeArchived: false, + includeTemplate: null, + templateSelected: "", + allSiteLabel: false, + /** + * To Emulate this old Backend Behavion + * https://github.com/dotCMS/core/blob/7bc05d335b98ffb30d909de9ec82dd4557b37078/dotCMS/src/main/java/com/dotmarketing/portlets/templates/ajax/TemplateAjax.java#L72-L76 + * + * @type {*} + * */ + ALL_SITE_TEMPLATE: { + title: "All Sites", + fullTitle: "All Sites", + htmlTitle: "
-- All Sites ---
", + identifier: "0", + inode: "0", + }, + + constructor: function (options) { + this.hostId = options.hostId; + this.allSiteLabel = options.allSiteLabel; + this.templateSelected = options.templateSelected; + }, + + getValue: function (item, attribute, defaultValue) { + return item[attribute] ? item[attribute] : defaultValue; + }, + + getValues: function (item, attribute) { + return dojo.isArray(item[attribute]) ? item[attribute] : [item[attribute]]; + }, + + getAttributes: function (item) { + var attributes = new Array(); + for (att in item) { + attributes.push(att); + } + return attributes; + }, + + hasAttribute: function (item, attribute) { + return item[attribute] != null; + }, + + containsValue: function (item, attribute, value) { + var values = this.getValues(item, attribute); + return dojo.indexOf(values, value) >= 0; + }, + + isItem: function (item) { + return item[type] == "template"; + }, + + isItemLoaded: function (item) { + return this.isItem(item) ? true : false; + }, + + loadItem: function (keywordArgs) { + if (!this.isItem(keywordArgs.item)) + keywordArgs.onError.call(scope ? scope : dojo.global(), { + message: "passed item is not a valid template item", + }); + + var scope = keywordArgs.scope; + keywordArgs.onItem.call(scope ? scope : dojo.global(), keywordArgs.item); + }, + + fetch: function (keywordArgs) { + var fetchCallbackVar = dojo.hitch( + this, + this.fetchTemplatesCallback, + keywordArgs, + ); + + if (dojo.isString(keywordArgs.query)) { + keywordArgs.query = { fullTitle: keywordArgs.query }; + } - fetch: function (keywordArgs) { - var fetchCallbackVar = dojo.hitch(this, this.fetchTemplatesCallback, keywordArgs); + if (this.hostId != "") { + keywordArgs.query.hostId = this.hostId; + } - if (dojo.isString(keywordArgs.query)) { - keywordArgs.query = { fullTitle: keywordArgs.query }; - } + if (this.includeArchived) { + keywordArgs.queryOptions.includeArchived = this.includeArchived; + } - if (this.hostId != '') { - keywordArgs.query.hostId = this.hostId; - } + if (this.includeTemplate) { + keywordArgs.queryOptions.includeTemplate = this.includeTemplate; + } - if (this.includeArchived) { - keywordArgs.queryOptions.includeArchived = this.includeArchived; - } + if (this.templateSelected) { + keywordArgs.query.templateSelected = this.templateSelected; + } - if (this.includeTemplate) { - keywordArgs.queryOptions.includeTemplate = this.includeTemplate; - } + if ( + (keywordArgs.query.fullTitle == "" || + keywordArgs.query.fullTitle == "undefined" || + keywordArgs.query.fullTitle.indexOf("*") === -1) && + (keywordArgs.count == "undefined" || keywordArgs.count == null) && + (keywordArgs.start == "undefined" || keywordArgs.start == null) + ) { + this.currentRequest = keywordArgs; + this.currentRequest.abort = function () {}; + return this.currentRequest; + } else { + const hostId = keywordArgs.query.hostId; + + // Calculate the current page based on the start index and the page size + const page = Math.floor(keywordArgs.start / keywordArgs.count) + 1; + + // Safely build the query string + const query = new URLSearchParams(); + + query.set("per_page", keywordArgs.count); + query.set("page", page); + query.set("filter", keywordArgs.query.fullTitle.replace("*", "")); + + if (hostId) { + query.set("host", hostId); + } + + if (keywordArgs.sort) { + query.set("orderby", keywordArgs.sort); + } + + const url = `/api/v1/templates/?${query.toString()}`; + + fetch(url) + .then((fetchResp) => fetchResp.json()) + .then((responseEntity) => { + // Check for first page and add the all site template, so it does not break the pagination + if (this.allSiteLabel && page === 1) { + responseEntity.entity.unshift(this.ALL_SITE_TEMPLATE); + } + + this.fetchTemplatesCallback(keywordArgs, responseEntity); + }); + + this.currentRequest = keywordArgs; + this.currentRequest.abort = function () {}; + return this.currentRequest; + } + }, + + fetchTemplatesCallback: function (keywordArgs, templatesEntity) { + var scope = keywordArgs.scope; + if (keywordArgs.onBegin) { + keywordArgs.onBegin.call( + scope ? scope : dojo.global, + templatesEntity.pagination.totalEntries, + this.currentRequest, + ); + } - if (this.templateSelected) { - keywordArgs.query.templateSelected = this.templateSelected; - } + if (keywordArgs.onItem) { + let templatesArray = templatesEntity.entity; + + dojo.forEach( + templatesArray, + function (template) { + keywordArgs.onItem.call( + scope ? scope : dojo.global, + template, + this.currentRequest, + ); + }, + this, + ); + } - if ( - (keywordArgs.query.fullTitle == '' || - keywordArgs.query.fullTitle == 'undefined' || - keywordArgs.query.fullTitle.indexOf('*') === -1) && - (keywordArgs.count == 'undefined' || keywordArgs.count == null) && - (keywordArgs.start == 'undefined' || keywordArgs.start == null) - ) { - this.currentRequest = keywordArgs; - this.currentRequest.abort = function () {}; - return this.currentRequest; - } else { - const hostId = keywordArgs.query.hostId; - - // Calculate the current page based on the start index and the page size - const page = Math.floor(keywordArgs.start / keywordArgs.count) + 1; - - // Safely build the query string - const query = new URLSearchParams(); - - query.set('per_page', keywordArgs.count); - query.set('page', page); - query.set('filter', keywordArgs.query.fullTitle.replace('*', '')); - - if (hostId) { - query.set('host', hostId); - } - - if (keywordArgs.sort) { - query.set('orderby', keywordArgs.sort); - } - - const url = `/api/v1/templates/?${query.toString()}`; - - fetch(url) - .then((fetchResp) => fetchResp.json()) - .then((responseEntity) => { - // Check for first page and add the all site template, so it does not break the pagination - if (this.allSiteLabel && page === 1) { - responseEntity.entity.unshift(this.ALL_SITE_TEMPLATE); - } - - this.fetchTemplatesCallback(keywordArgs, responseEntity); - }); - - this.currentRequest = keywordArgs; - this.currentRequest.abort = function () {}; - return this.currentRequest; - } - }, - - fetchTemplatesCallback: function (keywordArgs, templatesEntity) { - var scope = keywordArgs.scope; - if (keywordArgs.onBegin) { - keywordArgs.onBegin.call( - scope ? scope : dojo.global, - templatesEntity.pagination.totalEntries, - this.currentRequest - ); - } + if (keywordArgs.onComplete) { + keywordArgs.onComplete.call( + scope ? scope : dojo.global, + templatesEntity.entity, + this.currentRequest, + ); + } + }, + + fetchCallback: function (keywordArgs, templatesEntity) { + var scope = keywordArgs.scope; + if (keywordArgs.onBegin) { + keywordArgs.onBegin.call( + scope ? scope : dojo.global, + templatesEntity.pagination.totalEntries, + this.currentRequest, + ); + } - if (keywordArgs.onItem) { - let templatesArray = templatesEntity.entity; - - dojo.forEach( - templatesArray, - function (template) { - keywordArgs.onItem.call( - scope ? scope : dojo.global, - template, - this.currentRequest - ); - }, - this - ); - } + if (keywordArgs.onItem) { + dojo.forEach( + templatesEntity.entity, + function (template) { + keywordArgs.onItem.call( + scope ? scope : dojo.global, + template, + this.currentRequest, + ); + }, + this, + ); + } - if (keywordArgs.onComplete) { - keywordArgs.onComplete.call( - scope ? scope : dojo.global, - templatesEntity.entity, - this.currentRequest - ); - } - }, - - fetchCallback: function (keywordArgs, templatesEntity) { - var scope = keywordArgs.scope; - if (keywordArgs.onBegin) { - keywordArgs.onBegin.call( - scope ? scope : dojo.global, - templatesEntity.pagination.totalEntries, - this.currentRequest - ); - } + if (keywordArgs.onComplete) { + keywordArgs.onComplete.call( + scope ? scope : dojo.global, + templatesEntity.entity, + this.currentRequest, + ); + } + }, - if (keywordArgs.onItem) { - dojo.forEach( - templatesEntity.entity, - function (template) { - keywordArgs.onItem.call( - scope ? scope : dojo.global, - template, - this.currentRequest - ); - }, - this - ); - } + fetchItemByIdentity: function (request) { + const templateId = request.identity; + fetch("/api/v1/templates/" + templateId + "/working") + .then(async (response) => { + // The ok value represents the result of the response status 200 codes + if (response.ok) { + const result = await response.json(); - if (keywordArgs.onComplete) { - keywordArgs.onComplete.call( - scope ? scope : dojo.global, - templatesEntity.entity, - this.currentRequest - ); - } - }, - - fetchItemByIdentity: function (request) { - const templateId = request.identity; - fetch('/api/v1/templates/' + templateId + '/working') - .then(async (response) => { - // The ok value represents the result of the response status 200 codes - if (response.ok) { - const result = await response.json(); - - this.fetchItemByIdentityCallback(request, result.entity); // here we pass the result of the json response to the callback function - } - }) - .catch((e) => { - console.log(e); // Here we can catch the error - }); - }, - - fetchItemByIdentityCallback: function (request, template) { - var scope = request.scope; - if (request.onItem) { - request.onItem.call(scope ? scope : dojo.global, template, this.currentRequest); + this.fetchItemByIdentityCallback(request, result.entity); // here we pass the result of the json response to the callback function } - }, + }) + .catch((e) => { + console.log(e); // Here we can catch the error + }); + }, + + fetchItemByIdentityCallback: function (request, template) { + var scope = request.scope; + if (request.onItem) { + request.onItem.call( + scope ? scope : dojo.global, + template, + this.currentRequest, + ); + } + }, - getFeatures: function () { - return { 'dojo.data.api.Read': true }; - }, + getFeatures: function () { + return { "dojo.data.api.Read": true }; + }, - close: function (request) { - this.currentRequest = null; - this.filterHostId = null; - }, + close: function (request) { + this.currentRequest = null; + this.filterHostId = null; + }, - getLabel: function (item) { - return item['hostName'] + ' ' + item['fullTitle']; - }, + getLabel: function (item) { + return item["hostName"] + " " + item["fullTitle"]; + }, - getLabelAttributes: function (item) { - return ['hostName', 'fullTitle']; - }, + getLabelAttributes: function (item) { + return ["hostName", "fullTitle"]; + }, - getIdentity: function (item) { - return item['identifier']; - } + getIdentity: function (item) { + return item["identifier"]; + }, }); diff --git a/vtls/customFieldReactApp.vtl b/vtls/customFieldReactApp.vtl index ea4c060..32ff1bc 100644 --- a/vtls/customFieldReactApp.vtl +++ b/vtls/customFieldReactApp.vtl @@ -1,30 +1,33 @@ - \ No newline at end of file + iframe.onload = () => { + iframe.contentWindow.document.addEventListener( + "custom-field-react-app", + (event) => { + // Prevent default and stop propagation to avoid conflict issues + event.preventDefault(); + event.stopPropagation(); + + // Updates value of hidden field related to the custom field + hiddenFieldValue.value = event.detail; + }, + ); + }; + diff --git a/vtls/tools/TemplateReadStore.js b/vtls/tools/TemplateReadStore.js new file mode 100644 index 0000000..654c9b6 --- /dev/null +++ b/vtls/tools/TemplateReadStore.js @@ -0,0 +1,268 @@ +dojo.provide("dotcms.dojo.data.TemplateReadStore"); + +dojo.require("dojo.data.api.Read"); +dojo.require("dojo.data.api.Request"); + +dojo.declare("dotcms.dojo.data.TemplateReadStore", null, { + hostId: "", + currentRequest: null, + includeArchived: false, + includeTemplate: null, + templateSelected: "", + allSiteLabel: false, + /** + * To Emulate this old Backend Behavion + * https://github.com/dotCMS/core/blob/7bc05d335b98ffb30d909de9ec82dd4557b37078/dotCMS/src/main/java/com/dotmarketing/portlets/templates/ajax/TemplateAjax.java#L72-L76 + * + * @type {*} + * */ + ALL_SITE_TEMPLATE: { + title: "All Sites", + fullTitle: "All Sites", + htmlTitle: "
-- All Sites ---
", + identifier: "0", + inode: "0", + }, + + constructor: function (options) { + this.hostId = options.hostId; + this.allSiteLabel = options.allSiteLabel; + this.templateSelected = options.templateSelected; + }, + + getValue: function (item, attribute, defaultValue) { + return item[attribute] ? item[attribute] : defaultValue; + }, + + getValues: function (item, attribute) { + return dojo.isArray(item[attribute]) ? item[attribute] : [item[attribute]]; + }, + + getAttributes: function (item) { + var attributes = new Array(); + for (att in item) { + attributes.push(att); + } + return attributes; + }, + + hasAttribute: function (item, attribute) { + return item[attribute] != null; + }, + + containsValue: function (item, attribute, value) { + var values = this.getValues(item, attribute); + return dojo.indexOf(values, value) >= 0; + }, + + isItem: function (item) { + return item[type] == "template"; + }, + + isItemLoaded: function (item) { + return this.isItem(item) ? true : false; + }, + + loadItem: function (keywordArgs) { + if (!this.isItem(keywordArgs.item)) + keywordArgs.onError.call(scope ? scope : dojo.global(), { + message: "passed item is not a valid template item", + }); + + var scope = keywordArgs.scope; + keywordArgs.onItem.call(scope ? scope : dojo.global(), keywordArgs.item); + }, + + fetch: function (keywordArgs) { + var fetchCallbackVar = dojo.hitch( + this, + this.fetchTemplatesCallback, + keywordArgs, + ); + + if (dojo.isString(keywordArgs.query)) { + keywordArgs.query = { fullTitle: keywordArgs.query }; + } + + if (this.hostId != "") { + keywordArgs.query.hostId = this.hostId; + } + + if (this.includeArchived) { + keywordArgs.queryOptions.includeArchived = this.includeArchived; + } + + if (this.includeTemplate) { + keywordArgs.queryOptions.includeTemplate = this.includeTemplate; + } + + if (this.templateSelected) { + keywordArgs.query.templateSelected = this.templateSelected; + } + + if ( + (keywordArgs.query.fullTitle == "" || + keywordArgs.query.fullTitle == "undefined" || + keywordArgs.query.fullTitle.indexOf("*") === -1) && + (keywordArgs.count == "undefined" || keywordArgs.count == null) && + (keywordArgs.start == "undefined" || keywordArgs.start == null) + ) { + this.currentRequest = keywordArgs; + this.currentRequest.abort = function () {}; + return this.currentRequest; + } else { + const hostId = keywordArgs.query.hostId; + + // Calculate the current page based on the start index and the page size + const page = Math.floor(keywordArgs.start / keywordArgs.count) + 1; + + // Safely build the query string + const query = new URLSearchParams(); + + query.set("per_page", keywordArgs.count); + query.set("page", page); + query.set("filter", keywordArgs.query.fullTitle.replace("*", "")); + + if (hostId) { + query.set("host", hostId); + } + + if (keywordArgs.sort) { + query.set("orderby", keywordArgs.sort); + } + + const url = `/api/v1/templates/?${query.toString()}`; + + fetch(url) + .then((fetchResp) => fetchResp.json()) + .then((responseEntity) => { + // Check for first page and add the all site template, so it does not break the pagination + if (this.allSiteLabel && page === 1) { + responseEntity.entity.unshift(this.ALL_SITE_TEMPLATE); + } + + this.fetchTemplatesCallback(keywordArgs, responseEntity); + }); + + this.currentRequest = keywordArgs; + this.currentRequest.abort = function () {}; + return this.currentRequest; + } + }, + + fetchTemplatesCallback: function (keywordArgs, templatesEntity) { + var scope = keywordArgs.scope; + if (keywordArgs.onBegin) { + keywordArgs.onBegin.call( + scope ? scope : dojo.global, + templatesEntity.pagination.totalEntries, + this.currentRequest, + ); + } + + if (keywordArgs.onItem) { + let templatesArray = templatesEntity.entity; + + dojo.forEach( + templatesArray, + function (template) { + keywordArgs.onItem.call( + scope ? scope : dojo.global, + template, + this.currentRequest, + ); + }, + this, + ); + } + + if (keywordArgs.onComplete) { + keywordArgs.onComplete.call( + scope ? scope : dojo.global, + templatesEntity.entity, + this.currentRequest, + ); + } + }, + + fetchCallback: function (keywordArgs, templatesEntity) { + var scope = keywordArgs.scope; + if (keywordArgs.onBegin) { + keywordArgs.onBegin.call( + scope ? scope : dojo.global, + templatesEntity.pagination.totalEntries, + this.currentRequest, + ); + } + + if (keywordArgs.onItem) { + dojo.forEach( + templatesEntity.entity, + function (template) { + keywordArgs.onItem.call( + scope ? scope : dojo.global, + template, + this.currentRequest, + ); + }, + this, + ); + } + + if (keywordArgs.onComplete) { + keywordArgs.onComplete.call( + scope ? scope : dojo.global, + templatesEntity.entity, + this.currentRequest, + ); + } + }, + + fetchItemByIdentity: function (request) { + const templateId = request.identity; + fetch("/api/v1/templates/" + templateId + "/working") + .then(async (response) => { + // The ok value represents the result of the response status 200 codes + if (response.ok) { + const result = await response.json(); + + this.fetchItemByIdentityCallback(request, result.entity); // here we pass the result of the json response to the callback function + } + }) + .catch((e) => { + console.log(e); // Here we can catch the error + }); + }, + + fetchItemByIdentityCallback: function (request, template) { + var scope = request.scope; + if (request.onItem) { + request.onItem.call( + scope ? scope : dojo.global, + template, + this.currentRequest, + ); + } + }, + + getFeatures: function () { + return { "dojo.data.api.Read": true }; + }, + + close: function (request) { + this.currentRequest = null; + this.filterHostId = null; + }, + + getLabel: function (item) { + return item["hostName"] + " " + item["fullTitle"]; + }, + + getLabelAttributes: function (item) { + return ["hostName", "fullTitle"]; + }, + + getIdentity: function (item) { + return item["identifier"]; + }, +}); diff --git a/vtls/tools/cachettl_custom_field.vtl b/vtls/tools/cachettl_custom_field.vtl index c38240a..f187594 100644 --- a/vtls/tools/cachettl_custom_field.vtl +++ b/vtls/tools/cachettl_custom_field.vtl @@ -1,19 +1,19 @@ - \ No newline at end of file + diff --git a/vtls/tools/tag_storage_field_creation.vtl b/vtls/tools/tag_storage_field_creation.vtl index d2c27c4..1c82720 100644 --- a/vtls/tools/tag_storage_field_creation.vtl +++ b/vtls/tools/tag_storage_field_creation.vtl @@ -1,75 +1,52 @@ -#set($isCopyingHost = false) -#set($tagStorageFromURL = "") - -#if($request.getAttribute("CURRENT_URL").contains("copy_tag_storage")) - #set($isCopyingHost = true) - #set($tempURL = $request.getAttribute("CURRENT_URL")) - #set($beginning = $math.add($tempURL.indexOf("copy_tag_storage"), 19)) - #set($tagStorageFromURL = $tempURL.substring($beginning,$math.add($beginning, 36))) -#end - - - - - - -// value -// disabled state -// validations -// label -// hint -// validation message - --- rewrite values --- hide fields --- readonther fields --- rewrite options?? - - -WC - -Bridge.get('title') // value -Bridge.set('title', 'asas') // value -Bridge.onChangeField('saas', () => { - -}) - -Bridge.disabled('title', true) -Bridge.hidden('title', true) // @if - -{ - visibilty: true/false -} - - -const field = Bridge.getField('asas') -field.getValue() -field.visibilty() - - -.diabelr() -.sunsctibet() - - -Bridge.openModalPageSelector() // \ No newline at end of file +#set($isCopyingHost = false) #set($tagStorageFromURL = "") +#if($request.getAttribute("CURRENT_URL").contains("copy_tag_storage")) +#set($isCopyingHost = true) #set($tempURL = +$request.getAttribute("CURRENT_URL")) #set($beginning = +$math.add($tempURL.indexOf("copy_tag_storage"), 19)) #set($tagStorageFromURL = +$tempURL.substring($beginning,$math.add($beginning, 36))) #end + + + + + +// value // disabled state // validations // label // hint // validation message +-- rewrite values -- hide fields -- readonther fields -- rewrite options?? WC +Bridge.get('title') // value Bridge.set('title', 'asas') // value +Bridge.onChangeField('saas', () => { }) Bridge.disabled('title', true) +Bridge.hidden('title', true) // @if { visibilty: true/false } const field = +Bridge.getField('asas') field.getValue() field.visibilty() .diabelr() +.sunsctibet() Bridge.openModalPageSelector() // diff --git a/vtls/tools/template_custom_field.vtl b/vtls/tools/template_custom_field.vtl index e06ee14..8797658 100644 --- a/vtls/tools/template_custom_field.vtl +++ b/vtls/tools/template_custom_field.vtl @@ -1,310 +1,975 @@ - - -
- -
- Template Thumbnail -
- + } + }); + + +
+
+
+
+ + + +
+ Template Thumbnail +
+
+ \ No newline at end of file diff --git a/vtls/tools/template_custom_field_old.vtl b/vtls/tools/template_custom_field_old.vtl new file mode 100644 index 0000000..521064a --- /dev/null +++ b/vtls/tools/template_custom_field_old.vtl @@ -0,0 +1,230 @@ + + +
+ +
+ Template Thumbnail +
diff --git a/vtls/tools/userID.vtl b/vtls/tools/userID.vtl index 5f79405..8441ef3 100644 --- a/vtls/tools/userID.vtl +++ b/vtls/tools/userID.vtl @@ -1,42 +1,40 @@ ##Set Custom Field to ID of logged in user on Backend or Front end -#set($foundUser = $session.getAttribute("USER_ID")) - -#if($foundUser) - #set($userId = "$!{foundUser}") -#end +#set($foundUser = $session.getAttribute("USER_ID")) #if($foundUser) #set($userId += "$!{foundUser}") #end \ No newline at end of file + dojo.addOnLoad(function () { + setCustomUserIdValue(); + }); + diff --git a/vtls/vanillla/dynamic-field.vtl b/vtls/vanillla/dynamic-field.vtl index 036fcb5..239dabd 100644 --- a/vtls/vanillla/dynamic-field.vtl +++ b/vtls/vanillla/dynamic-field.vtl @@ -1,64 +1,68 @@ - -
- -    - -
+ updateLocation(); + }); + + +
+ + +    + + +
diff --git a/vtls/vanillla/example.vtl b/vtls/vanillla/example.vtl index 0f3c1c9..670a2ea 100644 --- a/vtls/vanillla/example.vtl +++ b/vtls/vanillla/example.vtl @@ -1,43 +1,44 @@ -
\ No newline at end of file +
diff --git a/vtls/vanillla/geolocation.vtl b/vtls/vanillla/geolocation.vtl index bda3fbc..1d624b8 100644 --- a/vtls/vanillla/geolocation.vtl +++ b/vtls/vanillla/geolocation.vtl @@ -1,5 +1,5 @@ - + - - + +
-
+
-
\ No newline at end of file +
diff --git a/vtls/vanillla/keytag_custom_field.vtl b/vtls/vanillla/keytag_custom_field.vtl index e5bc671..7738090 100644 --- a/vtls/vanillla/keytag_custom_field.vtl +++ b/vtls/vanillla/keytag_custom_field.vtl @@ -1,62 +1,78 @@ - - - - -
-
- - -
-
  - -
\ No newline at end of file + + +
+ +
+ + +   + + + + diff --git a/vtls/vanillla/og-preview.vtl b/vtls/vanillla/og-preview.vtl index 3f17d9b..39c98b3 100644 --- a/vtls/vanillla/og-preview.vtl +++ b/vtls/vanillla/og-preview.vtl @@ -1,162 +1,164 @@ -
-

Facebook

- -
-
-
-
-
$!{host.map.aliases}
-
-
-
+
+

Facebook

+ +
+
+
+
+
$!{host.map.aliases}
+
+
+
diff --git a/vtls/vanillla/text-count.vtl b/vtls/vanillla/text-count.vtl index 173f214..cce148c 100644 --- a/vtls/vanillla/text-count.vtl +++ b/vtls/vanillla/text-count.vtl @@ -1,47 +1,48 @@
-
- $maxChar characters -
-
Recommended Max $maxChar characters
+
+ $maxChar characters +
+
Recommended Max $maxChar characters
\ No newline at end of file + updateCharacterCount(field.getValue() || ""); + }); + diff --git a/vtls/vanillla/title_custom_field.vtl b/vtls/vanillla/title_custom_field.vtl index cab8b98..35387d3 100644 --- a/vtls/vanillla/title_custom_field.vtl +++ b/vtls/vanillla/title_custom_field.vtl @@ -1,28 +1,30 @@ - \ No newline at end of file + diff --git a/vtls/vanillla/url-title.vtl b/vtls/vanillla/url-title.vtl index 5d2301f..fb8ba27 100644 --- a/vtls/vanillla/url-title.vtl +++ b/vtls/vanillla/url-title.vtl @@ -1,89 +1,87 @@ - -
\ No newline at end of file + +
diff --git a/vtls/vanillla/youtube-search.vtl b/vtls/vanillla/youtube-search.vtl index 7fef45b..f45c7de 100644 --- a/vtls/vanillla/youtube-search.vtl +++ b/vtls/vanillla/youtube-search.vtl @@ -18,7 +18,9 @@ #myResults ul { margin: 0; padding: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; + font-family: + -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, + Cantarell, sans-serif; font-size: 14px; line-height: 1.5; } @@ -114,319 +116,381 @@
-
- - +
+ + +
+ +
+ + +
+

+ Select a Video +

+
- -
- - -
-

Select a Video

- -
-
-
+
+
- \ No newline at end of file + diff --git a/vtls/widget/color-picker.vtl b/vtls/widget/color-picker.vtl index cbec379..30acaf5 100644 --- a/vtls/widget/color-picker.vtl +++ b/vtls/widget/color-picker.vtl @@ -1,51 +1,81 @@ -
- - -
+
+ + +
-
-
\ No newline at end of file +
+
diff --git a/vtls/widget/file_browser_field_render.vtl b/vtls/widget/file_browser_field_render.vtl index 95e9233..7c7c101 100644 --- a/vtls/widget/file_browser_field_render.vtl +++ b/vtls/widget/file_browser_field_render.vtl @@ -1,37 +1,50 @@
- - -
-
+ +
+
diff --git a/vtls/widget/redirect.vtl b/vtls/widget/redirect.vtl index 210b962..27d93f5 100644 --- a/vtls/widget/redirect.vtl +++ b/vtls/widget/redirect.vtl @@ -1,75 +1,82 @@ #if( $structures.isNewEditModeEnabled() ) - + - dojo.ready(function() { - dijit.byId('redirectURLBox').set('value', dojo.byId('redirecturl').value); - dojo.connect(dijit.byId('redirectURLBox'),"onChange",updateRedirectValue); - }); +
+ + +
+#else + + function updateRedirectValue() { + dojo.byId("redirecturl").value = dijit.byId("redirectURLBox").get("value"); + } + dojo.ready(function () { + dijit.byId("redirectURLBox").set("value", dojo.byId("redirecturl").value); + dojo.connect(dijit.byId("redirectURLBox"), "onChange", updateRedirectValue); + }); + - + - + -
-#end \ No newline at end of file +
+#end diff --git a/vtls/widget/redirect_custom_field.vtl b/vtls/widget/redirect_custom_field.vtl index 3ab4a09..d0f496f 100644 --- a/vtls/widget/redirect_custom_field.vtl +++ b/vtls/widget/redirect_custom_field.vtl @@ -1,50 +1,45 @@ -
- - +
+ +