Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Edit Content: Research for Custom Fields #27661

Closed
Tracked by #25445
zJaaal opened this issue Feb 20, 2024 · 3 comments
Closed
Tracked by #25445

Edit Content: Research for Custom Fields #27661

zJaaal opened this issue Feb 20, 2024 · 3 comments

Comments

@zJaaal
Copy link
Contributor

zJaaal commented Feb 20, 2024

Parent Issue

#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 zJaaal self-assigned this Feb 20, 2024
@rjvelazco rjvelazco changed the title Edit Content: Research for Custom Fields (Proxy Approach) Edit Content: Research for Custom Fields Feb 20, 2024
@rjvelazco rjvelazco self-assigned this Feb 20, 2024
@KevinDavilaDotCMS KevinDavilaDotCMS self-assigned this Feb 20, 2024
@zJaaal
Copy link
Contributor Author

zJaaal commented Feb 20, 2024

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
Copy link
Contributor

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
Copy link
Contributor

rjvelazco commented Feb 20, 2024

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:

  • How to set the initial value of each input.
  • Test if this approach works with dojoInputElement.setValue("New Value").

Video

POC PR

POC-27661-edit-content-research-for-custom-fields.mov

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment