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

Trigger event on children when triggered on parent #7

Open
zkniebel opened this issue Feb 11, 2014 · 13 comments
Open

Trigger event on children when triggered on parent #7

zkniebel opened this issue Feb 11, 2014 · 13 comments

Comments

@zkniebel
Copy link

If showing or hiding an element with descendants, the events will fire on the target (i.e. the element being slid-up/down, hidden, shown, faded in/out, etc.) and each of its descendants. It would be better if the events were fired on the target element only, as it would better support the nesting of elements with similar functionality.

@zkniebel
Copy link
Author

As an example, I have a mobile site that includes a swiping feature for dragging content sections (slides) into view, from tabs on the side of the page. The script that provides this functionality initializes the slides by first calculating their default dimensions, and then sizing the swipable area accordingly. Whenever dynamic content is added or removed from a content section, that section's slide needs to be refreshed to update the slides dimensions, so that all of the content is visible. The showandtell.js plugin would be very useful if it could be bound to the addition or removal of any element inside a slide(".slide *"), so that dynamic content would trigger the refreshing of the slides.

@hypesystem
Copy link
Owner

@zkniebel I don't agree with your first comment. If this was implemented, hiding a parent element would not trigger on the child (but the child would still be hidden). For example:

<div id="parent">
  <div id="child">I want to know when I'm hidden</div>
</div>

If your changes were implemented, hiding the parent would not trigger an event on the child (which would be desirable in most cases).

I am not quite sure I understand your second comment. You want to know when a child is added to a parent? (As in parent.append(child) or similar?) This seems out of scope for the library.

@zkniebel
Copy link
Author

I see your point, and you may be right; this change may be out of scope for this project, depending on the desired implementation.

To clarify my example (second comment), what I am saying is that if you hide an element, then all of its descendants are hidden, as well. If one is to try to listen for just that top level parent hiding (so as to call some script that should only be called once or should only be called on the highest level element that was hidden - i.e. only the element that '.hide()' was called on), then the current implementation would not be effective.

In the current implementation, the code listens for any element that is hidden or shown, not just the element that ".hide()" or ".show()" (etc) was called on. On a second pass, I believe that this implementation is more correct than that which I suggested. However, I do believe that the change I suggested might be useful as a separate, or perhaps just a namespaced event. I have forked the repository and will look into implementing this in my spare time.

@hypesystem
Copy link
Owner

I definitely see the use case of being able to distinguish the activated element from other elements. I suggest you use the event arguments that can be passed at the time of triggering. It would be good with a general solution for all the supported events :)

@hypesystem hypesystem reopened this Feb 12, 2014
hypesystem added a commit that referenced this issue Feb 12, 2014
The events currently do not trigger on children
@hypesystem
Copy link
Owner

See the commit above: I added tests to check if children are triggered when an event happens on a parent. They actually don't at the moment!

Question is: should they? Imagine a DOM with 100 nested elements - this would be a lot of events to send (100)! Input welcome.

Maybe we should try and implement it and do benchmarking?

It is a great idea to still distinguish the ancestor of the event (the one it is originally called on).

@zkniebel
Copy link
Author

I am going to try to keep the majority of the discussion of this issue on this thread, I commented on the commit, suggesting a test with a delegated event handler, as well. It may be the case that a 'evt.stopPropogation' call will be needed to support these distinctions for delegated events.

I also agree that the functionality you suggested is worth implementing, and that benchmarking is going to be absolutely necessary. I will look into this more and get back.

@hypesystem
Copy link
Owner

Do you mean delegate as in $(...).delegate? (See: http://api.jquery.com/delegate/)?

This binds events to all the matching elements, so maybe your particular selector selects the children as well? :)

@zkniebel
Copy link
Author

Yes, but I prefer to use '.on ()' as it is the newer standard. For delegating with '.on ()', we bind to children via something of the form '$("ancestor").on("event","eventtarget",function(){...})'. My idea is to find a way to enable someone to use a universal handler, like '$("body").on ("topshow","*",function(){...})', with the event only firing on the top-level node.

hypesystem added a commit that referenced this issue Feb 18, 2014
These tests check if the "toplevel" anscestor has a particular argument given in its event arguments when triggered --- and that the children do NOT.
@hypesystem
Copy link
Owner

Alright, got it. I have written some sample tests that should veryfy this funcitonality.

I have used the event arguments to get the is_ancestor variable. I think this is preferable to adding three new events.

@mafar
Copy link

mafar commented Mar 5, 2016

I am having same issue here https://jsfiddle.net/bababalcksheep/1Loeh2pn/7/
Is this project still active ?

<div class="header" tabindex="1">
  <h1 class="heading">City Gallery</h1>
</div>

$('.heading').on('show hide', function(e) {
   console.log(e);
 });

$('.abc').on('click', function(e) {
   $('.header').toggle( );
});

I am hiding parent and expecting event on children too as parent is shown/hidden so are chidlren

@mafar
Copy link

mafar commented Mar 5, 2016

Here is my change. I have now
.on('parentShow parentHide', function(e) { working

https://jsfiddle.net/bababalcksheep/1Loeh2pn/8/

/**
 * jquery.hide-event.js
 * Currently supported hides: hide, fadeOut, slideUp, remove, toggle, fadeToggle, slideToggle,
 *     css (display), animate (height: hide, opacity: hide).
 * When a hide `h` is called, the event "hide" is triggered, with the additional parameter ["h"]. If it is
 * called as an action, the second parameter will be "action". If it is set as a CSS property it will be
 * "css".
 */
(function($) {

  function applyIfExistsOrNull(fun, self, args) {
    return typeof fun !== "undefined" ? fun.apply(self, args) : null;
  }

  function proxy$Functions(functionProxies) {
    $.each(functionProxies, function(old_function_name, proxy_settings) {
      var old_function = $.fn[old_function_name];

      $.fn[old_function_name] = function() {
        var pre_result = applyIfExistsOrNull(proxy_settings.pre, this, arguments);

        var result = old_function.apply(this, arguments);

        var postArguments = [result, pre_result, arguments];
        applyIfExistsOrNull(proxy_settings.post, this, postArguments);

        return result;
      }
    });
  }

  function checkIfElementWasHidden() {
    return $(this).is(":hidden");
  }

  //Only register if not already registered
  if (!$.showandtell) {

    //Indicate that showandtell has been registered.
    $.showandtell = true;

    proxy$Functions({
      hide: {
        pre: checkIfElementWasHidden,
        post: function(result, elementWasHidden, old_args) {
          if (!elementWasHidden) {
            //
            this.triggerHandler("hide", {
              type: "action"
            });
            //
            this.find('*').trigger("parentHide");
          }

        }
      },
      show: {
        pre: checkIfElementWasHidden,
        post: function(result, elementWasHidden, old_args) {
          if (elementWasHidden) {
            //
            this.triggerHandler("show", {
              type: "action"
            });
            //
            this.find('*').trigger("parentShow");
            //
          }

        }
      },
      remove: {
        //Remove has to trigger event before removing. After removal, there is no element to
        //  trigger the event on.
        pre: function() {
          this.triggerHandler("remove", {
            type: "action"
          });
        }
      },
      css: {
        pre: checkIfElementWasHidden,
        post: function(result, elementWasHidden, old_args) {
          if (!elementWasHidden && old_args[0] == "display" && old_args[1] == "none")
            $(this).triggerHandler("hide", {
              type: "css"
            });

          if (elementWasHidden && old_args[0] == "display" && old_args[1] != "none")
            $(this).triggerHandler("show", {
              type: "css"
            });
        }
      }
    });

  }
})(jQuery);


//

$('.header').on('show hide', function(e) {
  console.log(e);
});
//
$('.heading3').on('parentShow parentHide', function(e) {
  console.log('-------------------');
  console.log(e);
  console.log(this);
  console.log('-------------------');
});
//
$('.abc').on('click', function(e) {
  $('.header').toggle();
});

@mafar
Copy link

mafar commented Mar 5, 2016

My tests showed that find(*) is not good and could trigger events too many times even on same element when there are filled tables with lots of rows tds etc and many divs inside parent element.

So I just adopted to class .show-tell
All child elements with .show-tell class will only get parentShow and parentHide `event only
making it work exactly work as i wanted

/**
 * jquery.hide-event.js
 * Currently supported hides: hide, fadeOut, slideUp, remove, toggle, fadeToggle, slideToggle,
 *     css (display), animate (height: hide, opacity: hide).
 * When a hide `h` is called, the event "hide" is triggered, with the additional parameter ["h"]. If it is
 * called as an action, the second parameter will be "action". If it is set as a CSS property it will be
 * "css".
 */
(function ($) {

  function applyIfExistsOrNull(fun, self, args) {
    return typeof fun !== "undefined" ? fun.apply(self, args) : null;
  }

  function proxy$Functions(functionProxies) {
    $.each(functionProxies, function (old_function_name, proxy_settings) {
      var old_function = $.fn[old_function_name];

      $.fn[old_function_name] = function () {
        var pre_result = applyIfExistsOrNull(proxy_settings.pre, this, arguments);

        var result = old_function.apply(this, arguments);

        var postArguments = [result, pre_result, arguments];
        applyIfExistsOrNull(proxy_settings.post, this, postArguments);

        return result;
      }
    });
  }

  function checkIfElementWasHidden() {
    return $(this).is(":hidden");
  }

  //Only register if not already registered
  if (!$.showandtell) {

    //Indicate that showandtell has been registered.
    $.showandtell = true;

    proxy$Functions({
      hide: {
        pre: checkIfElementWasHidden,
        post: function (result, elementWasHidden, old_args) {
          if (!elementWasHidden) {
            //
            this.triggerHandler("hide", {
              type: "action"
            });
            //
            this.find('.show-tell').trigger("parentHide");
          }

        }
      },
      show: {
        pre: checkIfElementWasHidden,
        post: function (result, elementWasHidden, old_args) {
          if (elementWasHidden) {
            //
            this.triggerHandler("show", {
              type: "action"
            });
            //
            this.find('.show-tell').trigger("parentShow");
            //
          }

        }
      },
      remove: {
        //Remove has to trigger event before removing. After removal, there is no element to
        //  trigger the event on.
        pre: function () {
          this.triggerHandler("remove", {
            type: "action"
          });
          this.find('*').trigger("parentRemove");
        }
      },
      css: {
        pre: checkIfElementWasHidden,
        post: function (result, elementWasHidden, old_args) {
          if (!elementWasHidden && old_args[0] == "display" && old_args[1] == "none") {
            $(this).triggerHandler("hide", {
              type: "css"
            });
            $(this).find('.show-tell').trigger("parentHide");
          }
          if (elementWasHidden && old_args[0] == "display" && old_args[1] != "none") {
            $(this).triggerHandler("show", {
              type: "css"
            });
            $(this).find('.show-tell').trigger("parentShowCSS");
          }

        }
      }
    });

  }
})(jQuery);

@hypesystem
Copy link
Owner

Hey @djangosdk, I would love a Pull Request with the changes you are proposing 😄

I haven't personally been using this library for quite a while, but I am open to reviewing some proposed updates and perhaps reviving it 😛

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

3 participants