Shiny doesn't bind to elements that are 'hidden' when first initializing

Showing 1-6 of 6 messages
Shiny doesn't bind to elements that are 'hidden' when first initializing Kevin Ushey 10/4/13 10:58 AM
This is a crosspost from StackOverflow here: http://stackoverflow.com/questions/19166322/shiny-with-html-ui-limited-by-javascript-onload/19177028?noredirect=1#comment28388537_19177028; although there's a workaround, it seems like this is still a bug, or at least unexpected behavior.

It looks like, if an element is hidden when the DOM is loaded, then messages from that binding will not passed around (even though the binding is generated). This status doesn't get reset even if the element is later re-displayed.

Starting on line 2618 of shiny.js:

    // Return true if the object or one of its ancestors in the DOM tree has
    // style='display:none'; otherwise return false.
    function isHidden(obj) {
      // null means we've hit the top of the tree. If width or height is
      // non-zero, then we know that no ancestor has display:none.
      if (obj === null || obj.offsetWidth !== 0 || obj.offsetHeight !== 0) {
        return false;
      } else if (getStyle(obj, 'display') === 'none') {
        return true;
      } else {
        return(isHidden(obj.parentNode));
      }
    }
    var lastKnownVisibleOutputs = {};
    // Set initial state of outputs to hidden, if needed
    $('.shiny-bound-output').each(function() {
      if (isHidden(this)) {
        initialValues['.clientdata_output_' + this.id + '_hidden'] = true;
      } else {
        lastKnownVisibleOutputs[this.id] = true;
        initialValues['.clientdata_output_' + this.id + '_hidden'] = false;
      }
    });
    // Send update when hidden state changes
    function sendOutputHiddenState() {
      var visibleOutputs = {};
      $('.shiny-bound-output').each(function() {
        delete lastKnownVisibleOutputs[this.id];
        // Assume that the object is hidden when width and height are 0
        if (isHidden(this)) {
          inputs.setInput('.clientdata_output_' + this.id + '_hidden', true);
        } else {
          visibleOutputs[this.id] = true;
          inputs.setInput('.clientdata_output_' + this.id + '_hidden', false);
        }
      });
      // Anything left in lastKnownVisibleOutputs is orphaned
      for (var name in lastKnownVisibleOutputs) {
        if (lastKnownVisibleOutputs.hasOwnProperty(name)) {
          inputs.setInput('.clientdata_output_' + name + '_hidden', true);
        }
      }
      // Update the visible outputs for next time
      lastKnownVisibleOutputs = visibleOutputs;
    }

It seems like, when loading, this .clientdata_output_<foo>_hidden flag gets set to true, and it does not get unset even if the element is later displayed through 'standard' JavaScript (e.g. $("#foo").show())

Although 'outputOptions(output, 'x', suspendWhenHidden=FALSE)' fixes it, it seems more like a workaround than a real solution...

Hopefully someone on the Shiny team has comments / a solution.
Re: Shiny doesn't bind to elements that are 'hidden' when first initializing Yihui Xie 10/7/13 9:07 PM
Hi Kevin,

This is a good question. The problem here, as you said, is Shiny does
not refresh the visibility status of output elements if you manipulate
their visibility manually. The reason is when you call .show() or
.hide(), the function sendOutputHiddenState() is not triggered at all;
what really triggers it is $('body').on('shown.sendOutputHiddenState
hidden.sendOutputHiddenState') (see the next couple of lines in
shiny.js).

To solve this problem without hacking at suspendWhenHidden=FALSE, you
can trigger the internal events shown/hidden manually, e.g.

function showFoo(){
    $('#foodiv').show();
    $('#bardiv').hide().trigger('hidden');
}

function showBar(){
    $('#foodiv').hide();
    $('#bardiv').show().trigger('shown');
}

Then Shiny will be aware of the real visibility of #bardiv.

Perhaps what I said is a bad idea and Joe can share more wisdom on this issue.

Regards,
Yihui
--
Yihui Xie <yi...@rstudio.com>
Web: http://yihui.name
> --
> You received this message because you are subscribed to the Google Groups
> "Shiny - Web Framework for R" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to shiny-discus...@googlegroups.com.
> For more options, visit https://groups.google.com/groups/opt_out.
Re: Shiny doesn't bind to elements that are 'hidden' when first initializing Guillem Vicens 10/7/13 11:33 PM
Hi,

I am the one that asked in Stackoverflow about this issue. I might be misunderstanding you but you seem to imply this issue should happen every time you use .hide() or .show(). That is not exactly what is happening.

  • If I do not call .hide() in the OnShow event, afterwards I can call both .show() and .hide() as much as I want. Shiny will continue to react to inputs.
  • Only if I do call .hide() in the OnShow event Shiny stops to react to the hidden input, regardless of whether I use .show() or not.

El martes, 8 de octubre de 2013 06:07:56 UTC+2, Yihui Xie escribió:
Hi Kevin,

This is a good question. The problem here, as you said, is Shiny does
not refresh the visibility status of output elements if you manipulate
their visibility manually. The reason is when you call .show() or
.hide(), the function sendOutputHiddenState() is not triggered at all;
what really triggers it is $('body').on('shown.sendOutputHiddenState
hidden.sendOutputHiddenState') (see the next couple of lines in
shiny.js).

<snip>
Re: Shiny doesn't bind to elements that are 'hidden' when first initializing Winston Chang 10/8/13 8:40 AM

Although 'outputOptions(output, 'x', suspendWhenHidden=FALSE)' fixes it, it seems more like a workaround than a real solution...


The short answer is that `outputOptions(output, 'x', suspendWhenHidden=FALSE)` is the solution - it's not a workaround. It's possible that in the future, there will be an option to control the behavior from the client side.

-Winston
 
Re: Shiny doesn't bind to elements that are 'hidden' when first initializing Yihui Xie 10/8/13 9:04 AM
Well, it all depends on the initial status:

- if an element is hidden initially, shiny will _always_ ignore it,
even if you .show() it later
- if an element is shown initially, shiny will always use it, even if
you .hide() it later

What I said was, it is not enough to call .show()/.hide() -- you have
to trigger the internal events "shown"/"hidden" of shiny, so that
shiny can refresh its memory about the visibility of that element.

outputOptions(output, 'x', suspendWhenHidden=FALSE) can solve the
problem, but it depends on what you really want: do you want the
hidden elements to react or not? If you do want them to react, then
use suspendWhenHidden=FALSE; if you want to forget about hidden
elements, you need to trigger the shown/hidden events after you
.show()/.hide().

Regards,
Yihui
--
Yihui Xie <yi...@rstudio.com>
Web: http://yihui.name


Re: Shiny doesn't bind to elements that are 'hidden' when first initializing Guillem Vicens 10/8/13 11:26 PM
ok, that makes total sense. Thanks for the information, Yihui!