Skip to content

Commit

Permalink
keyboard shortcuts
Browse files Browse the repository at this point in the history
Signed-off-by: Julio Jimenez <[email protected]>
  • Loading branch information
juliojimenez committed Jan 31, 2024
1 parent ea4ff03 commit b0ee84b
Show file tree
Hide file tree
Showing 18 changed files with 1,217 additions and 5 deletions.
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

Examples from building the Contacts app in the book [Hypermedia Systems](https://hypermedia.systems).

The code here is slightly different from the book, as I try to use types where I can, and check for them using [Mypy](https://mypy.readthedocs.io/en/stable/index.html), a static type checker for Python.
The code here is slightly different from the book, as I try to use types where I can, and check for them using [Mypy](https://mypy.readthedocs.io/en/stable/index.html), a static type checker for Python.

[PyTest](https://docs.pytest.org/en/7.4.x/) is used for testing. While inside an example directory simply run `pytest` to run the tests for that example.

Expand Down Expand Up @@ -79,21 +79,21 @@ Each example runs on a different port. Run multiple examples simultaneously and

- [Adding Active Search](./chapter-6/1-adding-active-search/)
- [Targeting The Correct Element](./chapter-6/2-targeting-the-correct-element/)
- [Paring Down Our Content](./chapter-6/3-paring-down-our-content/)
- [Paring Down Our Content](./chapter-6/3-paring-down-our-content/)
- [HTTP Headers In HTMX](./chapter-6/4-http-headers-in-htmx/)
- [Factoring Your Templates](./chapter-6/4-http-headers-in-htmx/)
- [Using Our New Template](./chapter-6/4-http-headers-in-htmx/)
- [Updating The Navigation Bar With "hx-push-url"](./chapter-6/5-updating-the-navigation-bar-with-hx-push-url/)
- [Adding A Request Indicator](./chapter-6/6-adding-a-request-indicator/)
- [Lazy Loading](./chapter-6/7-lazy-loading/)
- [Lazy Loading](./chapter-6/7-lazy-loading/)
- [Pulling Out The Expensive Code](./chapter-6/8-pulling-out-the-expensive-code/)
- [Adding An Indicator](./chapter-6/9-adding-an-indicator/)
- [But That's Not Lazy!](./chapter-6/10-but-thats-not-lazy/)
- [Inline Delete](./chapter-6/11-inline-delete/)
- [Narrowing Our Target](./chapter-6/12-narrowing-our-target/)
- [Narrowing Our Target](./chapter-6/12-narrowing-our-target/)
- [Updating The Server Side](./chapter-6/13-updating-the-server-side/)
- [Taking Advantage of "htmx-swapping"](./chapter-6/14-taking-advantage-of-htmx-swapping/)
- [Bulk Delete](./chapter-6/15-bulk-delete/)
- [Bulk Delete](./chapter-6/15-bulk-delete/)
- [The "Delete Selected Contacts" Button](./chapter-6/16-the-delete-selected-contacts-button/)
- [The Server Side for Delete Selected Contacts](./chapter-6/17-the-server-side-for-delete-selected-contacts/)

Expand All @@ -113,6 +113,7 @@ Each example runs on a different port. Run multiple examples simultaneously and

- [VanillaJS in Action: An Overflow Menu](./chapter-9/1-vanillajs-in-action-an-overflow-menu)
- [Alpine.js](./chapter-9/2-alpine.js)
- [_hyperscript](./chapter-9/3-hyperscript)

## Support

Expand Down
147 changes: 147 additions & 0 deletions chapter-9/3-hyperscript/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
from contacts_model import Contact, Archiver
from flask import Flask, flash, redirect, render_template, request, send_file
from typing import Any
from werkzeug.wrappers import response

Contact.load_db()

app: Flask = Flask(__name__)

app.secret_key = b"it is over"


@app.route("/")
def index() -> response.Response:
return redirect("/contacts")


@app.route("/contacts")
def contacts() -> str:
search: str | None = request.args.get("q")
page: int = int(request.args.get("page", 1))
if search is not None:
contacts_set: list = Contact.search(search)
if request.headers.get("HX-Trigger") == "search":
return render_template("rows.html", contacts=contacts_set)
else:
contacts_set = Contact.all(page)
return render_template(
"index.html", contacts=contacts_set, page=page, archiver=Archiver.get()
)


@app.route("/contacts/count")
def contacts_count() -> str:
count: int = Contact.count()
return f"({str(count)} total Contacts)"


@app.route("/contacts/new", methods=["GET"])
def contacts_new_get() -> str:
return render_template("new.html", contact=Contact())


@app.route("/contacts/new", methods=["POST"])
def contacts_new() -> response.Response | str:
c: Contact = Contact(
None,
request.form["first_name"],
request.form["last_name"],
request.form["phone"],
request.form["email"],
)
if c.save():
flash("Created New Contact!")
return redirect("/contacts")
else:
return render_template("new.html", contact=c)


@app.route("/contacts/<contact_id>")
def contacts_view(contact_id: int = 0) -> str:
contact: Any | None = Contact.find(contact_id)
return render_template("show.html", contact=contact)


@app.route("/contacts/<contact_id>/edit", methods=["GET"])
def contacts_edit_get(contact_id: int = 0) -> str:
contact: Any | None = Contact.find(contact_id)
return render_template("edit.html", contact=contact)


@app.route("/contacts/<contact_id>/edit", methods=["POST"])
def contacts_edit_post(contact_id: int = 0) -> response.Response | str:
c: Any = Contact.find(contact_id)
c.update(
request.form["first_name"],
request.form["last_name"],
request.form["phone"],
request.form["email"],
)
if c.save():
flash("Updated Contact!")
return redirect("/contacts/" + str(contact_id))
else:
return render_template("edit.html", contact=c)


@app.route("/contacts/<contact_id>/email", methods=["GET"])
def contacts_email_get(contact_id=0) -> response.Response | str:
c: Any = Contact.find(contact_id)
c.email = request.args.get("email")
c.validate()
return c.errors.get("email") or ""


@app.route("/contacts/<contact_id>", methods=["DELETE"])
def contacts_delete(contact_id: int = 0) -> response.Response | str:
contact: Any | None = Contact.find(contact_id)
if contact is not None:
contact.delete()
if request.headers.get("HX-Trigger") == "delete-btn":
flash("Deleted Contact!")
return redirect("/contacts", 303)
else:
return ""


@app.route("/contacts/", methods=["DELETE"])
def contacts_delete_all() -> str:
contact_ids: list = list(map(int, request.form.getlist("selected_contact_ids")))
for contact_id in contact_ids:
contact: Any | None = Contact.find(contact_id)
if contact is not None:
contact.delete()
flash("Deleted Contacts!")
contacts_set: list = Contact.all()
return render_template("index.html", contacts=contacts_set, page=1)


@app.route("/contacts/archive", methods=["POST"])
def start_archive() -> str:
archiver = Archiver.get()
archiver.run()
return render_template("archive_ui.html", archiver=archiver)


@app.route("/contacts/archive", methods=["GET"])
def archive_status() -> str:
archiver = Archiver.get()
return render_template("archive_ui.html", archiver=archiver)


@app.route("/contacts/archive/file", methods=["GET"])
def archive_content() -> response.Response:
manager = Archiver.get()
return send_file(manager.archive_file(), "archive.json", as_attachment=True)


@app.route("/contacts/archive", methods=["DELETE"])
def reset_archive() -> str:
archiver = Archiver.get()
archiver.reset()
return render_template("archive_ui.html", archiver=archiver)


if __name__ == "__main__":
app.run(port=5058)
162 changes: 162 additions & 0 deletions chapter-9/3-hyperscript/contacts.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
[
{
"id": 2,
"first": "Carson",
"last": "Gross",
"phone": "123-456-7890",
"email": "[email protected]",
"errors": {}
},
{
"id": 3,
"first": "",
"last": "",
"phone": "",
"email": "[email protected]",
"errors": {}
},
{
"id": 5,
"first": "Joe",
"last": "Blow",
"phone": "123-456-7890",
"email": "[email protected]",
"errors": {}
},
{
"id": 6,
"first": "Joe",
"last": "Blow",
"phone": "123-456-7890",
"email": "[email protected]",
"errors": {}
},
{
"id": 7,
"first": "Joe",
"last": "Blow",
"phone": "123-456-7890",
"email": "[email protected]",
"errors": {}
},
{
"id": 8,
"first": "Joe",
"last": "Blow",
"phone": "123-456-7890",
"email": "[email protected]",
"errors": {}
},
{
"id": 9,
"first": "Joe",
"last": "Blow",
"phone": "123-456-7890",
"email": "[email protected]",
"errors": {}
},
{
"id": 10,
"first": "Joe",
"last": "Blow",
"phone": "123-456-7890",
"email": "[email protected]",
"errors": {}
},
{
"id": 11,
"first": "Joe",
"last": "Blow",
"phone": "123-456-7890",
"email": "[email protected]",
"errors": {}
},
{
"id": 12,
"first": "Joe",
"last": "Blow",
"phone": "123-456-7890",
"email": "[email protected]",
"errors": {}
},
{
"id": 13,
"first": "Joe",
"last": "Blow",
"phone": "123-456-7890",
"email": "[email protected]",
"errors": {}
},
{
"id": 14,
"first": "Joe",
"last": "Blow",
"phone": "123-456-7890",
"email": "[email protected]",
"errors": {}
},
{
"id": 15,
"first": "Joe",
"last": "Blow",
"phone": "123-456-7890",
"email": "[email protected]",
"errors": {}
},
{
"id": 16,
"first": "Joe",
"last": "Blow",
"phone": "123-456-7890",
"email": "[email protected]",
"errors": {}
},
{
"id": 17,
"first": "Joe",
"last": "Blow",
"phone": "123-456-7890",
"email": "[email protected]",
"errors": {}
},
{
"id": 18,
"first": null,
"last": null,
"phone": null,
"email": "[email protected]",
"errors": {}
},
{
"id": 19,
"first": null,
"last": null,
"phone": null,
"email": "[email protected]",
"errors": {}
},
{
"id": 20,
"first": null,
"last": null,
"phone": null,
"email": "[email protected]",
"errors": {}
},
{
"id": 21,
"first": null,
"last": null,
"phone": null,
"email": "[email protected]",
"errors": {}
},
{
"id": 22,
"first": null,
"last": null,
"phone": null,
"email": "[email protected]",
"errors": {}
}
]
Loading

0 comments on commit b0ee84b

Please sign in to comment.