Repository: bokeh/bokeh

Issue:
`save` creates non-working files when used with widgets
Bokeh v0.12.15dev2

```python
from bokeh.document import Document
from bokeh.models import Button
from bokeh.plotting import figure, save, output_file

fig = figure()
fig.x([0], [0])

button = Button(label="Button")

d = Document()
d.add_root(fig)
d.add_root(button)

output_file('fig.html')
save(fig)
```

Now, if you open `fig.html` in your browser, you should see `Uncaught Error: Model 'Button' does not exist.`

The reason is that `bokeh.embed.bundle.bundle_for_objs_and_resources` checks just the passed models for widgets (`fig` in this case), but `bokeh.embed.util.standalone_docs_json_and_render_items` saves the whole document JSON.

It's hard for me to say how it should work ideally. Most likely, there should be some common function used by `save` that extracts only relevant models from a document, and only those models are checked for widgets and bundled as a JSON.



Discussion:
@p-himik What is the use case for ever doing this? I'm more inclined to just stipulate that this sort of thing is undefined and unsupported, unless there is a compelling motivation. 

> Most likely, there should be some common function used by save that extracts only relevant models from a document, and only those models are checked for widgets and bundled as a JSON.

It seems possible that `save` could look up any other roots on a model's associated document. It's also possible that the passed in models could be temp "re-homed" in a blank doc. That's a bit hacky but we have functions for doing it under test. 
I would argue this is a bug, because even if `save()` is called with a particular root, there may be some code (e.g. CustomJS) referencing other roots, so it should be saving entire document with all its dependencies.
In fact if you replace widgets with (still not implemented) namespaces, then this makes even more sense.
@bryevdv At the very least, current behavior prevents code like this from running correctly: https://gist.github.com/AzraelDD/c4573f531fbc52cf5d0811d81d30a3fa
I'd say that if you have a bunch of widgets, then exporting as PNG only a figure or a set of figures, without the widgets, is an understandable desire.
OK, that makes sense. 
> Most likely, there should be some common function used by save that extracts only relevant models from a document, and only those models are checked for widgets and bundled as a JSON.

In the short term at least, it seems much safer to go the other direction. This tiny diff solves the problem by always bundling the entire document:

```
index 7f6e76308..fc4c31b78 100644
--- a/bokeh/embed/standalone.py
+++ b/bokeh/embed/standalone.py
@@ -275,10 +275,10 @@ def file_html(models,
     '''
     models = check_models_or_docs(models)

-    with _ModelInDocument(models, apply_theme=theme):
+    with _ModelInDocument(models, apply_theme=theme) as doc:
         (docs_json, render_items) = standalone_docs_json_and_render_items(models)
         title = _title_from_models(models, title)
-        bundle = bundle_for_objs_and_resources(models, resources)
+        bundle = bundle_for_objs_and_resources([doc], resources)
         return html_page_for_render_items(bundle, docs_json, render_items, title=title,
                                            template=template, template_variables=template_variables)
```

I agree that could probably be improved but I think I'd rather make a small safe change now, even if it is not the most efficient solution, and leave a more sophisticated change for later work. Thoughts @p-himik @mattpap ?
I would argue that this is the right solution. In fact, I've been battling with `models` vs `doc` dichotomy in templates PR (#7708) and the best solution so far is to always work with complete documents and use `models` for templating purpose only.
@mattpap OK, should I make this small PR or wait for you to finish?
for testing, with this change, the following in app exports work:
```
# -*- coding: utf-8 -*-
'''
After the layout has been added to the root, exporting the png of a fig is
not possible anymore
With "bokeh serve export_test.py", the console output for me is the following:
doc_layout works
fig works
Layout is added to root
doc_layout works
fig throws error
'''
from bokeh.io import curdoc
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure
from bokeh.layouts import layout, widgetbox
from bokeh.io import export_png
from bokeh.models.widgets import Button


def download():
    try:
        export_png(doc_layout, filename="export doc_layout.png")
        print('doc_layout works')
    except Exception as ex:
        print('doc_layout throws error:', ex)
    try:
        export_png(fig, filename="export fig.png")
        print('fig works')
    except Exception as ex:
        print('fig throws error:', ex)


source = ColumnDataSource({'x': [2, 3, 4], 'y': [0.5, 1.5, 2.5]})
fig = figure(plot_width=1000, plot_height=300)
fig.hbar(y='y', right='x', height=0.4, source=source)

button = Button(label="Download")
button.on_click(download)
doc_layout = layout([[widgetbox(button)], [fig]], sizing_mode='fixed')

#download()  # Test 1

print('Layout is added to root')
curdoc().add_root(doc_layout)
curdoc().title = "Export PNG test"

#download()  # Test 2
```
despite a spurious warning:
```
2018-04-23 14:08:25,065 The webdriver raised a TimeoutException while waiting for                      a 'bokeh:idle' event to signify that the layout has rendered.                      Something may have gone wrong.
```
>  OK, should I make this small PR or wait for you to finish?

I will finish this as this intersects with other changes.
@mattpap is this fixed in the templates PR? A fix for this should go in this week, even if we have to punt on templates this release. 
@bryevdv, no, if you want, go ahead with the proposed change. In fact, it should have been done separately anyway.
OK I will push a PR later today

Resolving patch:
diff --git a/bokeh/embed/standalone.py b/bokeh/embed/standalone.py
--- a/bokeh/embed/standalone.py
+++ b/bokeh/embed/standalone.py
@@ -275,10 +275,10 @@ def file_html(models,
     '''
     models = check_models_or_docs(models)
 
-    with _ModelInDocument(models, apply_theme=theme):
+    with _ModelInDocument(models, apply_theme=theme) as doc:
         (docs_json, render_items) = standalone_docs_json_and_render_items(models)
         title = _title_from_models(models, title)
-        bundle = bundle_for_objs_and_resources(models, resources)
+        bundle = bundle_for_objs_and_resources([doc], resources)
         return html_page_for_render_items(bundle, docs_json, render_items, title=title,
                                            template=template, template_variables=template_variables)
 


Test patch:
diff --git a/bokeh/embed/tests/test_standalone.py b/bokeh/embed/tests/test_standalone.py
--- a/bokeh/embed/tests/test_standalone.py
+++ b/bokeh/embed/tests/test_standalone.py
@@ -237,6 +237,23 @@ def test_file_html_title_is_escaped(self, test_plot):
         r = bes.file_html(test_plot, CDN, "&<")
         assert "<title>&amp;&lt;</title>" in r
 
+    def test_entire_doc_is_used(self):
+        from bokeh.document import Document
+        from bokeh.models import Button
+
+        fig = figure()
+        fig.x([0], [0])
+
+        button = Button(label="Button")
+
+        d = Document()
+        d.add_root(fig)
+        d.add_root(button)
+        out = bes.file_html([fig], CDN)
+
+        # this is a very coarse test but it will do
+        assert "bokeh-widgets" in out
+
 #-----------------------------------------------------------------------------
 # Dev API
 #-----------------------------------------------------------------------------