diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..1f91790 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,10 @@ +{ + "overrides": [ + { + "files": "*.vtl", + "options": { + "parser": "html" + } + } + ] +} diff --git a/migration.md b/migration.md new file mode 100644 index 0000000..dd1b166 --- /dev/null +++ b/migration.md @@ -0,0 +1,1127 @@ +--- +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 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 +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 | +| --------------- | ------------------------------------------------------ | --------------------------------------------------------------------- | +| 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("variableName"); +``` + +**New way:** + +```js +const field = DotCustomFieldApi.getField("variableName"); +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("variableName", "new value"); +``` + +**New way:** + +```js +const field = DotCustomFieldApi.getField("variableName"); +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("variableName", (value) => { + console.log(value); +}); +``` + +**New way:** + +```js +const field = DotCustomFieldApi.getField("variableName"); +field.onChange((value) => { + console.log(value); +}); +``` + +### 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. + +**Required pattern:** + +```js +DotCustomFieldApi.ready(() => { + // All field access code goes here + const field = DotCustomFieldApi.getField("variableName"); + field.getValue(); +}); +``` + +**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. + +**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 + +``` + +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 + + +``` + +### 9. Native Dialog Implementation + +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 + + + + +``` + +### 11. File and Page Browser Dialog + +```html + +``` + +**Old way (deprecated):** + +```html + +
+``` + +```html + +``` + +## 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`) +- 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 + +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 + + + + +``` + +### New way + +slug-generator.vtl + +```html + + + +
+``` + +## Agent Instructions + +When migrating a VTL file: + +1. **Read the entire file first** to understand the full context and functionality +2. **Identify all deprecated patterns** using the checklist above +3. **Migrate systematically** following the migration rules in order +4. **Test your changes** by ensuring: + - All field accesses use the new API + - All deprecated APIs are removed + - VTL variables are preserved unchanged + - Business logic remains identical + - HTML structure is cleaned (dijit removed) +5. **Verify completeness**: + - No `DotCustomFieldApi.get()` calls remain + - No `DotCustomFieldApi.set()` calls remain + - No `DotCustomFieldApi.onChangeField()` calls remain + - No `dojo.*` references remain + - No `dijit.*` references remain + - No `dojoType` attributes remain + - No `class="dijit*"` classes remain + - All field access wrapped in `DotCustomFieldApi.ready()` + +6. **Output the complete migrated file** with all changes applied + +## Final Notes + +- When in doubt, preserve existing functionality +- 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 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 39549a6..f187594 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 + 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 f62dea4..8797658 100644 --- a/vtls/tools/template_custom_field.vtl +++ b/vtls/tools/template_custom_field.vtl @@ -1,219 +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 9865ce4..239dabd 100644 --- a/vtls/vanillla/dynamic-field.vtl +++ b/vtls/vanillla/dynamic-field.vtl @@ -1,68 +1,68 @@ - - - -
- -    - -
- - ##//i.ytimg.com/vi/aAHeJ5pjROk/mqdefault.jpg \ No newline at end of file + updateLocation(); + }); + + +
+ + +    + + +
diff --git a/vtls/vanillla/example.vtl b/vtls/vanillla/example.vtl new file mode 100644 index 0000000..670a2ea --- /dev/null +++ b/vtls/vanillla/example.vtl @@ -0,0 +1,44 @@ + +
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 e24ad9e..39c98b3 100644 --- a/vtls/vanillla/og-preview.vtl +++ b/vtls/vanillla/og-preview.vtl @@ -1,101 +1,164 @@ - + - + DotCustomFieldApi.ready(() => { + const ogTitleField = DotCustomFieldApi.getField("ogTitle"); + const ogDescriptionField = DotCustomFieldApi.getField("ogDescription"); + const ogImageField = DotCustomFieldApi.getField("ogImage"); -
- -
+ const dialog = document.getElementById("ogPreview"); + const previewButton = document.getElementById("ogPreviewButton"); + const closeButton = document.getElementById("ogPreviewCloseBtn"); + const cardImage = document.getElementById("cardImage"); + const cardTitle = document.getElementById("cardTitle"); + const cardDescription = document.getElementById("cardDescription"); - + // Open dialog and populate preview + previewButton.addEventListener("click", () => { + const ogTitle = ogTitleField.getValue() || ""; + const ogDescription = ogDescriptionField.getValue() || ""; + + // Get image from ogImage field container + const imgInode = ogImageField.getValue() || ""; + const imgUrl = imgInode ? `/dA/${imgInode}/asset/500w/50q/` : ""; + // Truncate description to 92 characters + const truncatedDescription = truncateDescription(ogDescription, 92); + + // Update preview card + cardImage.src = imgUrl; + cardTitle.textContent = ogTitle; + cardDescription.textContent = truncatedDescription; + + dialog.showModal(); + }); + + // Close dialog + closeButton.addEventListener("click", () => { + dialog.close(); + }); + + // Close dialog when clicking outside + dialog.addEventListener("click", (e) => { + if (e.target === dialog) { + dialog.close(); + } + }); + }); + diff --git a/vtls/vanillla/text-count.vtl b/vtls/vanillla/text-count.vtl index 1a735b3..cce148c 100644 --- a/vtls/vanillla/text-count.vtl +++ b/vtls/vanillla/text-count.vtl @@ -1,49 +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 bb0a6dc..35387d3 100644 --- a/vtls/vanillla/title_custom_field.vtl +++ b/vtls/vanillla/title_custom_field.vtl @@ -1,31 +1,30 @@ - - \ No newline at end of file + diff --git a/vtls/vanillla/url-title.vtl b/vtls/vanillla/url-title.vtl index 0d6ded7..fb8ba27 100644 --- a/vtls/vanillla/url-title.vtl +++ b/vtls/vanillla/url-title.vtl @@ -1,82 +1,87 @@ -#** 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..f45c7de 100644 --- a/vtls/vanillla/youtube-search.vtl +++ b/vtls/vanillla/youtube-search.vtl @@ -1,138 +1,496 @@ -
-
- - -
- -
- - - \ 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 new file mode 100644 index 0000000..27d93f5 --- /dev/null +++ b/vtls/widget/redirect.vtl @@ -0,0 +1,82 @@ +#if( $structures.isNewEditModeEnabled() ) + + +
+ + +
+#else + + + + + + +
+#end diff --git a/vtls/widget/redirect_custom_field.vtl b/vtls/widget/redirect_custom_field.vtl index 10f6475..d0f496f 100644 --- a/vtls/widget/redirect_custom_field.vtl +++ b/vtls/widget/redirect_custom_field.vtl @@ -1,30 +1,45 @@ - - - - - -
\ No newline at end of file +
+ + +