From d4db0ea4e0b0a90756d18ae7e3179441515a8857 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Sat, 8 Oct 2022 22:27:47 +0100 Subject: [PATCH] queries docs --- docs/guide/queries.md | 67 ++++++++++++++++++++++++++++++++++++++++ docs/reference/query.md | 2 +- mkdocs.yml | 1 + src/textual/css/query.py | 10 +++--- 4 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 docs/guide/queries.md diff --git a/docs/guide/queries.md b/docs/guide/queries.md new file mode 100644 index 000000000..1a53a3d28 --- /dev/null +++ b/docs/guide/queries.md @@ -0,0 +1,67 @@ +# DOM Queries + +In the previous chapter we introduced the [DOM](../guide/CSS.md#the-dom), which represents the widgets in a Textual app. We saw how you can apply styles to the DOM with CSS *selectors*. + +Selectors are a very useful thing and can do more that apply styles. We can also modify widgets using selectors in a simple expressive way. Let's look at how! + +## Making queries + +Apps and widgets have a [query][textual.dom.DOMNode.query] method which finds (or queries) widgets. Calling this method will return a [DOMQuery][textual.css.query.DOMQuery] object which is a container (list-like) object with widgets you may iterate over. + +If you call `query` with no arguments, you will get back a `DOMQuery` containing all widgets. This method is *recursive*, meaning it will return all child widgets. + +Here's how you might iterate over all the widgets in your app: + +```python +for widget in self.query(): + print(widget) +``` + +Called on the `app`, this will retrieve all widgets in the app. If you call the same method on a widget, it will return children of that widget. + +### Query selectors + +You can also call `query` with a CSS selector. Let's look a few examples: + +If we want to find all the button widgets, we could do something like the following: + +```python +for button in self.query("Button"): + print(button) +``` + +Any selector that works in CSS will work. For instance, if we want to find all the disabled buttons in a Dialog widget, we could do something like the following: + +```python +for button in self.query("Dialog > Button.disabled"): + print(button) +``` + +### First and Last + +The [first][textual.css.query.DOMQuery.first] and [last][textual.css.query.DOMQuery.last] methods will return the first and last widgets from the selector, respectively. + +Here's how we might find the last button in an app. + +```python +last_button = self.query("Button").last() +``` + +If there are no buttons, textual will raise a [NoMatchingNodesError][textual.css.query.NoMatchingNodesError] exception. Otherwise it will return a button widgets. + +Both `first()` and `last()` accept an `expect_type` argument that should be the class of the widget you are expecting. For instance, lets say we want to get the last with class `.disabled`, and we want to check it really is a button. We could do this: + +```python +disabled_button = self.query(".disables").last(Button) +``` + +The query selects all widgets with a `disabled` CSS class. The `last` method ensures that it is a `Button` and not any other kind of widget. + +If the last widget is *not* a button, Textual will raise a [WrongType][textual.css.query.WrongType] exception. + +!!! tip + + Specifying the expected type allows type-checkers like MyPy to know the exact return type. + +### Filtering + diff --git a/docs/reference/query.md b/docs/reference/query.md index e8f8234a6..313755809 100644 --- a/docs/reference/query.md +++ b/docs/reference/query.md @@ -1 +1 @@ -::: textual.css.query.DOMQuery +::: textual.css.query diff --git a/mkdocs.yml b/mkdocs.yml index 6b2fd4854..68aef278a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -14,6 +14,7 @@ nav: - "guide/app.md" - "guide/styles.md" - "guide/CSS.md" + - "guide/queries.md" - "guide/layout.md" - "guide/events.md" - "guide/input.md" diff --git a/src/textual/css/query.py b/src/textual/css/query.py index 5941bba7a..5779c0bc9 100644 --- a/src/textual/css/query.py +++ b/src/textual/css/query.py @@ -31,15 +31,15 @@ if TYPE_CHECKING: class QueryError(Exception): - pass + """Base class for a query related error.""" class NoMatchingNodesError(QueryError): - pass + """No nodes matched the query.""" class WrongType(QueryError): - pass + """Query result was not of the correct type.""" QueryType = TypeVar("QueryType", bound="Widget") @@ -172,7 +172,7 @@ class DOMQuery(Generic[QueryType]): def first( self, expect_type: type[ExpectType] | None = None ) -> QueryType | ExpectType: - """Get the *first* match node. + """Get the *first* matching node. Args: expect_type (type[ExpectType] | None, optional): Require matched node is of this type, @@ -207,7 +207,7 @@ class DOMQuery(Generic[QueryType]): def last( self, expect_type: type[ExpectType] | None = None ) -> QueryType | ExpectType: - """Get the *last* match node. + """Get the *last* matching node. Args: expect_type (type[ExpectType] | None, optional): Require matched node is of this type,