Skip to content

Commit a3c3f5b

Browse files
authored
Add index files (#2)
* Add index files * update notebook * edit readme
1 parent 1faee6d commit a3c3f5b

File tree

4 files changed

+173
-31
lines changed

4 files changed

+173
-31
lines changed

README.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
# Anywidget Lite
22

3-
[![lite-badge](https://jupyterlite.rtfd.io/en/latest/_static/badge.svg)](https://jtpio.github.io/anywidget-lite)
3+
[![lite-badge](https://jupyterlite.rtfd.io/en/latest/_static/badge.svg)](https://jtpio.github.io/anywidget-lite/lab/index.html?path=anywidget.ipynb)
44

55
Prototype your Jupyter Widget in the browser with anywidget and JupyterLite 💡
66

77
## Usage
88

9-
TODO
9+
Open the following link in your browser and start coding:
10+
11+
**https://jtpio.github.io/anywidget-lite/lab/index.html?path=anywidget.ipynb**
12+

content/anywidget.ipynb

+150-29
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,173 @@
11
{
2-
"metadata": {
3-
"kernelspec": {
4-
"name": "python",
5-
"display_name": "Python (Pyodide)",
6-
"language": "python"
7-
},
8-
"language_info": {
9-
"codemirror_mode": {
10-
"name": "python",
11-
"version": 3
12-
},
13-
"file_extension": ".py",
14-
"mimetype": "text/x-python",
15-
"name": "python",
16-
"nbconvert_exporter": "python",
17-
"pygments_lexer": "ipython3",
18-
"version": "3.8"
19-
}
20-
},
21-
"nbformat_minor": 4,
22-
"nbformat": 4,
232
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# Create a Jupyter Widget with anywidget\n",
8+
"\n",
9+
"*This notebook is heavily inspired by the \"Getting Started\" guide on the anywidget documentation: https://anywidget.dev/en/getting-started/*\n",
10+
"\n",
11+
"## What is anywidget?\n",
12+
"\n",
13+
"**anywidget** is a Python library that simplifies creating and publishing\n",
14+
"custom [Jupyter Widgets](https://ipywidgets.readthedocs.io/en/latest/).\n",
15+
"No messy build configuration or complicated cookiecutter templates.\n",
16+
"\n",
17+
"It is <u>**not**</u> a new interactive widgets framework, but rather\n",
18+
"an abstraction for creating custom Jupyter Widgets using modern web standards.\n",
19+
"\n",
20+
"## Key features\n",
21+
"\n",
22+
"- 🛠️ Create widgets **without complicated cookiecutter templates**\n",
23+
"- 📚 **Publish to PyPI** like any other Python package\n",
24+
"- 🤖 Prototype **within** `.ipynb` or `.py` files\n",
25+
"- 🚀 Run in **Jupyter**, **JupyterLab**, **Google Colab**, **VSCode**, and more\n",
26+
"- ⚡ Develop with **instant HMR**, like modern web frameworks\n"
27+
]
28+
},
29+
{
30+
"cell_type": "markdown",
31+
"metadata": {},
32+
"source": [
33+
"First you need to install `anywidget`:"
34+
]
35+
},
2436
{
2537
"cell_type": "code",
26-
"source": "%pip install anywidget",
38+
"execution_count": null,
2739
"metadata": {
2840
"trusted": true
2941
},
3042
"outputs": [],
31-
"execution_count": null
43+
"source": [
44+
"%pip install anywidget"
45+
]
46+
},
47+
{
48+
"cell_type": "markdown",
49+
"metadata": {},
50+
"source": [
51+
"Then you can define a new widget and provide the `count`, `_esm` and `_css` attributes."
52+
]
3253
},
3354
{
3455
"cell_type": "code",
35-
"source": "import anywidget\nimport traitlets\n\nclass CounterWidget(anywidget.AnyWidget):\n _esm = \"\"\"\n function render({ model, el }) {\n let getCount = () => model.get(\"count\");\n let button = document.createElement(\"button\");\n button.classList.add(\"counter-button\");\n button.innerHTML = `count is ${getCount()}`;\n button.addEventListener(\"click\", () => {\n model.set(\"count\", getCount() + 1);\n model.save_changes();\n });\n model.on(\"change:count\", () => {\n button.innerHTML = `count is ${getCount()}`;\n });\n el.appendChild(button);\n }\n\texport default { render };\n \"\"\"\n _css=\"\"\"\n .counter-button { background-color: #ea580c; }\n .counter-button:hover { background-color: #9a3412; }\n \"\"\"\n count = traitlets.Int(0).tag(sync=True)\n\ncounter = CounterWidget()\ncounter.count = 42\ncounter",
56+
"execution_count": null,
3657
"metadata": {
3758
"trusted": true
3859
},
3960
"outputs": [],
40-
"execution_count": null
61+
"source": [
62+
"import anywidget\n",
63+
"import traitlets\n",
64+
"\n",
65+
"class CounterWidget(anywidget.AnyWidget):\n",
66+
" _esm = \"\"\"\n",
67+
" function render({ model, el }) {\n",
68+
" let getCount = () => model.get(\"count\");\n",
69+
" let button = document.createElement(\"button\");\n",
70+
" button.classList.add(\"counter-button\");\n",
71+
" button.innerHTML = `count is ${getCount()}`;\n",
72+
" button.addEventListener(\"click\", () => {\n",
73+
" model.set(\"count\", getCount() + 1);\n",
74+
" model.save_changes();\n",
75+
" });\n",
76+
" model.on(\"change:count\", () => {\n",
77+
" button.innerHTML = `count is ${getCount()}`;\n",
78+
" });\n",
79+
" el.appendChild(button);\n",
80+
" }\n",
81+
"\texport default { render };\n",
82+
" \"\"\"\n",
83+
" _css=\"\"\"\n",
84+
" .counter-button { background-color: #ea580c; }\n",
85+
" .counter-button:hover { background-color: #9a3412; }\n",
86+
" \"\"\"\n",
87+
" count = traitlets.Int(0).tag(sync=True)\n",
88+
"\n",
89+
"counter = CounterWidget()\n",
90+
"counter.count = 42\n",
91+
"counter"
92+
]
93+
},
94+
{
95+
"cell_type": "markdown",
96+
"metadata": {},
97+
"source": [
98+
"- `count` is a stateful property for that both the client JavaScript and Python have access to.\n",
99+
" Shared state is defined via [traitlets](https://traitlets.readthedocs.io/en/stable/) with the `sync=True`\n",
100+
" keyword argument.\n",
101+
"\n",
102+
"- `_esm` specifies a <u>**required**</u> [ECMAScript module](https://nodejs.org/api/esm.html) for the widget.\n",
103+
" It defines and exports `render`, a function for rendering and initializes dynamic updates for the custom widget.\n",
104+
"\n",
105+
"- `_css` specifies an <u>**optional**</u> CSS stylesheet to load for the widget. It can be a full URL or plain text. Styles are loaded\n",
106+
" in the global scope if using this feature, so take care to avoid naming conflicts.\n",
107+
"\n",
108+
" Feel free to modify some of the code above and re-execute the cells to see the changes 🪄"
109+
]
110+
},
111+
{
112+
"cell_type": "markdown",
113+
"metadata": {},
114+
"source": [
115+
"## Progressive Development\n",
116+
"\n",
117+
"As your widgets grow in complexity, it is recommended to separate your\n",
118+
"front-end code from your Python code. Just move the `_esm` and `_css`\n",
119+
"definitions to separate files and reference them via path."
120+
]
41121
},
42122
{
43123
"cell_type": "code",
44-
"source": "",
124+
"execution_count": null,
45125
"metadata": {
46126
"trusted": true
47127
},
48128
"outputs": [],
49-
"execution_count": null
129+
"source": [
130+
"from pathlib import Path\n",
131+
"\n",
132+
"class CounterWidget(anywidget.AnyWidget):\n",
133+
" _esm = Path('/drive/index.js')\n",
134+
" _css= Path('/drive/index.css')\n",
135+
" count = traitlets.Int(0).tag(sync=True)\n",
136+
"\n",
137+
"counter = CounterWidget()\n",
138+
"counter.count = 42\n",
139+
"counter"
140+
]
141+
},
142+
{
143+
"cell_type": "markdown",
144+
"metadata": {},
145+
"source": [
146+
"**Note**: since this particular notebook is meant to be used in JupyterLite, we specify `/drive` as the prefix for finding the `.js` and `.css` files. `/drive` is the location of the underlying (in-browser) file system where JupyterLite expects to find the files, so they can be displayed in the file browser.\n",
147+
"\n",
148+
"You can now open `index.js` and `index.css` in JupyterLab and edit the files directly. After making changes, you will have to recreate the widget so they are applied."
149+
]
150+
}
151+
],
152+
"metadata": {
153+
"kernelspec": {
154+
"display_name": "Python (Pyodide)",
155+
"language": "python",
156+
"name": "python"
157+
},
158+
"language_info": {
159+
"codemirror_mode": {
160+
"name": "python",
161+
"version": 3
162+
},
163+
"file_extension": ".py",
164+
"mimetype": "text/x-python",
165+
"name": "python",
166+
"nbconvert_exporter": "python",
167+
"pygments_lexer": "ipython3",
168+
"version": "3.8"
50169
}
51-
]
52-
}
170+
},
171+
"nbformat": 4,
172+
"nbformat_minor": 4
173+
}

content/index.css

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.counter-button { background-color: #ea580c; }
2+
.counter-button:hover { background-color: #9a3412; }

content/index.js

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
function render({ model, el }) {
2+
let getCount = () => model.get("count");
3+
let button = document.createElement("button");
4+
button.classList.add("counter-button");
5+
button.innerHTML = `count is ${getCount()}`;
6+
button.addEventListener("click", () => {
7+
model.set("count", getCount() + 1);
8+
model.save_changes();
9+
});
10+
model.on("change:count", () => {
11+
button.innerHTML = `COUNT is ${getCount()}`;
12+
});
13+
el.appendChild(button);
14+
}
15+
16+
export default { render };

0 commit comments

Comments
 (0)