Repository: holoviz/panel

Issue:
FileDownload working incorrectly inside Tabs when `dynamic=True`


#### ALL software version info
python: 3.9.16
panel: 0.14.2
param: 1.12.3


#### Description of expected behavior and the observed behavior

When creating  FileDownload widget with a callback function inside a dynamic Tab, the widget stops using the callback if the user changes to a different tab. The bug does not happen if `dynamic=False`.

#### Complete, minimal, self-contained example code that reproduces the issue

``` python
from io import StringIO
import panel as pn
import param

class DownloadTab(param.Parameterized):
    file_contents = param.String("lorem ipsum")

    def __init__(self, **params):
        super().__init__(**params)

        self.file_download = pn.widgets.FileDownload(
            callback=self.create_file, filename='f.txt',
            embed=False
        )
    
    def create_file(self):
        bio = StringIO()
        bio.write(self.file_contents)
        bio.seek(0)
        return bio


    def view(self):
        return pn.Column(
            self.param.file_contents,
            self.file_download
        )

if __name__.startswith("bokeh"):
    download_tab = DownloadTab()
    
    app = pn.template.BootstrapTemplate()
    app.main.append(
        pn.Tabs(
            ("Download", download_tab.view),
            ("Dummy", "dummy tab"),
            dynamic=True
        )
    )
    app.servable()

```

#### Stack traceback and/or browser JavaScript console output

A link seems to get attached to the element

```html
<a class="bk-btn bk-btn-default" href="blob:http://localhost:5006/dbc15e4c-06a9-4851-8092-2216fd9d64c3" download="f.txt">Download f.txt</a>
```

#### Screenshots or screencasts of the bug in action

https://user-images.githubusercontent.com/14357967/214595638-7e0f5951-500f-4aa8-bd31-76aaa4929bdf.mp4




Discussion:
Hi @guilhermebs 

As an *orsome* work around you can insert a new `file_download` when the `Tab` changes.

https://user-images.githubusercontent.com/42288570/214629323-9d731ad4-b8f9-4ee0-af09-e29cb3c12648.mp4

```python
from io import StringIO
import panel as pn
import param

class DownloadTab(param.Parameterized):
    file_contents = param.String("lorem ipsum")
    updates = param.Integer()

    def __init__(self, **params):
        super().__init__(**params)

    def create_file(self):
        bio = StringIO()
        bio.write(self.file_contents)
        bio.seek(0)
        return bio

    @pn.depends("updates")
    def view(self):
        print("view")
        file_download = pn.widgets.FileDownload(
            callback=self.create_file, filename='f.txt',
            embed=False
        )
        return pn.Column(
            self.param.file_contents,
            file_download
        )

    def reset_callback_hack(self, _):
        self.updates +=1
        print("reset")

if __name__.startswith("bokeh"):
    download_tab = DownloadTab()
    
    app = pn.template.BootstrapTemplate()
    tabs = pn.Tabs(
            ("Download", download_tab.view),
            ("Dummy", "dummy tab"),
            dynamic=True
        )
    pn.bind(download_tab.reset_callback_hack, tabs.param.active, watch=True)
    app.main.append(
        tabs
    )
    app.servable()
```
@MarcSkovMadsen works beautifully! Thanks!

Resolving patch:
diff --git a/panel/models/widgets.py b/panel/models/widgets.py
--- a/panel/models/widgets.py
+++ b/panel/models/widgets.py
@@ -157,6 +157,8 @@ class FileDownload(InputWidget):
 
     data = String(help="""Encoded URI data.""")
 
+    embed = Bool(False, help="""Whether the data is pre-embedded.""")
+
     label = String("", help="""The text label for the button to display.""")
 
     filename = String(help="""Filename to use on download""")
diff --git a/panel/widgets/misc.py b/panel/widgets/misc.py
--- a/panel/widgets/misc.py
+++ b/panel/widgets/misc.py
@@ -136,8 +136,7 @@ class FileDownload(Widget):
     }
 
     _rename: ClassVar[Mapping[str, str | None]] = {
-        'callback': None, 'embed': None, 'file': None,
-        '_clicks': 'clicks', 'name': 'title'
+        'callback': None, 'file': None, '_clicks': 'clicks', 'name': 'title'
     }
 
     _widget_type: ClassVar[Type[Model]] = _BkFileDownload


Test patch:
diff --git a/panel/tests/ui/widgets/test_misc.py b/panel/tests/ui/widgets/test_misc.py
new file mode 100644
--- /dev/null
+++ b/panel/tests/ui/widgets/test_misc.py
@@ -0,0 +1,63 @@
+import io
+import sys
+import tempfile
+import time
+
+import param
+import pytest
+
+from panel.io.server import serve
+from panel.layout import Column, Tabs
+from panel.widgets import FileDownload, TextInput
+
+pytestmark = pytest.mark.ui
+not_windows = pytest.mark.skipif(sys.platform=='win32', reason="Does not work on Windows")
+
+
+@not_windows
+def test_file_download_updates_when_navigating_between_dynamic_tabs(page, port):
+    text_input = TextInput(value='abc')
+
+    @param.depends(text_input.param.value)
+    def create_file(value):
+        return io.StringIO(value)
+
+    download = FileDownload(
+        callback=create_file, filename='f.txt', embed=False
+    )
+
+    tabs = Tabs(
+        ("Download", Column(text_input, download)),
+        ("Dummy", "dummy tab"),
+        dynamic=True
+    )
+
+    serve(tabs, port=port, threaded=True, show=False)
+
+    time.sleep(0.5)
+
+    page.goto(f"http://localhost:{port}")
+
+    with page.expect_download() as download_info:
+        page.click('a.bk-btn')
+
+    download = download_info.value
+    tmp = tempfile.NamedTemporaryFile(suffix='.txt')
+    download.save_as(tmp.name)
+    assert tmp.file.read().decode('utf-8') == 'abc'
+
+    page.click('.bk-tab:not(.bk-active)')
+    page.click('.bk-tab:not(.bk-active)')
+
+    page.click('.bk-input')
+
+    page.keyboard.type('def')
+    page.keyboard.press('Enter')
+
+    with page.expect_download() as download_info:
+        page.click('a.bk-btn')
+
+    download = download_info.value
+    tmp = tempfile.NamedTemporaryFile(suffix='.txt')
+    download.save_as(tmp.name)
+    assert tmp.file.read().decode('utf-8') == 'abcdef'