hyva-themes / magento2-hyva-admin

This module aims to make creating grids and forms in the Magento 2 adminhtml area joyful and fast.
https://hyva-themes.github.io/magento2-hyva-admin/
BSD 3-Clause "New" or "Revised" License
168 stars 39 forks source link

JavaScript extensibility for grids and forms #35

Open Vinai opened 3 years ago

Vinai commented 3 years ago

Issue Description

This Issue is to document the decisions regarding how to enable developers to trigger JavaScript on visitor actions, and to gather feedback.

Originally I started thinking about it in relation to the forms (issue #27), but @Fabian Schmengler has a use case for the grids, too (see #34).

I think a common solution should be used.


A) Event specific attributes with native JS expressions

The idea is to add attributes like onchange, onclick, onfocus and so on to field and action elements, and the value could be rendered into the generated HTML directly. For example

<action id="foo" label="Foo" onclick="showPopup()"/>

This would result in an HTML element like this:

<a onclick="showPopup()">Foo</a>

Pros of this approach:

Cons of this approach:


B) Generic event child elements with convention based event names

In this approach, event elements can be added as children to supported grid and form nodes. For example:

<action id="foo" label="Foo">
    <event on="click"/>
</action>

This would result in the following HTML:

<a @on:click="$dispatch('hyva_grid_[grid-name]_[action-id]_click"/>

For grids the event name would include the grid-name and the action-id following the pattern in the example above (the last part would depend on the event, i.e. click, keydown, etc). For forms the event name would follow the pattern hyva_form_[form-name]_[field-name]_[event-type].

Grid and form names would need to be normalized so they work in event names, that is, lowercase characters and underscores only.

The above example HTML would actially be slightly more complex because event arguments would need to be added to the dispatch.

Pros of this approach:

Cons of this approach:


C) Generic event child elements with configurable event names

This approach is very similar to approach B), except that each event can specify a custom event name. For example:

<action id="foo" label="Foo">
    <event on="click" name="my_foo_clicked"/>
</action>

This would result in the following HTML:

<a @on:click="$dispatch('my_foo_click')"/>

The above example HTML would actially be slightly more complex because event arguments would need to be added to the dispatch.

Pros of this approach:

Cons of this approach:


D) Native events only with external subscribers

Any HTML element can be observed by subscribers that add attach programmatically. This already is possible and require no knowledge beyond native JavaScript and the DOM model.

Pros of this approach:

Cons of this approach:

Vinai commented 3 years ago

Whatever approach comes up on top, I'll first create a prototype to see how it feels to use it. Any feedback or additional ideas are very welcome.

schmengler commented 3 years ago

Other alternatives:

schmengler commented 3 years ago

I do like the <event> approach and I think with one or more of the mentioned adjustments we can make it more accessible too

Vinai commented 3 years ago

Note to self: if using B or C there should be a special events that is triggered before and after the initial render. Maybe something like

<event on="init"/> <event on="render"/>

Vinai commented 3 years ago

Hyva_Admin Elements that can have events:

Grids

Forms

Vinai commented 3 years ago

I'll start by implementing option B (Generic event child elements with convention based event names).

To try this out only grid actions will support events initially. Generic vents for rows and buttons will be added later once things stabilize.

If needed an attribute to specify custom events can still be added in a backward compatible manner later.

Vinai commented 3 years ago

First experimental release in 1.1.13 to gather feedback

The API for the action event node might be removed or changed in future.

Specifying event triggers on actions allows creating complex ui customizations. Only the event trigger can be specified in the grid XML:

<actions>
    <action id="delete" label="Delete" url="*/*/delete">
        <event on="click"/>
    </action>
</actions>

Event Name

The event name is built based on the grid name, the action id and the event trigger:

private function getEventName(): string
{
    $gridNameInEvent = $this->eventify($this->gridName);
    return sprintf('hyva-grid-%s-action-%s-%s', $gridNameInEvent, $this->eventify($this->targetId), $this->on);
}

For example, given a grid named products-query-grid, and an action with the id delete, the JavaScript event that is dispatched is

hyva-grid-products-query-grid-action-delete-click

Event Subscribers

Event subscribers can be declared in .phtml template files that are added to the grid page via layout XML.

Example:

<script>
window.addEventListener('hyva-grid-products-grid-action-delete-click', e => {
    if (! confirm('<?= __('Are you sure?') ?>')) {
        e.detail.origEvent.preventDefault();
    }
});
</script>

Event Arguments

The event arguments can be retrieved from the events.detail property in subscribers.

event.detail.origEvent

This is the original event that was triggered by the user interaction.

probably this is mainly useful to abort user actions with event.detail.origEvent.preventDefault().

event.detail.row

This property is a reference to the clicked grid table row.

It might be useful to retrieve the rendered cell values in a kind of hacky way.

event.detail.viewModel

This is the Alpine.js view model of the grid.

event.detail.action

This is the grid action id. In the examples on this page it is the string delete.

event.detail.params

This is the map of parameters that would be passed to the URL. It depends on the action configuration.

The following example will add the params {foo => idValue}:

<actions idColumn="id">
    <action id="delete" label="Delete" url="*/*/delete" idParam="foo">
        <event on="click"/>
    </action>
</actions>