简体   繁体   中英

What is the best way to deal with elements/objects that listen AND trigger events?

I have asked some way similar question yesterday, but after one day of research I've found it too complicated and not precise enough, so I deleted it.

So I have created another fiddle here , which should be self-descriptive in most parts.

Below, you can find a HTML and JavaScript part of it:

<select name="country">
    <option value="spain">Spain</option>
    <option value="sweden">Sweden</option>
    <option value="russia">Russia</option>
    <option value="canada">Canada</option>
</select>

<ul class="another-selector">
    <li data-value="spain">Spain</li>
    <li data-value="sweden">Sweden</li>
    <li data-value="russia">Russia</li>
    <li data-value="canada">Canda</li>
</ul>

And the promised Javascript goes here:

// Following function is called when 'country_changed' event is triggered
var country_changed = function(event, data) {
    $another_selector_items = $('.another-selector')
        .children();

    $another_selector_items
        .removeClass('selected');

    $another_selector_items.each(function(index, element) {
        var $element = $(element);
        if ($element.data('value') == data.country)
            $element.addClass('selected');
    });

    var $select = $('select[name=country]');
    $select
        .children()
        .removeAttr('selected')
        .prop('selected', false)
        .filter('[value=' + data.country + ']')
        .attr('selected', 'selected')
        .prop('selected', true);

    // ----------------------------------------
    // Uncomment this to get infinite recursion:
    // ----------------------------------------
    //$select.trigger('change');
};

// Register event bound to the document
// (so not attached to any particular element)
$(document)
    .on("country_changed", country_changed);


// Bind changing the
$('select[name=country]').on('change', function(event) {
    $.event.trigger('country_changed', {
        country: $(this).val()
    });
});

// Bind clicking on the LI to triggering the event 
$('.another-selector').on('click', 'li', function(event) {
    $.event.trigger('country_changed', {
        country: $(this).data('value')
    });
});

// Separate 'change' event binding --- required for 'pretty select' plugin
$('select[name=country]').on('change', function(event, data) {
    // ...
});

The thing is, that I do not know what is the best approach to situations like this, when elements are both: listeners to the events AND have ability to TRIGGER the same events. So, for example selecting an option from a native SELECT element causes event to trigger and select appropriate LI element on the other list. But as the event has to deal also with vice-versa situation, it also tries to update the SELECT again, which is useless and - what's even worse - causes an infinite resursion (hopefully stopped by smart browsers).

Try to uncomment #46 line of JavaScript code in my fiddle to see what I am talking about.

So what I'm looking for is a nice way of detecting where the event came from, and then excluding it's source from the list of possible callbacks.

OR - maybe I'm trying to solve the problem in using wrong approach? Maybe I have read not enough for two days I've already spend on this? ;)

Please help me if you can.

I would separate UI generated events from the ones triggered from code. Also, there is no need for custom events and 2 change handlers on SELECT . Also, there is no need to re-trigger change event from your custom event.

My suggestion for your problem is something like:

$('select[name=country]').on('change', function(event) {
    var country = ... /* obtain country */

    selectContry(country);
});

$('.another-selector').on('click', 'li', function(event) {
    var country = ... /* obtain country */

    selectContry(country);
});

function selectContry(country) {
    // update view, e.g. SELECT and UL
    // ...

    // process new selection
    // ...
}

In short: keep your UI event handlers short (identify context, ie data related to the event). The handler should then make a call to utility method (if appropriate via custom event) updating the view (if necessary) and processing the data.

If further events are required, do not (re)trigger the same ones as this is now new situation, the program state changed and different event (eg after_x ) is more appropriate.

If pretty select plugin bothers you, as part of updating the view do either:

  1. call: update method of prettyselect plugin: $('select[name=country]').prettyselect('update')
  2. trigger change.ps event on select : $('select[name=country]').trigger('change.ps')

Take a look at the source and see how it all works.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM