dotCMS / core

Headless/Hybrid Content Management System for Enterprises
http://dotcms.com
Other
846 stars 465 forks source link

Edit Content: Research for Custom Fields #27661

Closed zJaaal closed 7 months ago

zJaaal commented 7 months ago

Parent Issue

https://github.com/dotCMS/core/issues/25445

Task

Make research of how to communicate Angular with Custom Field Iframe using input with hidden type and an strategy to dispatch events from the Iframe to the Angular side.

Timebox: 2h

Proposed Objective

Technical User Experience

Proposed Priority

Priority 3 - Average

zJaaal commented 7 months ago

Research (Proxy API Approach)

We know that a Proxy needs to be created using new Proxy(target, handler) this means that we have two objects that in theory has the same prototype but one with a Proxy in the middle and one without one. So in order to hit the handler of the proxy we need to somehow inject the Proxied Element/Node instead of the original one.

Due to the default behavior of DOM Nodes and DOM Elements, we cannot inject Proxied Elements/Nodes, because the browser engine manage those Objects as special and embedded ones. This means that any attempt to append/replace a node/element with a proxied one is rejected by the engine.

One approach that I found several times while investigating how this works, is to create custom elements that controls the get/set of their values. I'm not really sure if it is an optimal approach right now but can be a point to explore if needed.

Proxy is not a way because of how the engine works at the moment. This can change in the future, but right now is kind of impossible.

KevinDavilaDotCMS commented 7 months ago

Research Pure Javascript

After some attemps, is clear the element.value = "some" doesnt trigger any event. So we have some options

  1. Trigger manually event
    element.value = "some"
    dispatchEvent(InputEvent)

So this trigger the InputEvent and run the callback inside EventListener This implies re-write a lot of code. It's not convenient

  1. MutationObserver
    
    const targetNode = dojo.byId("search");
    const config = { attributes: true, childList: true, subtree: true };

const callback = function(mutationsList, observer) { for(const mutation of mutationsList) { if(mutation.type === 'attributes' && mutation.target === targetNode) {

        // Write code here
    }
}

};

const observer = new MutationObserver(callback); observer.observe(targetNode, config);



This approach seems works, but is cost and doest work good with dojo.byId().
Needs reseach but probably this is not the way
rjvelazco commented 7 months ago

Research Pure Javascript (Object.defineProperty Approach).

To make custom fields functional in Angular without breaking legacy code, we can use the Object.defineProperty approach.

  1. We need to create the Dojo Inputs inside the iframe so that the user can get them by calling the dojo.byId() method.
const fields = Object.values(<%= fieldJson %>);
const bodyElement = document.querySelector('body');
fields.forEach(({ variable, value }) => {
    const input = document.createElement('input');
    input.setAttribute('type', 'hidden');
    input.setAttribute('name', variable);
    input.setAttribute('id', variable); // Use the variable as the id for the input, as we did in the previous edit content.
    input.setAttribute('dojoType', 'dijit.form.TextBox'); // Required for Dojo to recognize the input as a widget
    input.setAttribute('value', value);
    bodyElement.appendChild(input);
});
  1. Once dojo is loaded, we redefine the property value for each field:
fields.forEach(({ variable }) => {
    // This code copies the original descriptor from the HTMLInputElement prototype.
    const valueDescriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
    const input = dojo.byId(variable);
    Object.defineProperty(input, 'value', {
        get: () => valueDescriptor.get.apply(this),
        set: function(value) {
            // We can emit custom events in Angular to notify changes.
            console.log("Something triggered the setter", {
                variable,
                value
            });
            valueDescriptor.set.apply(this, [value]);
        }
    });
});

This approach works with: dojoInputElement.value="new Value".

To-do:

Video

POC PR

https://github.com/dotCMS/core/assets/72418962/09d9d457-0cd5-4fac-b2c9-216ececaa947

P.S.: I added those console logs without modifying the .vtl code. However, we can emit an event to Angular instead.