Repository: bokeh/bokeh

Issue:
Inline, Minified Resources do not work in classic notebooks
This is due to an interaction with the classic notebooks use of JQuery, when output is published as `text/html`. New notebook code published a div and a script together as `text/html`. Propose to solve by publishing a single script as `application/javascript` (which should work) that creates the necessary div itself 


Discussion:
> Propose to solve by publishing a single script as application/javascript (which should work) that creates the necessary div itself

This is how it should be done and `<div>` creation is something that embed usually handles.
> something that embed usually handles.

The entire previous machinery had to be ripped up to accommodate JLab, I am not surprised there are regressions, especially given that we have no reliable ways to end-to-end test the notebook integration. 
@mattpap @canavandl  here is the start of a proposed path to solving. I am not sure what needs to go where the caps comment is (mostly because in this context I don't know offhand how to get ahold of the output are to add the div too.
```
diff --git a/bokeh/util/notebook.py b/bokeh/util/notebook.py
index ddb23004e..ec02f13f3 100644
--- a/bokeh/util/notebook.py
+++ b/bokeh/util/notebook.py
@@ -7,6 +7,7 @@ from IPython.display import publish_display_data

 from ..embed import _wrap_in_script_tag

+JS_MIME_TYPE   = 'application/javascript'
 LOAD_MIME_TYPE = 'application/vnd.bokehjs_load.v0+json'
 EXEC_MIME_TYPE = 'application/vnd.bokehjs_exec.v0+json'

@@ -40,11 +41,13 @@ def load_notebook(resources=None, verbose=False, hide_banner=False, load_timeout
         None

     '''
-    nb_html, nb_js = _load_notebook_html(resources, verbose, hide_banner, load_timeout)
-    lab_html, lab_js = _load_notebook_html(resources, verbose, hide_banner, load_timeout, register_mimetype=False)
+    classic_script = _classic_load_script(resources, verbose, hide_banner, load_timeout)
+    jlab_mime_data = _jlab_load_mime_data(resources, verbose, hide_banner, load_timeout)
     if notebook_type=='jupyter':
-        publish_display_data({'text/html': nb_html + _wrap_in_script_tag(nb_js),
-                              LOAD_MIME_TYPE: {"script": lab_js, "div": lab_html}})
+        publish_display_data({
+            JS_MIME_TYPE   : classic_script,
+            LOAD_MIME_TYPE : jlab_mime_data
+        })
     else:
         _publish_zeppelin_data(lab_html, lab_js)

@@ -58,6 +61,14 @@ def _publish_zeppelin_data(html, js):
     print('%html ' + html)
     print('%html ' + '<script type="text/javascript">' + js + "</script>")

+def _classic_load_script(resources=None, verbose=False, hide_banner=False, load_timeout=5000):
+    # THIS FUNCTION NEEDS TO RETURN A SINGLE JS STRING THAT ALSO ADDS THE DIV IN HTML
+    html, js = _load_notebook_html(resources, verbose, hide_banner, load_timeout, register_mimetype=True)
+
+def _jlab_load_mime_data(resources=None, verbose=False, hide_banner=False, load_timeout=5000):
+    html, js = _load_notebook_html(resources, verbose, hide_banner, load_timeout, register_mimetype=False)
+    return {"script": js, "div": html}
+
 def _load_notebook_html(resources=None, verbose=False, hide_banner=False,
                         load_timeout=5000, register_mimetype=True):
     global _notebook_loaded
~/work/bokeh/bokeh master*
```
I guess another option is to simply punt and stop adding the loading message entirely. 
If I just try to publish the JS and leave off the div as an experiment, I am see this error, which another user is reporting with rc1:

https://gist.github.com/nleclercq/a83d678e5444b9bf98195a8abd8c3bc4

```
events.js:33 Exception in event handler for output_added.OutputArea TypeError: Cannot read property 'id' of undefined
    at window._Events.handleAddOutput (eval at globalEval (jquery.min.js:4), <anonymous>:72:63)
```

I'm not sure why just on output notebook, it is *calling* `handleAddOutput` instead of just *defining* it
Well, it's getting called because of this:
<img width="817" alt="screen shot 2017-08-24 at 15 56 03" src="https://user-images.githubusercontent.com/1078448/29688278-ce4b6ab6-88e4-11e7-94d4-61c564185de8.png">

I guess its managing to execute the `application/javascript` before calling `output` which triggers the event which I presume is really only intended for `show` output cells

This is a quick-fix:
```
if (output.metadata[MIME_TYPE] !== undefined) {
  output_area._bokeh_element_id = output.metadata[MIME_TYPE]["id"];
  output_area._bokeh_server_id = output.metadata[MIME_TYPE]["server_id"];
}
```
 

I think a fuller solution will involve sending the div id of the div we want ot build as the metadata
Actually, we can just issue a separate `display_data` first with just the div HTML. That should actually simplify things. I have a partially working solution, I will have something concrete later/tomorrow morning. @mattpap @canavandl 
Here is a diff of a solution. I have not tried on JLAb, I think it might publish the div twice. Guessing we should just not have it publish the div at all in the plugin (but be resilient to a missing div)
```
diff --git a/bokeh/core/_templates/autoload_nb_js.js b/bokeh/core/_templates/autoload_nb_js.js
index 7ff6e4820..fe962eb17 100644
--- a/bokeh/core/_templates/autoload_nb_js.js
+++ b/bokeh/core/_templates/autoload_nb_js.js
@@ -62,8 +62,10 @@
     var output_area = handle.output_area;
     var output = handle.output;
     // store reference to embed id or server id on output_area
-    output_area._bokeh_element_id = output.metadata[MIME_TYPE]["id"];
-    output_area._bokeh_server_id = output.metadata[MIME_TYPE]["server_id"];
+    if (output.metadata[MIME_TYPE] !== undefined) {
+      output_area._bokeh_element_id = output.metadata[MIME_TYPE]["id"];
+      output_area._bokeh_server_id = output.metadata[MIME_TYPE]["server_id"];
+    }
   }

   function register_renderer(notebook, OutputArea) {
diff --git a/bokeh/util/notebook.py b/bokeh/util/notebook.py
index ddb23004e..5a2d4f94f 100644
--- a/bokeh/util/notebook.py
+++ b/bokeh/util/notebook.py
@@ -7,6 +7,7 @@ from IPython.display import publish_display_data

 from ..embed import _wrap_in_script_tag

+JS_MIME_TYPE   = 'application/javascript'
 LOAD_MIME_TYPE = 'application/vnd.bokehjs_load.v0+json'
 EXEC_MIME_TYPE = 'application/vnd.bokehjs_exec.v0+json'

@@ -40,32 +41,12 @@ def load_notebook(resources=None, verbose=False, hide_banner=False, load_timeout
         None

     '''
-    nb_html, nb_js = _load_notebook_html(resources, verbose, hide_banner, load_timeout)
-    lab_html, lab_js = _load_notebook_html(resources, verbose, hide_banner, load_timeout, register_mimetype=False)
-    if notebook_type=='jupyter':
-        publish_display_data({'text/html': nb_html + _wrap_in_script_tag(nb_js),
-                              LOAD_MIME_TYPE: {"script": lab_js, "div": lab_html}})
-    else:
-        _publish_zeppelin_data(lab_html, lab_js)
-
-
-FINALIZE_JS = """
-document.getElementById("%s").textContent = "BokehJS is loading...";
-"""
-
-# TODO (bev) This will eventually go away
-def _publish_zeppelin_data(html, js):
-    print('%html ' + html)
-    print('%html ' + '<script type="text/javascript">' + js + "</script>")

-def _load_notebook_html(resources=None, verbose=False, hide_banner=False,
-                        load_timeout=5000, register_mimetype=True):
     global _notebook_loaded

     from .. import __version__
-    from ..core.templates import AUTOLOAD_NB_JS, NOTEBOOK_LOAD
+    from ..core.templates import NOTEBOOK_LOAD
     from ..util.serialization import make_id
-    from ..util.compiler import bundle_all_models
     from ..resources import CDN

     if resources is None:
@@ -97,20 +78,57 @@ def _load_notebook_html(resources=None, verbose=False, hide_banner=False,
         hide_banner   = hide_banner,
     )

+    classic_script = _classic_load_script(resources, element_id, hide_banner, load_timeout)
+    jlab_mime_data = _jlab_load_mime_data(resources, element_id, hide_banner, load_timeout)
+    jlab_mime_data['div'] = html # hack for now
+    if notebook_type=='jupyter':
+        publish_display_data({'text/html': html})
+        publish_display_data({
+            JS_MIME_TYPE   : classic_script,
+            LOAD_MIME_TYPE : jlab_mime_data
+        })
+    else:
+        _publish_zeppelin_data(lab_html, lab_js)
+
+
+FINALIZE_JS = """
+document.getElementById("%s").textContent = "BokehJS is loading...";
+"""
+
+# TODO (bev) This will eventually go away
+def _publish_zeppelin_data(html, js):
+    print('%html ' + html)
+    print('%html ' + '<script type="text/javascript">' + js + "</script>")
+
+def _classic_load_script(resources, elementid, hide_banner=False, load_timeout=5000):
+    # THIS FUNCTION NEEDS TO RETURN A SINGLE JS STRING THAT ALSO ADDS THE DIV IN HTML
+    js = _load_notebook_html(resources, elementid, hide_banner, load_timeout, register_mimetype=True)
+    return js
+
+def _jlab_load_mime_data(resources, elementid, hide_banner=False, load_timeout=5000):
+    js = _load_notebook_html(resources, elementid, hide_banner, load_timeout, register_mimetype=False)
+    return {"script": js}
+
+def _load_notebook_html(resources, elementid, hide_banner=False,
+                        load_timeout=5000, register_mimetype=True):
+
+    from ..core.templates import AUTOLOAD_NB_JS
+    from ..util.compiler import bundle_all_models
+
     custom_models_js = bundle_all_models()

     js = AUTOLOAD_NB_JS.render(
-        elementid = '' if hide_banner else element_id,
+        elementid = '' if hide_banner else elementid,
         js_urls  = resources.js_files,
         css_urls = resources.css_files,
-        js_raw   = resources.js_raw + [custom_models_js] + ([] if hide_banner else [FINALIZE_JS % element_id]),
+        js_raw   = resources.js_raw + [custom_models_js] + ([] if hide_banner else [FINALIZE_JS % elementid]),
         css_raw  = resources.css_raw_str,
         force    = True,
         timeout  = load_timeout,
         register_mimetype = register_mimetype
     )

-    return html, js
+    return js

 def get_comms(target_name):
     ''' Create a Jupyter comms object for a specific target, that can
```
This is the salient point:
```
+    classic_script = _classic_load_script(resources, element_id, hide_banner, load_timeout)
+    jlab_mime_data = _jlab_load_mime_data(resources, element_id, hide_banner, load_timeout)
+    jlab_mime_data['div'] = html # hack for now
+    if notebook_type=='jupyter':
+        publish_display_data({'text/html': html})
+        publish_display_data({
+            JS_MIME_TYPE   : classic_script,
+            LOAD_MIME_TYPE : jlab_mime_data
+        })
```
The "hack for now" is where stick the div html back in the jlab mime data. I think we should just omit it and let the python side be responsible for publishing the div separately (and have the extension) just ignore/log a missing load status div)

Resolving patch:
diff --git a/bokeh/util/notebook.py b/bokeh/util/notebook.py
--- a/bokeh/util/notebook.py
+++ b/bokeh/util/notebook.py
@@ -5,8 +5,7 @@
 
 from IPython.display import publish_display_data
 
-from ..embed import _wrap_in_script_tag
-
+JS_MIME_TYPE   = 'application/javascript'
 LOAD_MIME_TYPE = 'application/vnd.bokehjs_load.v0+json'
 EXEC_MIME_TYPE = 'application/vnd.bokehjs_exec.v0+json'
 
@@ -40,33 +39,14 @@ def load_notebook(resources=None, verbose=False, hide_banner=False, load_timeout
         None
 
     '''
-    nb_html, nb_js = _load_notebook_html(resources, verbose, hide_banner, load_timeout)
-    lab_html, lab_js = _load_notebook_html(resources, verbose, hide_banner, load_timeout, register_mimetype=False)
-    if notebook_type=='jupyter':
-        publish_display_data({'text/html': nb_html + _wrap_in_script_tag(nb_js),
-                              LOAD_MIME_TYPE: {"script": lab_js, "div": lab_html}})
-    else:
-        _publish_zeppelin_data(lab_html, lab_js)
 
-
-FINALIZE_JS = """
-document.getElementById("%s").textContent = "BokehJS is loading...";
-"""
-
-# TODO (bev) This will eventually go away
-def _publish_zeppelin_data(html, js):
-    print('%html ' + html)
-    print('%html ' + '<script type="text/javascript">' + js + "</script>")
-
-def _load_notebook_html(resources=None, verbose=False, hide_banner=False,
-                        load_timeout=5000, register_mimetype=True):
     global _notebook_loaded
 
     from .. import __version__
-    from ..core.templates import AUTOLOAD_NB_JS, NOTEBOOK_LOAD
+    from ..core.templates import NOTEBOOK_LOAD
     from ..util.serialization import make_id
-    from ..util.compiler import bundle_all_models
     from ..resources import CDN
+    from ..util.compiler import bundle_all_models
 
     if resources is None:
         resources = CDN
@@ -99,18 +79,48 @@ def _load_notebook_html(resources=None, verbose=False, hide_banner=False,
 
     custom_models_js = bundle_all_models()
 
+    nb_js = _loading_js(resources, element_id, custom_models_js, load_timeout, register_mime=True)
+    jl_js = _loading_js(resources, element_id, custom_models_js, load_timeout, register_mime=False)
+
+    if notebook_type=='jupyter':
+
+        if not hide_banner:
+            publish_display_data({'text/html': html})
+
+        publish_display_data({
+            JS_MIME_TYPE   : nb_js,
+            LOAD_MIME_TYPE : {"script": jl_js}
+        })
+
+    else:
+        _publish_zeppelin_data(html, jl_js)
+
+
+FINALIZE_JS = """
+document.getElementById("%s").textContent = "BokehJS is loading...";
+"""
+
+# TODO (bev) This will eventually go away
+def _publish_zeppelin_data(html, js):
+    print('%html ' + html)
+    print('%html ' + '<script type="text/javascript">' + js + "</script>")
+
+def _loading_js(resources, element_id, custom_models_js, load_timeout=5000, register_mime=True):
+
+    from ..core.templates import AUTOLOAD_NB_JS
+
     js = AUTOLOAD_NB_JS.render(
-        elementid = '' if hide_banner else element_id,
-        js_urls  = resources.js_files,
-        css_urls = resources.css_files,
-        js_raw   = resources.js_raw + [custom_models_js] + ([] if hide_banner else [FINALIZE_JS % element_id]),
-        css_raw  = resources.css_raw_str,
-        force    = True,
-        timeout  = load_timeout,
-        register_mimetype = register_mimetype
+        elementid = element_id,
+        js_urls   = resources.js_files,
+        css_urls  = resources.css_files,
+        js_raw    = resources.js_raw + [custom_models_js] + [FINALIZE_JS % element_id],
+        css_raw   = resources.css_raw_str,
+        force     = True,
+        timeout   = load_timeout,
+        register_mime = register_mime
     )
 
-    return html, js
+    return js
 
 def get_comms(target_name):
     ''' Create a Jupyter comms object for a specific target, that can


Test patch: