Blur background on modal open - working example & feedback welcome

Hi all,
A gentle blur + opacity drop to the background content seems to do a nice job of focusing users’ attention on a modal window, i.e. this example.

Unless I missed something, Panel Templates don’t provide this option out-of-the-box, so I found a way to add this, which you can see a video clip of here: https://twitter.com/charlesstern/status/1353829953410715654?s=20. Technical summary follows:

First, I define and include a blurred CSS class (this within the .py file which contains my servable object):

import panel as pn
css = '''
.blurred {
   filter: blur(2px) opacity(40%);
}
'''
pn.config.raw_css.append(css)
...

Then, I modified Bokeh’s file.html template as follows: https://gist.github.com/cisaacstern/0094946216ce1b68b15258fbb18fc16d

On my MacBook running a conda env, the original Bokeh file.html (which I replaced with the modified version) resides here:

~/.pyenv/versions/anaconda3-2019.10/envs/terrain_env/lib/python3.7/site-packages/bokeh/core/_templates/file.html

(Definitely a good idea to modify a Bokeh install within a virtual env, rather than a base install! EDIT: Disclaimer: this may very well break your Bokeh install if you try to use it to serve any other template, so definitely do this in a distinct virtual environment or not at all. :slight_smile: )

Those more experienced in JS than me (i.e. most everyone) will surely find room for improvement with the script at the bottom of that template. For example: rather than delaying the JS addEventListener call, it would be more elegant to trigger it on completion of the BokehJS render. Also, there must be a better way to make the event listener persist, as opposed to resetting it on every click event, as I do!

Finally, note that while this could be adapted for other templates, I’m using the React Template and the script assumes that there is only a single button in the sidebar area.

Any and all feedback/criticism welcome and happy to answer questions if others are working on something similar.

Best,
Charles

2 Likes

Nice functionality !!!

I tried and it is great the focus in the modal content :slight_smile: !!!

I am not confortable with changing template files of bokeh. Then I add the JS code in a html pane to the template. For completeness the code is below.

import panel as pn
import holoviews as hv 
css = '''
.blurred {
   filter: blur(2px) opacity(40%);
}
'''
pn.config.raw_css.append(css)


js = """

<script type='text/javascript'>

  function addBlur(){
    closeNav()
    document.getElementById("responsive-grid").className = "blurred";
    document.getElementById("header").className = "blurred";
    // reset the close listener
    var close = document.getElementById("pn-closeModal");
    close.addEventListener('click', unBlur);
  }

  function unBlur(){
    openNav()
    document.getElementById("responsive-grid").className = "";
    document.getElementById("header").className = "";
    // reset the open listener
    var open = document.getElementById("sidebar").getElementsByTagName("button");
    open[0].addEventListener('click', addBlur);

  }

  function delay() {
    setTimeout(function() {
        var open = document.getElementById("sidebar").getElementsByTagName("button");
        var close = document.getElementById("pn-closeModal");
        open[0].addEventListener('click', addBlur);
        close.addEventListener('click', unBlur);
    }, 200);
  }

  if (document.readyState == 'complete') {
      delay();
  } else {
      document.onreadystatechange = function () {
          if (document.readyState === "complete") {
              delay();
          }
      }
  }

</script>
"""

# with this html pane the js it is added to the template 
ht = pn.pane.HTML(js)





react = pn.template.ReactTemplate(title='Material Template')

pn.config.sizing_mode = 'stretch_width'

xs = np.linspace(0, np.pi)
freq = pn.widgets.FloatSlider(name="Frequency", start=0, end=10, value=2)
phase = pn.widgets.FloatSlider(name="Phase", start=0, end=np.pi)

@pn.depends(freq=freq, phase=phase)
def sine(freq, phase):
    return hv.Curve((xs, np.sin(xs*freq+phase))).opts(
        responsive=True, min_height=400)

@pn.depends(freq=freq, phase=phase)
def cosine(freq, phase):
    return hv.Curve((xs, np.cos(xs*freq+phase))).opts(
        responsive=True, min_height=400)

react.sidebar.append(freq)
react.sidebar.append(phase)

react.main[:2,:12] = pn.Row(
                            pn.Card(hv.DynamicMap(sine), title='Sine'),
                            pn.Card(hv.DynamicMap(cosine), title='Cosine')
                        )




react.modal.append(pn.Card(hv.DynamicMap(sine), title='Sine'))

btn = pn.widgets.Button(name='Blur Modal')

def opene(e):
    react.open_modal()

btn.on_click(opene)

react.sidebar.append(btn)

react.sidebar.append(ht)

react.show()
1 Like

This is an even cleaner and more reproducible implementation for others to try, @nghenzi !

Awesome!! :blush: