Skip to content

Commit

Permalink
Location component (#1150)
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr authored Apr 15, 2020
1 parent 950e2c6 commit d499fdf
Show file tree
Hide file tree
Showing 30 changed files with 1,281 additions and 643 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,4 @@ builtdocs/
# vscode settings
.vscode/
.vscode/panel.code-workspace
discover/
discover/
45 changes: 38 additions & 7 deletions examples/user_guide/Deploy_and_Export.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -229,13 +229,8 @@
"pn.serve({'markdown': '# This is a Panel app', 'json': pn.pane.JSON({'abc': 123})})\n",
"```\n",
"\n",
"The ``pn.serve`` function accepts the same arguments as the `show` method."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The ``pn.serve`` function accepts the same arguments as the `show` method.\n",
"\n",
"\n",
"\n",
"## Launching a server on the commandline\n",
Expand Down Expand Up @@ -332,6 +327,42 @@
"\n",
"This mechanism may be used to modify the behavior of an app dependending on parameters provided in the URL. \n",
"\n",
"#### Cookies\n",
"\n",
"The `panel.state.cookies` will allow accessing the cookies stored in the browser and on the bokeh server.\n",
"\n",
"#### Headers\n",
"\n",
"The `panel.state.headers` will allow accessing the HTTP headers stored in the browser and on the bokeh server.\n",
"\n",
"#### Location\n",
"\n",
"When starting a server session Panel will attach a `Location` component which can be accessed using `pn.state.location`. The `Location` component servers a number of functions:\n",
"\n",
"- Navigation between pages via ``pathname``\n",
"- Sharing (parts of) the page state in the url as ``search`` parameters for bookmarking and sharing.\n",
"- Navigating to subsections of the page via the ``hash_`` parameter.\n",
"\n",
"##### Core\n",
"\n",
"* **``pathname``** (string): pathname part of the url, e.g. '/user_guide/Interact.html'.\n",
"* **``search``** (string): search part of the url e.g. '?color=blue'.\n",
"* **``hash_``** (string): hash part of the url e.g. '#interact'.\n",
"* **``reload``** (bool): Whether or not to reload the page when the url is updated.\n",
" - For independent apps this should be set to True. \n",
" - For integrated or single page apps this should be set to False.\n",
"\n",
"##### Readonly\n",
"\n",
"* **``href``** (string): The full url, e.g. 'https://panel.holoviz.org/user_guide/Interact.html:80?color=blue#interact'.\n",
"* **``protocol``** (string): protocol part of the url, e.g. 'http:' or 'https:'\n",
"* **``port``** (string): port number, e.g. '80'"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Accessing the Bokeh model\n",
"\n",
"Since Panel is built on top of Bokeh, all Panel objects can easily be converted to a Bokeh model. The ``get_root`` method returns a model representing the contents of a Panel:"
Expand Down
11 changes: 10 additions & 1 deletion examples/user_guide/Overview.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,16 @@
"\n",
"> - `cache`: A global cache which can be used to share data between different processes.\n",
"> - `cookies`: HTTP request cookies for the current session.\n",
"> - `curdoc`: When running a server session this property holds the current bokeh Document. \n",
"> - `curdoc`: When running a server session this property holds the current bokeh Document.\n",
"> - `location`: In a server context this provides read and write access to the URL:\n",
" * `hash`: hash in window.location e.g. '#interact'\n",
" * `pathname`: pathname in window.location e.g. '/user_guide/Interact.html'\n",
" * `search`: search in window.location e.g. '?color=blue'\n",
" * `reload`: Reloads the page when the location is updated.\n",
" * `href` (readonly): The full url, e.g. 'https://localhost:80?color=blue#interact'\n",
" * `hostname` (readonly): hostname in window.location e.g. 'panel.holoviz.org'\n",
" * `protocol` (readonly): protocol in window.location e.g. 'http:' or 'https:'\n",
" * `port` (readonly): port in window.location e.g. '80'\n",
"> - `headers`: HTTP request headers for the current session.\n",
"> - `session_args`: When running a server session this return the request arguments.\n",
"> - `webdriver`: Caches the current webdriver to speed up export of bokeh models to PNGs.\n",
Expand Down
77 changes: 74 additions & 3 deletions examples/user_guide/Param.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
"source": [
"Panel supports using parameters and dependencies between parameters as expressed by ``param`` in a simple way to encapsulate dashboards as declarative, self-contained classes.\n",
"\n",
"Parameters are Python attributes extended using the [Param library](https://github.com/ioam/param) to support types, ranges, and documentation, which turns out to be just the information you need to automatically create widgets for each parameter. \n",
"Parameters are Python attributes extended using the [Param library](https://github.com/holoviz/param) to support types, ranges, and documentation, which turns out to be just the information you need to automatically create widgets for each parameter.\n",
"\n",
"Additionally Panel provides support for linking parameters to the URL query string to allow parameterizing an app very easily.\n",
"\n",
"# Parameters and widgets\n",
"\n",
Expand Down Expand Up @@ -466,6 +468,62 @@
" viewer.panel())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Syncing query parameters\n",
"\n",
"By default the current [query parameters](https://en.wikipedia.org/wiki/Query_string) in the URL (specified as a URL suffix such as `?color=blue`) are made available on `pn.state.location.query_params`. To make working with query parameters straightforward the `Location` object also provides a `sync` method which allows syncing query parameters with the parameters on a `Parameterized` object.\n",
"\n",
"We will start by defining a `Parameterized` class:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"class QueryExample(param.Parameterized):\n",
" \n",
" integer = param.Integer(default=None, bounds=(0, 10))\n",
" \n",
" string = param.String(default='A string')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we will use the `pn.state.location` object to sync it with the URL query string (note that in a notebook environment `pn.state.location` is not initialized until the first plot has been displayed). The sync method takes the Parameterized object or instance to sync with as the first argument and a list or dictionary of the parameters as the second argument. If a dictionary is provided it should map from the Parameterized object's parameters to the query parameter name in the URL:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"pn.state.location.sync(QueryExample, {'integer': 'int', 'string': 'str'})"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now the Parameterized object is bi-directionally linked to the URL query parameter, if we set a query parameter in Python it will update the URL bar and when we specify a URL with a query parameter that will be set on the Parameterized, e.g. let us set the 'integer' parameter and watch the URL in your browser update:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"QueryExample.integer = 5"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand All @@ -475,11 +533,24 @@
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"pygments_lexer": "ipython3"
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.5"
}
},
"nbformat": 4,
"nbformat_minor": 2
"nbformat_minor": 4
}
4 changes: 2 additions & 2 deletions panel/io/embed.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def param_to_jslink(model, widget):
"""
Converts Param pane widget links into JS links if possible.
"""
from ..viewable import Reactive
from ..reactive import Reactive
from ..widgets import Widget, LiteralInput

param_pane = widget._param_pane
Expand Down Expand Up @@ -188,7 +188,7 @@ def embed_state(panel, model, doc, max_states=1000, max_opts=3,
Arguments
---------
panel: panel.viewable.Reactive
panel: panel.reactive.Reactive
The Reactive component being exported
model: bokeh.model.Model
The bokeh model being exported
Expand Down
120 changes: 120 additions & 0 deletions panel/io/location.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
"""
Defines the Location widget which allows changing the href of the window.
"""

import urllib.parse as urlparse

import param

from ..models.location import Location as _BkLocation
from ..reactive import Syncable
from ..util import parse_query


class Location(Syncable):
"""
The Location component can be made available in a server context
to provide read and write access to the URL components in the
browser.
"""

href = param.String(readonly=True, doc="""
The full url, e.g. 'https://localhost:80?color=blue#interact'""")

hostname = param.String(readonly=True, doc="""
hostname in window.location e.g. 'panel.holoviz.org'""")

pathname = param.String(regex=r"^$|[\/].*$", doc="""
pathname in window.location e.g. '/user_guide/Interact.html'""")

protocol = param.String(readonly=True, doc="""
protocol in window.location e.g. 'http:' or 'https:'""")

port = param.String(readonly=True, doc="""
port in window.location e.g. '80'""")

search = param.String(regex=r"^$|\?", doc="""
search in window.location e.g. '?color=blue'""")

hash = param.String(regex=r"^$|#", doc="""
hash in window.location e.g. '#interact'""")

reload = param.Boolean(default=False, doc="""
Reload the page when the location is updated. For multipage
apps this should be set to True, For single page apps this
should be set to False""")

# Mapping from parameter name to bokeh model property name
_rename = {"name": None}

def __init__(self, **params):
super(Location, self).__init__(**params)
self._synced = []
self._syncing = False
self.param.watch(self._update_synced, ['search'])

def _get_model(self, doc, root=None, parent=None, comm=None):
model = _BkLocation(**self._process_param_change(self._init_properties()))
root = root or model
values = dict(self.param.get_param_values())
properties = list(self._process_param_change(values))
self._models[root.ref['id']] = (model, parent)
self._link_props(model, properties, doc, root, comm)
return model

def _update_synced(self, event=None):
if self._syncing:
return
query_params = self.query_params
for p, parameters in self._synced:
mapping = {v: k for k, v in parameters.items()}
p.param.set_param(**{mapping[k]: v for k, v in query_params.items()
if k in mapping})

def _update_query(self, *events, query=None):
if self._syncing:
return
query = query or {}
for e in events:
matches = [ps for o, ps in self._synced if o in (e.cls, e.obj)]
if not matches:
continue
query[matches[0][e.name]] = e.new
self._syncing = True
try:
self.update_query(**{k: v for k, v in query.items() if v is not None})
finally:
self._syncing = False

@property
def query_params(self):
return parse_query(self.search)

def update_query(self, **kwargs):
query = self.query_params
query.update(kwargs)
self.search = '?' + urlparse.urlencode(query)

def sync(self, parameterized, parameters=None):
"""
Syncs the parameters of a Parameterized object with the query
parameters in the URL.
Arguments
---------
parameterized (param.Parameterized):
The Paramterized object to sync query parameters with
parameters (list or dict):
A list or dictionary specifying parameters to sync.
If a dictionary is supplied it should define a mapping from
the Parameterized's parameteres to the names of the query
parameters.
"""
parameters = parameters or [p for p in parameterized.param if p != 'name']
if not isinstance(parameters, dict):
parameters = dict(zip(parameters, parameters))
self._synced.append((parameterized, parameters))
parameterized.param.watch(self._update_query, list(parameters))
self._update_synced()
self._update_query(query={v: getattr(parameterized, k)
for k, v in parameters.items()})
5 changes: 4 additions & 1 deletion panel/io/notebook.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ def render_model(model, comm=None):
{EXEC_MIME: {'id': target}})


def render_mimebundle(model, doc, comm, manager=None):
def render_mimebundle(model, doc, comm, manager=None, location=None):
"""
Displays bokeh output inside a notebook using the PyViz display
and comms machinery.
Expand All @@ -157,6 +157,9 @@ def render_mimebundle(model, doc, comm, manager=None):
add_to_doc(model, doc, True)
if manager is not None:
doc.add_root(manager)
if location is not None:
loc = location._get_model(doc, model, model, comm)
doc.add_root(loc)
return render_model(model, comm)


Expand Down
Loading

0 comments on commit d499fdf

Please sign in to comment.