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

JavaScript extensibility for grids and forms #35

Open
Vinai opened this issue Feb 8, 2021 · 7 comments
Open

JavaScript extensibility for grids and forms #35

Vinai opened this issue Feb 8, 2021 · 7 comments

Comments

@Vinai
Copy link
Collaborator

Vinai commented Feb 8, 2021

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:

  • Intuitive
  • Fast to use

Cons of this approach:

  • Scope of expression unclear, i.e. probably the function showPopup in the above expression would have to be declared on the window in global scope, unless more work is done to make the alpineJs view model extensible.
  • Conflict prone. Existing onclick values would be overwritten during configuration merging.
  • More and more event specific attributes need to be declared if more events need to be supported
  • Hard to find a good syntax for dynamic argument, e.g. the row idParam value, the for field and so on

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:

  • Multiple extension can add subscribers without conflicting.
  • Can be used with any event without new attributes needed to be added to the XML schema for each one.
  • Clear regarding the scope of the subscribers (completely decoupled from the grid and form) besides the event name.
  • Simple to pass a good selection of arguments: for grids the tr row element and the view model, and for forms the field element and also the view model.

Cons of this approach:

  • A event can't be disabled once it was declared (but is this even needed?)
  • More complicated to learn, not intuitive.

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:

  • Multiple extension can add subscribers without conflicting.
  • Can be used with any event without new attributes needed to be added to the XML schema for each one.
  • Clear regarding the scope of the subscribers (completely decoupled from the grid and form) besides the event name.
  • Simple to pass a good selection of arguments: for grids the tr row element and the view model, and for forms the field element and also the view model.

Cons of this approach:

  • A enabled attribute cold be added which would allow disabling events declared by other modules (but is this even needed?)
  • More complicated to learn, not intuitive, but maybe easier than B)(?)

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:

  • No additional work needed in Hyva_Admin

Cons of this approach:

  • Feels "hacky" to me :)
  • Not intuitive to use
@Vinai
Copy link
Collaborator Author

Vinai commented Feb 8, 2021

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

Other alternatives:

  • Same as C but if you omit the event name, a default one is chosen (i.e. like B)
  • B but without XML. Always trigger the most common events like "click".
  • B or C with an optional observer attribute so that for simple scripts it is as easy to use as A: <event on="click" observer="console.log(this)">

@schmengler
Copy link
Contributor

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 Vinai mentioned this issue Feb 9, 2021
@Vinai
Copy link
Collaborator Author

Vinai commented Feb 9, 2021

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
Copy link
Collaborator Author

Vinai commented Feb 9, 2021

Hyva_Admin Elements that can have events:

Grids

  • Grid row actions
  • Grid rows
  • Grid buttons

Forms

  • Form fields
  • Form groups
  • Form sections
  • Form buttons

@Vinai Vinai mentioned this issue Feb 9, 2021
15 tasks
@Vinai
Copy link
Collaborator Author

Vinai commented May 5, 2021

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
Copy link
Collaborator Author

Vinai commented May 5, 2021

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>

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

No branches or pull requests

2 participants