Repository: bokeh/bokeh

Issue:
Use globally unique and CSS safe IDs in `<script type="application/json">`
### Problem description

Embedding **multiple** Bokeh-html-files with javascript on **one single html-page**, I run into problems.

Reason: Bokeh repeatedly prints this into html-files:

`<script type="application/json" id="p1050"> `

However: I think, it is unnecessary, to have a "short" id in this context...


### Feature description

When producing html-files with javascript: Use a **random** id such as: 

id="p7365985367398" 

instead of 

id="p1050"


### Potential alternatives

At the moment, I modify my htm-files manually to fix the problem, which is not a big deal...

If I could set the id's myself: This would be an alternative solution...


### Additional information

I thought, there might be other users with the same problem.


Discussion:
@kassbohm please describe how you are attempting to embed multiple plots. If you are doing everything from a single process you should not get duplicate ids. What exact steps are you taking?
1. I create several plots one by one. Each one with a Python script.
2. I embed a subset of these plots into different html-pages.





Embed *how*? Are you using bokeh embedding APIs? (Could be an issue) Just concatenating multiple separate complete HTML files? (Not supported usage) ***please provide actual reproducible details***.

Edit: let me just be clear: if we cannot, ourselves, reproduce an actual problem locally, then this issue will be closed. I hope that better conveys how much and what kind of information we need. 
I am (as you say) "just concatenating multiple separate complete HTML files". And this works just fine, if the above mentioned id's are unique.

I did not know, that this method is "not supported" or "not the way it is supposed to be done" - now I know. Also I have seen bokeh.embed now.

I described the problem as good as I could. So that I do not see a reason for being upset.


You can prefix your invocations of bokeh with `BOKEH_SIMPLE_IDS="no"` to remedy this issue. Given that all other elements get globally unique IDs (and CSS safe), at least for consistency sake we should change the behavior JSON scripts.
@mattpap Thanks!

This is what I was after.

With this setting: Everything works as expected for me. I have just checked this.



> You can prefix your invocations of bokeh with BOKEH_SIMPLE_IDS="no" to remedy this issue. Given that all other elements get globally unique IDs (and CSS safe), at least for consistency sake we should change the behavior JSON scripts.

@mattpap can you elaborate, I don't really understand what you are proposing. Do you mean getting rid of "simple" ids altogether? Making them not-default? Something else? 

> So that I do not see a reason for being upset.

I'm not upset @kassbohm just trying to be extra clear and completely unambiguous about needs. You've asked for something but with zero actual details about what you are doing and how it takes lots of back and forth to suss out whether the ask is:

* a feature that already exists that you weren't aware of
* a usage problem that can be addressed by other means
* a bug
* an actual reasonable new feature to consider
* something else
* a combination of all of the above

The number one thing users can do to help maintainers help them is to ***give more details***. For bug reports, or feature requests, or help questions. Always include more specifics: code to run, code you'd like to be able to run, exact steps to follow, full text of error messages, package versions...
> can you elaborate, I don't really understand what you are proposing. Do you mean getting rid of "simple" ids altogether? Making them not-default? Something else?

I'm proposing making all generated elements' IDs to be unique and CSS safe. That in particular means any `<script type="application/json">`. As far as I can tell, JSON scripts are the only elements left that use simple IDs.
@mattpap OK, but just noting that the switch to simple ids by default was originally [in response to performance complaints](https://github.com/bokeh/bokeh/pull/8309#issuecomment-427519035)

> Additionally, in 1.0 Bokeh is switching to "simple ids" by default, i.e. a simple monotonically increasing sequence of integers. This is ~2x improvement in time to generate IDs for models

Is there some way to avoid regressing performance these days? Are we certain we have not already incurred a cost, and if so how large?
We are talking about one `<script type="application/json" id="...">` per HTML file (or very rarely a few), not changing all IDs under such script(s), so there will be no performance regression. My suggestion to set `BOKEH_SIMPLE_IDS="no"` was a temporary workaround.

Patch:
diff --git a/src/bokeh/embed/elements.py b/src/bokeh/embed/elements.py
--- a/src/bokeh/embed/elements.py
+++ b/src/bokeh/embed/elements.py
@@ -39,7 +39,7 @@
 )
 from ..document.document import DEFAULT_TITLE
 from ..settings import settings
-from ..util.serialization import make_id
+from ..util.serialization import make_globally_unique_css_safe_id
 from .util import RenderItem
 from .wrappers import wrap_in_onload, wrap_in_safely, wrap_in_script_tag
 
@@ -79,29 +79,34 @@ def div_for_render_item(item: RenderItem) -> str:
     '''
     return PLOT_DIV.render(doc=item, macros=MACROS)
 
-def html_page_for_render_items(bundle: Bundle | tuple[str, str], docs_json: dict[ID, DocJson],
-        render_items: list[RenderItem], title: str, template: Template | str | None = None,
-        template_variables: dict[str, Any] = {}) -> str:
+def html_page_for_render_items(
+    bundle: Bundle | tuple[str, str],
+    docs_json: dict[ID, DocJson],
+    render_items: list[RenderItem],
+    title: str | None,
+    template: Template | str | None = None,
+    template_variables: dict[str, Any] = {},
+) -> str:
     ''' Render an HTML page from a template and Bokeh render items.
 
     Args:
         bundle (tuple):
-            a tuple containing (bokehjs, bokehcss)
+            A tuple containing (bokeh_js, bokeh_css).
 
         docs_json (JSON-like):
-            Serialized Bokeh Document
+            Serialized Bokeh Document.
 
-        render_items (RenderItems)
-            Specific items to render from the document and where
+        render_items (RenderItems):
+            Specific items to render from the document and where.
 
-        title (str or None)
-            A title for the HTML page. If None, DEFAULT_TITLE is used
+        title (str or None):
+            A title for the HTML page. If None, DEFAULT_TITLE is used.
 
-        template (str or Template or None, optional) :
+        template (str or Template or None, optional):
             A Template to be used for the HTML page. If None, FILE is used.
 
         template_variables (dict, optional):
-            Any Additional variables to pass to the template
+            Any Additional variables to pass to the template.
 
     Returns:
         str
@@ -112,7 +117,7 @@ def html_page_for_render_items(bundle: Bundle | tuple[str, str], docs_json: dict
 
     bokeh_js, bokeh_css = bundle
 
-    json_id = make_id()
+    json_id = make_globally_unique_css_safe_id()
     json = escape(serialize_json(docs_json), quote=False)
     json = wrap_in_script_tag(json, "application/json", json_id)
 
diff --git a/src/bokeh/embed/notebook.py b/src/bokeh/embed/notebook.py
--- a/src/bokeh/embed/notebook.py
+++ b/src/bokeh/embed/notebook.py
@@ -40,7 +40,7 @@
 #-----------------------------------------------------------------------------
 
 __all__ = (
-    'notebook_content'
+    'notebook_content',
 )
 
 #-----------------------------------------------------------------------------
diff --git a/src/bokeh/embed/server.py b/src/bokeh/embed/server.py
--- a/src/bokeh/embed/server.py
+++ b/src/bokeh/embed/server.py
@@ -31,7 +31,7 @@
 # Bokeh imports
 from ..core.templates import AUTOLOAD_REQUEST_TAG, FILE
 from ..resources import DEFAULT_SERVER_HTTP_URL
-from ..util.serialization import make_id
+from ..util.serialization import make_globally_unique_css_safe_id
 from ..util.strings import format_docstring
 from .bundle import bundle_for_objs_and_resources
 from .elements import html_page_for_render_items
@@ -107,7 +107,7 @@ def server_document(url: str = "default", relative_urls: bool = False, resources
 
     app_path = _get_app_path(url)
 
-    elementid = make_id()
+    elementid = make_globally_unique_css_safe_id()
     src_path = _src_path(url, elementid)
 
     src_path += _process_app_path(app_path)
@@ -197,7 +197,7 @@ def server_session(model: Model | None = None, session_id: ID | None = None, url
 
     app_path = _get_app_path(url)
 
-    elementid = make_id()
+    elementid = make_globally_unique_css_safe_id()
     modelid = "" if model is None else model.id
     src_path = _src_path(url, elementid)
 


Test patch:
diff --git a/tests/unit/bokeh/embed/test_elements.py b/tests/unit/bokeh/embed/test_elements.py
--- a/tests/unit/bokeh/embed/test_elements.py
+++ b/tests/unit/bokeh/embed/test_elements.py
@@ -16,8 +16,13 @@
 # Imports
 #-----------------------------------------------------------------------------
 
+# Standard library imports
+import re
+
 # Bokeh imports
 from bokeh.core.types import ID
+from bokeh.document.json import DocJson
+from bokeh.embed.bundle import URL, Bundle
 from bokeh.embed.util import RenderItem
 
 # Module under test
@@ -42,7 +47,30 @@ def test_render(self) -> None:
             """<div id="foo123" style="display: contents;"></div>"""
 
 class Test_html_page_for_render_items:
-    pass
+    def test_issue_13629(self) -> None:
+        bundle = Bundle(js_files=[
+            URL(url='http://localhost:5006/static/js/bokeh.js'),
+        ])
+        render_item = RenderItem(docid=ID("doc123"), elementid=ID("foo123"))
+        docs_json = {
+            ID("doc123"): DocJson(
+                version="3.4",
+                title="Bokeh Plot",
+                roots=[],
+            ),
+        }
+        html = bee.html_page_for_render_items(bundle, docs_json, [render_item], None)
+
+        script_pattern = re.compile(r'<script\s*type="application/json"\s*id="([^"]*)"\s*>')
+        uuid_pattern = re.compile(r"^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$")
+
+        result = script_pattern.search(html)
+        assert result is not None
+
+        (id,) = result.groups()
+
+        result = uuid_pattern.match(id)
+        assert result is not None
 
 class Test_script_for_render_items:
     pass