I'm still sceptical about this feature:

IMHO event delegation is a delegate tool and you cannot insert it transparently: No one in the JavaScript world would try to do it that way.

When you start thinking about JavaScript performance of your event handlers you probably have a huge table in your markup. This can easily be solved with a custom event behavior (usually just click handlers) which can dissect which item was clicked on. No need to change anything in Wicket for that, instead you really get smaller html markup. If you hope to be able to reuse complicated components in the table and just wrap event delegation magic around it ... that just doesn't work. For example Wicket's autoComplete and editableLabel won't benefit from the suggested solution, they use special event handling.

My proposal is to add an indirection into Wicket so that people can plug in their solution if they want to, e.g. something Martin has presented. But I wouldn't like to see this in core (yet).

Best regards
Sven


On 07/12/2013 10:50 AM, Martin Grigorov wrote:
On Fri, Jul 12, 2013 at 8:59 AM, Igor Vaynberg <[email protected]>wrote:

On Thu, Jul 11, 2013 at 7:22 AM, Martin Grigorov <[email protected]>
wrote:
On Thu, Jul 11, 2013 at 4:48 PM, Sven Meier <[email protected]> wrote:

Hi,


The idea with plain JS solution I cannot visualize in my head yet.
EventDelegatingBehavior is just a collector of JavaScript snippets. The
actual magic runs in the browser: a custom bubbling of events and
delegation to the actual behavior.
It should be possible to do this plain with JavaScript:

   public class DelegatingAjax implements IAjax {

     public ajax(IHeaderResponse response, Component component,
AjaxRequestAttributes attributes) {
       CharSequence ajaxAttributes = renderAjaxAttributes(**component,
attributes);


response.render(**OnDomReadyHeaderItem.**forScript("Wicket.Event.***delegate*("
+ ajaxAttributes + ");");
     }
   }

This would be page-global though.

This is an important detail!
I'll consult with my frontend colleagues but so far I don't see problems.

For every delegated component we can set special CSS class, e.g.
'wicket-delegated'.
The binding will be: $(document).on('click', '.wicket-delegated',
function(event) {....})
i.e. we will take advantage of jQuery delegation/live support.
This way even newly added items in the repeaters will be automatically
supported.

this is partially on the right track, but there are still some
optimization that can be made.

first, the ajax attributes need to be moved into a data attribute that
is written out on the tag. the final output of attaching a onclick
ajax behavior to a tag should end up looking like this:

<a wicket:id="ajaxlink"
data-w-click="u/?0.foo:bar.ILinkListener/c/default/pd/true"/>

(we will need to figure out how to encode ajax attributes into a string)

example:
<a id="c23" data-w-attrs='{"u":"someUrl","m":"post"}' ...>

$('#c23').data("w-attrs") === {u: "someUrl", m: "post"}

This works for valid JSON, but it doesn't for the enhancement we use - the
functions for the call listeners.


then you can have the one global listener:

$(document).on("click", function(e) {

The problem here is that using 'document' will make the things actually
slower.
We need to find a simple way to be able to bind on a parent component.
In Sven's example - a table with many cells the most appropriate element is
the <table> itself.

In event-delegating-behavior branch I need to traverse the parent
components and their behaviors to be able to find the appropriate parent.
So we win some performance in JS execution but lose some in Java :-/

    var element=$(this), attrs=element.attr("data-w-click");
    if (attrs&&!e.handledByWicket)
        Wicket.Ajax.call(attrs);
        e.handledByWicket=true; // if there are more handlers above, do
not double process the event - read below
    }
}

the advantage here is that we only have one javascript listener that
needs to be registered.

however, there are a few disadvantages:
* event propagation options wont work anymore, because the event has
to propagate all the way to the document in order to trigger.
* some libraries block events. for example if there is a panel with an
ajax link inside a third party modal window. the modal window lib may
prevent any clicks from propagating out of itself, which means the
handler on the document will never see them.

we can sort of solve this by having a behavior that would write out
the listener above, but attached to the component not the document.

that way, if we look at my example with the panel inside the modal,
the user can add this behavior to the panel that will be in the modal
and still be able to capture the event.

this does, however, make troubleshooting more difficult. why didnt my
ajax event trigger? you will have to be a lot more aware about what
javascript you have in the dom.

i think a short term goal might be to move the ajax attributes into a
dom attribute and change our ajax code to simply say
Wicket.Ajax.bind("click", "component234");

see above (valid JSON)

we can enrich the DOM:
<a ... onsuccess="someScript">
but I think this is a step back to Wicket 1.5 days (ajax decorators on
strings, etc.)


this will register the listener like above on the element directly. so
no delegation yet but cleaner javascript/html. also the browser doesnt
have to parse as much javascript, so it will be a bit speedier.

potentially we can collect ids to further optimize js size:
Wicket.Ajax.bind({click, ["c34", "c32"], blur: ["c22","c98"]);

-igor




Sven



On 07/11/2013 03:40 PM, Martin Grigorov wrote:

On Thu, Jul 11, 2013 at 4:30 PM, Nick Pratt <[email protected]> wrote:

  I think this is great - we have some tables now with a ton of JS
events
on
the child elements.  Just to clarify, will this make the rendered page
smaller since there will only be a single JS handler for the event for
the
container rather than N JS handlers?

  At the moment all attributes for an inner element are preserved.
'e' (the event name), 'c' (the component markup id), pd (prevent
default),
sp (stop propagation) can be removed because they are not really used.
But every inner element can have its own call listeners, form
submitters
can also have custom settings ('f', 'sc', 'mp', 'm'), so I think they
have
to be preserved.
If you look in #updateAjaxAttributes() for your ajax behaviors in your
table cells you will probably notice that they have their own
attributes.

  Making it switchable (I think how Sven suggested) would be an
improvement -
we could leave it off by default, but provide a simple switch on a
per-container (or per-app) basis that would allow the dev to choose.

  Yes, it looks as an improvement.
Moving the current code to such implementation is easy.
The idea with plain JS solution I cannot visualize in my head yet.


  Regards
Nick

On Thu, Jul 11, 2013 at 4:59 AM, Martin Grigorov <
[email protected]
wrote:
Hi,

At https://github.com/apache/**wicket/compare/event-**
delegating-behavioryou<
https://github.com/apache/wicket/compare/event-delegating-behavioryou>
may see the diff between master and event-delegating-behavior
branches.
The latter provides a new AjaxEventBehavior (AEB) -

EventDelegatingBehavior

(EDB), that suppresses the JS event binding for all
AjaxEventBehaviors
for

a given event type (click, submit, change, ...) in the children

components

of the host component of EDB.

How EDB works:

- until now AjaxEventBehavior#renderHead() renders ondomready header
item
with JS snippet like:
Wicket.Ajax.ajax(**attributesObject);
In the new branch there is a check if some parent has EDB for the
event
type of this AEB, and if there is such then the AEB "donates" its
attributes to the EDB.

- EventDelegatingBehavior#**getCallbackScript() renders :
Wicket.Event.delegate('**edbComponentMarkupId', 'eventType',
edbAttributes,
childrenAttrsMap);

- when a delegated component fires its event (e.g. the user clicks
on an
AjaxLink) the event is handled by EDB's event handler. It extracts
the
markupId of the inner HTML element and fires Wicket.Ajax.Call with
the
specific attributes for the extracted inner element.

Pros:

- simple to use - just add EDB to a container component around your
Ajax
heavy component (e.g. repeater with many Ajax behaviors). See the
demo
app

at https://issues.apache.org/**jira/browse/WICKET-5267<
https://issues.apache.org/jira/browse/WICKET-5267>
-  faster JS execution
-- faster execution of the domready handler because there is just one
binding instead of N
-- faster reaction because the browser finds the event handler much

faster.

I wasn't able to prove this with numbers because there is no way to

detect

the 'start time', i.e. when the user makes the action. With JS the

earliest

point is when the browser has already looked up the event handler.
Chrome Dev tools (timeline, profiling, pagespeed) don't help too. So
my
reference that it is faster are the articles in the web and a use
case
in
our application.

Cons:

- AEB#renderHead() needs to check whether there is EDB up in the

hierarchy

to be able to decide what to do.
This is ugly, I agree. But I see no other solution that will preserve
the
transparent usage of something like EDB and will not require a major
rewrite of user applications to be able to use event delegation.
-- there are some optimizations to lower the impact of the new
checks:
--- a new setting (IAjaxSettings#**useEventDelegation) - a global
property
that prevents visiting the parent components and their behaviors for
all
apps which do not use EDB
--- when EDB is bound it registers a metadata for its event type in
the
page instance. This prevents visiting all behaviors of all parent
components


I have no more ideas how to further optimize it.

Any feedback is welcome! Even if you have a completely different idea
how
to implement this functionality.

Thanks for reading!



Reply via email to