Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Execute pys-on* events when triggered, not at load #686

Merged
merged 15 commits into from
Sep 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ repos:
- --py310-plus

- repo: https://github.com/pre-commit/mirrors-eslint
rev: v8.23.0
rev: v8.23.1
hooks:
- id: eslint
files: pyscriptjs/src/.*\.[jt]sx?$ # *.js, *.jsx, *.ts and *.tsx
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ PyScript is a meta project that aims to combine multiple open technologies into
To try PyScript, import the appropriate pyscript files into the ```<head>``` tag of your html page with:
```html
<head>
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
```
You can then use PyScript components in your html page. PyScript currently implements the following elements:
Expand Down
4 changes: 2 additions & 2 deletions docs/_static/examples/what-is-pyscript.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<html>
<head>
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<style>
.pulse {
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
Expand Down
28 changes: 14 additions & 14 deletions docs/tutorials/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ If you're new to programming and know nothing about HTML or just want to try som
<title>REPL</title>

<link rel="icon" type="image/png" href="favicon.png" />
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>

<body>
Expand Down Expand Up @@ -65,8 +65,8 @@ open an HTML by double-clicking it in your file explorer.
```html
<html>
<head>
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<body> <py-script> print('Hello, World!') </py-script> </body>
</html>
Expand All @@ -84,8 +84,8 @@ print back onto the page. For example, we can compute π.
```html
<html>
<head>
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<body>
<py-script>
Expand Down Expand Up @@ -117,8 +117,8 @@ the `<py-script>` tag to write to.
```html
<html>
<head>
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
</head>

Expand Down Expand Up @@ -166,8 +166,8 @@ as a shortcut, which takes the expression on the last line of the script and run
```html
<html>
<head>
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<py-env>
- numpy
- matplotlib
Expand Down Expand Up @@ -215,8 +215,8 @@ In the HTML tag `<py-env>`, paths to local modules are provided in the
```html
<html>
<head>
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<py-env>
- numpy
- matplotlib
Expand Down Expand Up @@ -247,8 +247,8 @@ The `<py-repl>` tag creates a REPL component that is rendered to the page as a c
```html
<html>
<head>
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<py-repl></py-repl>
</html>
Expand Down
2 changes: 1 addition & 1 deletion examples/handtrack/say_hello.html
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@
</py-script>

<div class="mb10">
<button id="trackbutton" class="bx--btn bx--btn--secondary" type="button" py-onClick="toggle_video">
<button id="trackbutton" class="bx--btn bx--btn--secondary" type="button" py-onClick="toggle_video()">
Toggle Video
</button>
<button id="nextimagebutton" class="mt10 bx--btn bx--btn--secondary" type="button" disabled>
Expand Down
2 changes: 1 addition & 1 deletion examples/mario/play_mario.html
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@

<div class="mb10">
<p>Use < > to move, ↓ to crouch and x to jump. If video is enabled, say hi to jump as well! </p>
<button id="trackbutton" class="bx--btn bx--btn--secondary" type="button" py-onClick="toggle_video">
<button id="trackbutton" class="bx--btn bx--btn--secondary" type="button" py-onClick="toggle_video()">
Start Video
</button>
<div id="update-note" py-mount class="updatenote mt10">loading model ..</div>
Expand Down
2 changes: 1 addition & 1 deletion examples/micrograd_ai.html
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ <h1>Micrograd - A tiny Autograd engine (with a bite! :))</h1><br>
<div id="python-status">Python is currently starting. Please wait...</div>
</p>
<p>
<button id="run-all-button" class="btn btn-primary" type="submit" py-onClick="run_all_micrograd_demo">Run All</button><br>
<button id="run-all-button" class="btn btn-primary" type="submit" py-onClick="run_all_micrograd_demo()">Run All</button><br>
<py-script src="/micrograd_ai.py"></py-script>
<div id="micrograd-run-all-print-div"></div><br>
<div id="micrograd-run-all-fig1-div"></div>
Expand Down
4 changes: 2 additions & 2 deletions examples/simple_bioinformatics_tool.html
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@
</div>
</div>
<div class="control">
<button id="run" type="button" class="button is-primary" py-onClick="run">Run!</button>
<button id="clear" type="button" class="button is-danger" py-onClick="clear">Clear</button>
<button id="run" type="button" class="button is-primary" py-onClick="run()">Run!</button>
<button id="clear" type="button" class="button is-danger" py-onClick="clear()">Clear</button>
</div>
</div>

Expand Down
2 changes: 1 addition & 1 deletion examples/todo.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ <h1 class="text-3xl font-bold text-gray-800 uppercase tracking-tight">To Do List
</div>
<div>
<input id="new-task-content" class="py-input" type="text">
<button id="new-task-btn" class="py-button" type="submit" py-onClick="add_task">
<button id="new-task-btn" class="py-button" type="submit" py-onClick="add_task()">
Add task
</button>
</div>
Expand Down
35 changes: 35 additions & 0 deletions pyscriptjs/src/components/elements.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { PyRepl } from './pyrepl';
import { PyBox } from './pybox';
import { PyButton } from './pybutton';
import { PyTitle } from './pytitle';
import { PyInputBox } from './pyinputbox';
import { PyWidget } from './base';

/*
These were taken from main.js because some of our components call
runAfterRuntimeInitialized immediately when we are creating the custom
element, this was causing tests to fail since runAfterRuntimeInitialized
expects the runtime to have been loaded before being called.

This function is now called from within the `runtime.initialize`. Once
the runtime finished initializing, then we will create the custom elements
so they are rendered in the page and we will always have a runtime available.

Ideally, this would live under utils.js, but importing all the components in
the utils.js file was causing jest to fail with weird errors such as:
"ReferenceError: Cannot access 'BaseEvalElement' before initialization" coming
from the PyScript class.

*/
function createCustomElements() {
/* eslint-disable @typescript-eslint/no-unused-vars */
const xPyRepl = customElements.define('py-repl', PyRepl);
const xPyBox = customElements.define('py-box', PyBox);
const xPyTitle = customElements.define('py-title', PyTitle);
const xPyWidget = customElements.define('py-register-widget', PyWidget);
const xPyInputBox = customElements.define('py-inputbox', PyInputBox);
const xPyButton = customElements.define('py-button', PyButton);
/* eslint-enable @typescript-eslint/no-unused-vars */
}

export { createCustomElements };
18 changes: 13 additions & 5 deletions pyscriptjs/src/components/pyscript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,12 +227,20 @@ async function createElementsWithEventListeners(runtime: Runtime, pyAttribute: s
}
const handlerCode = el.getAttribute(pyAttribute);
const event = pyAttributeToEvent.get(pyAttribute);
const source = `
from pyodide.ffi import create_proxy
Element("${el.id}").element.addEventListener("${event}", create_proxy(${handlerCode}))
`;
await runtime.run(source);

if (pyAttribute === 'pys-onClick' || pyAttribute === 'pys-onKeyDown'){
console.warn("Use of pys-onClick and pys-onKeyDown attributes is deprecated in favor of py-onClick() and py-onKeyDown(). pys-on* attributes will be deprecated in a future version of PyScript.")
const source = `
from pyodide.ffi import create_proxy
Element("${el.id}").element.addEventListener("${event}", create_proxy(${handlerCode}))
`;
await runtime.run(source);
}
else{
el.addEventListener(event, () => {
(async() => {await runtime.run(handlerCode)})();
});
}
// TODO: Should we actually map handlers in JS instead of Python?
// el.onclick = (evt: any) => {
// console.log("click");
Expand Down
18 changes: 3 additions & 15 deletions pyscriptjs/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,20 @@
import './styles/pyscript_base.css';

import { PyScript } from './components/pyscript';
import { PyRepl } from './components/pyrepl';
import { PyEnv } from './components/pyenv';
import { PyBox } from './components/pybox';
import { PyButton } from './components/pybutton';
import { PyTitle } from './components/pytitle';
import { PyInputBox } from './components/pyinputbox';
import { PyWidget } from './components/base';
import { PyLoader } from './components/pyloader';
import { globalLoader } from './stores';
import { PyConfig } from './components/pyconfig';
import { getLogger } from './logger';
import { globalLoader } from './stores';

const logger = getLogger('pyscript/main');

/* eslint-disable @typescript-eslint/no-unused-vars */
const xPyScript = customElements.define('py-script', PyScript);
const xPyRepl = customElements.define('py-repl', PyRepl);
const xPyEnv = customElements.define('py-env', PyEnv);
const xPyBox = customElements.define('py-box', PyBox);
const xPyButton = customElements.define('py-button', PyButton);
const xPyTitle = customElements.define('py-title', PyTitle);
const xPyInputBox = customElements.define('py-inputbox', PyInputBox);
const xPyWidget = customElements.define('py-register-widget', PyWidget);
const xPyLoader = customElements.define('py-loader', PyLoader);
const xPyConfig = customElements.define('py-config', PyConfig);
/* eslint-enable @typescript-eslint/no-unused-vars */
const xPyEnv = customElements.define('py-env', PyEnv);
/* eslint-disable @typescript-eslint/no-unused-vars */

// As first thing, loop for application configs
logger.info('checking for py-confing');
Expand Down
8 changes: 6 additions & 2 deletions pyscriptjs/src/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import {
postInitializers,
Initializer,
scriptsQueue,
appConfig
} from './stores'
appConfig,
} from './stores';
import { createCustomElements } from './components/elements';
import type { PyScript } from './components/pyscript';
import { getLogger } from './logger';

Expand Down Expand Up @@ -167,6 +168,9 @@ export abstract class Runtime extends Object {
// now we call all post initializers AFTER we actually executed all page scripts
loader?.log('Running post initializers...');

// Finally create the custom elements for pyscript such as pybutton
createCustomElements();

if (appConfig_ && appConfig_.autoclose_loader) {
loader?.close();
}
Expand Down
3 changes: 3 additions & 0 deletions pyscriptjs/tests/integration/support.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,9 @@ def wait_for_pyscript(self, *, timeout=None, check_errors=True):
timeout=timeout,
check_errors=check_errors,
)
# We still don't know why this wait is necessary, but without it
# events aren't being triggered in the tests.
self.page.wait_for_timeout(100)

def pyscript_run(self, snippet, *, extra_head=""):
"""
Expand Down
16 changes: 0 additions & 16 deletions pyscriptjs/tests/integration/test_py_button.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,8 @@
import pytest

from .support import PyScriptTest


class TestPyButton(PyScriptTest):
@pytest.mark.xfail
def test_on_click(self):
"""
currently this test fails for a bad reason which is unrelated to
py-button. During the page loading, the following JS exception occur,
in base.ts:BaseEvalElement.evaluate

[JS exception ] TypeError: Cannot use 'in' operator to search for 'runPythonAsync' in undefined
at http://127.0.0.1:8080/build/pyscript.js:305:38
at Object.subscribe (http://127.0.0.1:8080/build/pyscript.js:46:13)
at PyButton.runAfterRuntimeInitialized (http://127.0.0.1:8080/build/pyscript.js:304:27)
at PyButton.connectedCallback (http://127.0.0.1:8080/build/pyscript.js:26856:18)
at http://127.0.0.1:8080/build/pyscript.js:27075:20
at http://127.0.0.1:8080/build/pyscript.js:27093:3
""" # noqa: E501
self.pyscript_run(
"""
<py-button label="my button">
Expand Down
22 changes: 1 addition & 21 deletions pyscriptjs/tests/integration/test_py_inputbox.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,8 @@
import time

import pytest

from .support import PyScriptTest


class TestPyInputBox(PyScriptTest):
@pytest.mark.xfail
def test_input_box_typing(self):
"""
This test fails in a similar fashion as the pybutton
test so it's xfailed for now.

[JS exception ] TypeError: Cannot use 'in' operator to search for 'runPythonAsync' in undefined
at http://127.0.0.1:8080/build/pyscript.js:305:38
at Object.subscribe (http://127.0.0.1:8080/build/pyscript.js:46:13)
at PyButton.runAfterRuntimeInitialized (http://127.0.0.1:8080/build/pyscript.js:304:27)
at PyButton.connectedCallback (http://127.0.0.1:8080/build/pyscript.js:26856:18)
at http://127.0.0.1:8080/build/pyscript.js:27075:20
at http://127.0.0.1:8080/build/pyscript.js:27093:3
""" # noqa: E501
self.pyscript_run(
"""
<py-inputbox label="my input">
Expand All @@ -32,10 +15,7 @@ def on_keypress(evt):
)
assert self.console.log.lines == [self.PY_COMPLETE]
input = self.page.locator("input")
# We need to wait some time before we can type any text
# otherwise it won't be registered. This was the smallest
# amount that seems to work.
time.sleep(0.1)

input.type("Hello")
input.press("Enter")

Expand Down
Loading