diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 28bb58e15..1234d6ff8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,7 +30,7 @@ jobs: - name: Install Python Dependencies run: pip install -r requirements/test-run.txt - name: Run Tests - run: nox -s test --verbose -- --headless + run: nox -s test -- --headless test-python-versions: runs-on: ${{ matrix.os }} strategy: @@ -54,7 +54,7 @@ jobs: - name: Install Python Dependencies run: pip install -r requirements/test-run.txt - name: Run Tests - run: nox -s test --verbose -- --headless --no-cov + run: nox -s test -- --headless --no-cov test-javascript: runs-on: ubuntu-latest steps: diff --git a/docs/source/examples/play_audio_sound.py b/docs/source/examples/play_audio_sound.py new file mode 100644 index 000000000..582588a8c --- /dev/null +++ b/docs/source/examples/play_audio_sound.py @@ -0,0 +1,21 @@ +import json + +import idom + + +@idom.component +def PlayDinosaurSound(): + event, set_event = idom.hooks.use_state(None) + return idom.html.div( + idom.html.audio( + { + "controls": True, + "onTimeUpdate": lambda e: set_event(e), + "src": "https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3", + } + ), + idom.html.pre(json.dumps(event, indent=2)), + ) + + +idom.run(PlayDinosaurSound) diff --git a/docs/source/examples/prevent_default_event_actions.py b/docs/source/examples/prevent_default_event_actions.py new file mode 100644 index 000000000..eb3bac208 --- /dev/null +++ b/docs/source/examples/prevent_default_event_actions.py @@ -0,0 +1,18 @@ +import idom + + +@idom.component +def DoNotChangePages(): + return idom.html.div( + idom.html.p("Normally clicking this link would take you to a new page"), + idom.html.a( + { + "onClick": idom.event(lambda e: None, prevent_default=True), + "href": "https://google.com", + }, + "https://google.com", + ), + ) + + +idom.run(DoNotChangePages) diff --git a/docs/source/examples/show_click_event.py b/docs/source/examples/show_click_event.py new file mode 100644 index 000000000..6e9691e50 --- /dev/null +++ b/docs/source/examples/show_click_event.py @@ -0,0 +1,15 @@ +import json + +import idom + + +@idom.component +def BasicButton(): + event, set_event = idom.hooks.use_state(None) + return idom.html.div( + idom.html.button({"onClick": lambda e: set_event(e)}, "click to see event"), + idom.html.pre(json.dumps(event, indent=2)), + ) + + +idom.run(BasicButton) diff --git a/docs/source/examples/stop_event_propagation.py b/docs/source/examples/stop_event_propagation.py new file mode 100644 index 000000000..82aeefb7b --- /dev/null +++ b/docs/source/examples/stop_event_propagation.py @@ -0,0 +1,38 @@ +import idom + + +@idom.component +def DivInDiv(): + stop_propagatation, set_stop_propagatation = idom.hooks.use_state(True) + inner_count, set_inner_count = idom.hooks.use_state(0) + outer_count, set_outer_count = idom.hooks.use_state(0) + + div_in_div = idom.html.div( + { + "onClick": idom.event(lambda e: set_outer_count(outer_count + 1)), + "style": {"height": "100px", "width": "100px", "backgroundColor": "red"}, + }, + idom.html.div( + { + "onClick": idom.event( + lambda e: set_inner_count(inner_count + 1), + stop_propagation=stop_propagatation, + ), + "style": {"height": "50px", "width": "50px", "backgroundColor": "blue"}, + }, + ), + ) + + return idom.html.div( + idom.html.button( + {"onClick": lambda e: set_stop_propagatation(not stop_propagatation)}, + "Toggle Propogation", + ), + idom.html.pre(f"Will stop propagation: {stop_propagatation}"), + idom.html.pre(f"Inner click count: {inner_count}"), + idom.html.pre(f"Outer click count: {outer_count}"), + div_in_div, + ) + + +idom.run(DivInDiv) diff --git a/docs/source/faq.rst b/docs/source/faq.rst index 55645a4ae..b473b54b4 100644 --- a/docs/source/faq.rst +++ b/docs/source/faq.rst @@ -16,7 +16,7 @@ favorite Python packages with IDOM. Does IDOM transpile Python to Javascript? ----------------------------------------- -No. As in the answer to :ref:`Do UI components run server-side?`, IDOM runs almost +No. As in the answer to :ref:`Do UI components run client-side?`, IDOM runs almost everything server-side and in Python. This was an explicit design choice to keep things simple and one which allows you to do everything you normally would in Python. diff --git a/docs/source/handling-events.rst b/docs/source/handling-events.rst new file mode 100644 index 000000000..2d741c72e --- /dev/null +++ b/docs/source/handling-events.rst @@ -0,0 +1,62 @@ +Handling Events +=============== + +When :ref:`Getting Started`, we saw how IDOM makes it possible to write server-side code +that defines basic views and can react to client-side events. The simplest way to listen +and respond to events is by assigning a callable object to a :ref:`VDOM ` +an attribute where event signals are sent. This is relatively similar to +`handling events in React`_: + +.. _handling events in React: https://reactjs.org/docs/handling-events.html + +.. example:: show_click_event + + +Differences With React Events +----------------------------- + +Because IDOM operates server-side, there are inevitable limitations that prevent it from +achieving perfect parity with all the behaviors of React. With that said, any feature +that cannot be achieved in Python with IDOM, can be done by creating +:ref:`Custom Javascript Components`. + + +Preventing Default Event Actions +................................ + +Instead of calling an ``event.preventDefault()`` method as you would do in React, you +must declare whether to prevent default behavior ahead of time. This can be accomplished +using the :func:`~idom.core.events.event` decorator and setting ``prevent_default``. For +example, we can stop a link from going to the specified URL: + +.. example:: prevent_default_event_actions + +Unfortunately this means you cannot conditionally prevent default behavior in response +to event data without writing :ref:`Custom Javascript Components`. + + +Stop Event Propogation +...................... + +Similarly to :ref:`preventing default behavior `, you +can use the :func:`~idom.core.events.event` decorator to forward declare whether or not +you want events from a child element propogate up through the document to parent +elements by setting ``stop_propagation``. In the example below we place a red ``div`` +inside a parent blue ``div``. When propogation is turned on, clicking the red element +will cause the handler for the outer blue one to fire. Conversely, when it's off, only +the handler for the red element will fire. + +.. example:: stop_event_propagation + + +Event Data Serialization +........................ + +Not all event data is serialized. The most notable example of this is the lack of a +``target`` key in the dictionary sent back to the handler. Instead, data which is not +inherhently JSON serializable must be treated on a case-by-case basis. A simple case +to demonstrate this is the ``currentTime`` attribute of ``audio`` and ``video`` +elements. Normally this would be accessible via ``event.target.currenTime``, but here +it's simply passed in under the key ``currentTime``: + +.. example:: play_audio_sound diff --git a/docs/source/index.rst b/docs/source/index.rst index fb8c6c654..bfeaa8aac 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -3,9 +3,11 @@ IDOM .. toctree:: :hidden: + :caption: User Guide installation getting-started + handling-events life-cycle-hooks auto/api-reference examples