From e906863874f1037009b586694174b04da20597c4 Mon Sep 17 00:00:00 2001 From: Doug Farrell Date: Thu, 16 May 2019 18:47:19 -0400 Subject: [PATCH 1/7] Starting to change the JavaScript SPA code to use native JavaScript instead of jQuery, Handlebars and Moment --- flask-connexion-rest-part-4/.gitignore | 118 ++++++ flask-connexion-rest-part-4/__init__.py | 0 flask-connexion-rest-part-4/build_database.py | 63 +++ flask-connexion-rest-part-4/config.py | 26 ++ flask-connexion-rest-part-4/models.py | 80 ++++ flask-connexion-rest-part-4/notes.py | 151 +++++++ flask-connexion-rest-part-4/people.py | 148 +++++++ flask-connexion-rest-part-4/requirements.txt | 25 ++ flask-connexion-rest-part-4/server.py | 60 +++ .../static/css/home.css | 65 +++ .../static/css/notes.css | 110 +++++ .../static/css/parent.css | 54 +++ .../static/css/people.css | 95 +++++ flask-connexion-rest-part-4/static/js/home.js | 192 +++++++++ .../static/js/home_1.js | 102 +++++ .../static/js/notes.js | 303 ++++++++++++++ .../static/js/people.js | 307 ++++++++++++++ flask-connexion-rest-part-4/swagger.yml | 383 ++++++++++++++++++ .../templates/home.html | 46 +++ .../templates/notes.html | 83 ++++ .../templates/parent.html | 47 +++ .../templates/people.html | 67 +++ 22 files changed, 2525 insertions(+) create mode 100644 flask-connexion-rest-part-4/.gitignore create mode 100644 flask-connexion-rest-part-4/__init__.py create mode 100644 flask-connexion-rest-part-4/build_database.py create mode 100644 flask-connexion-rest-part-4/config.py create mode 100644 flask-connexion-rest-part-4/models.py create mode 100644 flask-connexion-rest-part-4/notes.py create mode 100644 flask-connexion-rest-part-4/people.py create mode 100644 flask-connexion-rest-part-4/requirements.txt create mode 100644 flask-connexion-rest-part-4/server.py create mode 100644 flask-connexion-rest-part-4/static/css/home.css create mode 100644 flask-connexion-rest-part-4/static/css/notes.css create mode 100644 flask-connexion-rest-part-4/static/css/parent.css create mode 100644 flask-connexion-rest-part-4/static/css/people.css create mode 100644 flask-connexion-rest-part-4/static/js/home.js create mode 100644 flask-connexion-rest-part-4/static/js/home_1.js create mode 100644 flask-connexion-rest-part-4/static/js/notes.js create mode 100644 flask-connexion-rest-part-4/static/js/people.js create mode 100644 flask-connexion-rest-part-4/swagger.yml create mode 100644 flask-connexion-rest-part-4/templates/home.html create mode 100644 flask-connexion-rest-part-4/templates/notes.html create mode 100644 flask-connexion-rest-part-4/templates/parent.html create mode 100644 flask-connexion-rest-part-4/templates/people.html diff --git a/flask-connexion-rest-part-4/.gitignore b/flask-connexion-rest-part-4/.gitignore new file mode 100644 index 0000000000..18fbabb962 --- /dev/null +++ b/flask-connexion-rest-part-4/.gitignore @@ -0,0 +1,118 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# ignore the database +people.db \ No newline at end of file diff --git a/flask-connexion-rest-part-4/__init__.py b/flask-connexion-rest-part-4/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/flask-connexion-rest-part-4/build_database.py b/flask-connexion-rest-part-4/build_database.py new file mode 100644 index 0000000000..feeb1d0534 --- /dev/null +++ b/flask-connexion-rest-part-4/build_database.py @@ -0,0 +1,63 @@ +import os +from datetime import datetime +from config import db +from models import Person, Note + +# Data to initialize database with +PEOPLE = [ + { + "fname": "Doug", + "lname": "Farrell", + "notes": [ + ("Cool, a mini-blogging application!", "2019-01-06 22:17:54"), + ("This could be useful", "2019-01-08 22:17:54"), + ("Well, sort of useful", "2019-03-06 22:17:54"), + ], + }, + { + "fname": "Kent", + "lname": "Brockman", + "notes": [ + ( + "I'm going to make really profound observations", + "2019-01-07 22:17:54", + ), + ( + "Maybe they'll be more obvious than I thought", + "2019-02-06 22:17:54", + ), + ], + }, + { + "fname": "Bunny", + "lname": "Easter", + "notes": [ + ("Has anyone seen my Easter eggs?", "2019-01-07 22:47:54"), + ("I'm really late delivering these!", "2019-04-06 22:17:54"), + ], + }, +] + +# Delete database file if it exists currently +if os.path.exists("people.db"): + os.remove("people.db") + +# Create the database +db.create_all() + +# iterate over the PEOPLE structure and populate the database +for person in PEOPLE: + p = Person(lname=person.get("lname"), fname=person.get("fname")) + + # Add the notes for the person + for note in person.get("notes"): + content, timestamp = note + p.notes.append( + Note( + content=content, + timestamp=datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S"), + ) + ) + db.session.add(p) + +db.session.commit() diff --git a/flask-connexion-rest-part-4/config.py b/flask-connexion-rest-part-4/config.py new file mode 100644 index 0000000000..002cd2a481 --- /dev/null +++ b/flask-connexion-rest-part-4/config.py @@ -0,0 +1,26 @@ +import os +import connexion +from flask_sqlalchemy import SQLAlchemy +from flask_marshmallow import Marshmallow + +basedir = os.path.abspath(os.path.dirname(__file__)) + +# Create the connexion application instance +connex_app = connexion.App(__name__, specification_dir=basedir) + +# Get the underlying Flask app instance +app = connex_app.app + +# Build the Sqlite ULR for SqlAlchemy +sqlite_url = "sqlite:////" + os.path.join(basedir, "people.db") + +# Configure the SqlAlchemy part of the app instance +app.config["SQLALCHEMY_ECHO"] = False +app.config["SQLALCHEMY_DATABASE_URI"] = sqlite_url +app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False + +# Create the SqlAlchemy db instance +db = SQLAlchemy(app) + +# Initialize Marshmallow +ma = Marshmallow(app) diff --git a/flask-connexion-rest-part-4/models.py b/flask-connexion-rest-part-4/models.py new file mode 100644 index 0000000000..7e970977eb --- /dev/null +++ b/flask-connexion-rest-part-4/models.py @@ -0,0 +1,80 @@ +from datetime import datetime +from config import db, ma +from marshmallow import fields + + +class Person(db.Model): + __tablename__ = "person" + person_id = db.Column(db.Integer, primary_key=True) + lname = db.Column(db.String(32)) + fname = db.Column(db.String(32)) + timestamp = db.Column( + db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow + ) + notes = db.relationship( + "Note", + backref="person", + cascade="all, delete, delete-orphan", + single_parent=True, + order_by="desc(Note.timestamp)", + ) + + +class Note(db.Model): + __tablename__ = "note" + note_id = db.Column(db.Integer, primary_key=True) + person_id = db.Column(db.Integer, db.ForeignKey("person.person_id")) + content = db.Column(db.String, nullable=False) + timestamp = db.Column( + db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow + ) + + +class PersonSchema(ma.ModelSchema): + def __init__(self, **kwargs): + super().__init__(strict=True, **kwargs) + + class Meta: + model = Person + sqla_session = db.session + + notes = fields.Nested("PersonNoteSchema", default=[], many=True) + + +class PersonNoteSchema(ma.ModelSchema): + """ + This class exists to get around a recursion issue + """ + + def __init__(self, **kwargs): + super().__init__(strict=True, **kwargs) + + note_id = fields.Int() + person_id = fields.Int() + content = fields.Str() + timestamp = fields.Str() + + +class NoteSchema(ma.ModelSchema): + def __init__(self, **kwargs): + super().__init__(strict=True, **kwargs) + + class Meta: + model = Note + sqla_session = db.session + + person = fields.Nested("NotePersonSchema", default=None) + + +class NotePersonSchema(ma.ModelSchema): + """ + This class exists to get around a recursion issue + """ + + def __init__(self, **kwargs): + super().__init__(strict=True, **kwargs) + + person_id = fields.Int() + lname = fields.Str() + fname = fields.Str() + timestamp = fields.Str() diff --git a/flask-connexion-rest-part-4/notes.py b/flask-connexion-rest-part-4/notes.py new file mode 100644 index 0000000000..9e3805e146 --- /dev/null +++ b/flask-connexion-rest-part-4/notes.py @@ -0,0 +1,151 @@ +""" +This is the people module and supports all the REST actions for the +people data +""" + +from flask import make_response, abort +from config import db +from models import Person, Note, NoteSchema + + +def read_all(): + """ + This function responds to a request for /api/people/notes + with the complete list of notes, sorted by note timestamp + + :return: json list of all notes for all people + """ + # Query the database for all the notes + notes = Note.query.order_by(db.desc(Note.timestamp)).all() + + # Serialize the list of notes from our data + note_schema = NoteSchema(many=True, exclude=["person.notes"]) + data = note_schema.dump(notes).data + return data + + +def read_one(person_id, note_id): + """ + This function responds to a request for + /api/people/{person_id}/notes/{note_id} + with one matching note for the associated person + + :param person_id: Id of person the note is related to + :param note_id: Id of the note + :return: json string of note contents + """ + # Query the database for the note + note = ( + Note.query.join(Person, Person.person_id == Note.person_id) + .filter(Person.person_id == person_id) + .filter(Note.note_id == note_id) + .one_or_none() + ) + + # Was a note found? + if note is not None: + note_schema = NoteSchema() + data = note_schema.dump(note).data + return data + + # Otherwise, nope, didn't find that note + else: + abort(404, f"Note not found for Id: {note_id}") + + +def create(person_id, note): + """ + This function creates a new note related to the passed in person id. + + :param person_id: Id of the person the note is related to + :param note: The JSON containing the note data + :return: 201 on success + """ + # get the parent person + person = Person.query.filter(Person.person_id == person_id).one_or_none() + + # Was a person found? + if person is None: + abort(404, f"Person not found for Id: {person_id}") + + # Create a note schema instance + schema = NoteSchema() + new_note = schema.load(note, session=db.session).data + + # Add the note to the person and database + person.notes.append(new_note) + db.session.commit() + + # Serialize and return the newly created note in the response + data = schema.dump(new_note).data + + return data, 201 + + +def update(person_id, note_id, note): + """ + This function updates an existing note related to the passed in + person id. + + :param person_id: Id of the person the note is related to + :param note_id: Id of the note to update + :param content: The JSON containing the note data + :return: 200 on success + """ + update_note = ( + Note.query.filter(Person.person_id == person_id) + .filter(Note.note_id == note_id) + .one_or_none() + ) + + # Did we find an existing note? + if update_note is not None: + + # turn the passed in note into a db object + schema = NoteSchema() + update = schema.load(note, session=db.session).data + + # Set the id's to the note we want to update + update.person_id = update_note.person_id + update.note_id = update_note.note_id + + # merge the new object into the old and commit it to the db + db.session.merge(update) + db.session.commit() + + # return updated note in the response + data = schema.dump(update_note).data + + return data, 200 + + # Otherwise, nope, didn't find that note + else: + abort(404, f"Note not found for Id: {note_id}") + + +def delete(person_id, note_id): + """ + This function deletes a note from the note structure + + :param person_id: Id of the person the note is related to + :param note_id: Id of the note to delete + :return: 200 on successful delete, 404 if not found + """ + # Get the note requested + note = ( + Note.query.filter(Person.person_id == person_id) + .filter(Note.note_id == note_id) + .one_or_none() + ) + + # did we find a note? + if note is not None: + db.session.delete(note) + db.session.commit() + return make_response( + "Note {note_id} deleted".format(note_id=note_id), 200 + ) + + # Otherwise, nope, didn't find that note + else: + abort(404, f"Note not found for Id: {note_id}") diff --git a/flask-connexion-rest-part-4/people.py b/flask-connexion-rest-part-4/people.py new file mode 100644 index 0000000000..04fd0ccafb --- /dev/null +++ b/flask-connexion-rest-part-4/people.py @@ -0,0 +1,148 @@ +""" +This is the people module and supports all the REST actions for the +people data +""" + +from flask import make_response, abort +from config import db +from models import Person, PersonSchema, Note + + +def read_all(): + """ + This function responds to a request for /api/people + with the complete lists of people + + :return: json string of list of people + """ + # Create the list of people from our data + people = Person.query.order_by(Person.lname).all() + + # Serialize the data for the response + person_schema = PersonSchema(many=True) + data = person_schema.dump(people).data + return data + + +def read_one(person_id): + """ + This function responds to a request for /api/people/{person_id} + with one matching person from people + + :param person_id: Id of person to find + :return: person matching id + """ + # Build the initial query + person = ( + Person.query.filter(Person.person_id == person_id) + .outerjoin(Note) + .one_or_none() + ) + + # Did we find a person? + if person is not None: + + # Serialize the data for the response + person_schema = PersonSchema() + data = person_schema.dump(person).data + return data + + # Otherwise, nope, didn't find that person + else: + abort(404, f"Person not found for Id: {person_id}") + + +def create(person): + """ + This function creates a new person in the people structure + based on the passed in person data + + :param person: person to create in people structure + :return: 201 on success, 406 on person exists + """ + fname = person.get("fname") + lname = person.get("lname") + + existing_person = ( + Person.query.filter(Person.fname == fname) + .filter(Person.lname == lname) + .one_or_none() + ) + + # Can we insert this person? + if existing_person is None: + + # Create a person instance using the schema and the passed in person + schema = PersonSchema() + new_person = schema.load(person, session=db.session).data + + # Add the person to the database + db.session.add(new_person) + db.session.commit() + + # Serialize and return the newly created person in the response + data = schema.dump(new_person).data + + return data, 201 + + # Otherwise, nope, person exists already + else: + abort(409, f"Person {fname} {lname} exists already") + + +def update(person_id, person): + """ + This function updates an existing person in the people structure + + :param person_id: Id of the person to update in the people structure + :param person: person to update + :return: updated person structure + """ + # Get the person requested from the db into session + update_person = Person.query.filter( + Person.person_id == person_id + ).one_or_none() + + # Did we find an existing person? + if update_person is not None: + + # turn the passed in person into a db object + schema = PersonSchema() + update = schema.load(person, session=db.session).data + + # Set the id to the person we want to update + update.person_id = update_person.person_id + + # merge the new object into the old and commit it to the db + db.session.merge(update) + db.session.commit() + + # return updated person in the response + data = schema.dump(update_person).data + + return data, 200 + + # Otherwise, nope, didn't find that person + else: + abort(404, f"Person not found for Id: {person_id}") + + +def delete(person_id): + """ + This function deletes a person from the people structure + + :param person_id: Id of the person to delete + :return: 200 on successful delete, 404 if not found + """ + # Get the person requested + person = Person.query.filter(Person.person_id == person_id).one_or_none() + + # Did we find a person? + if person is not None: + db.session.delete(person) + db.session.commit() + return make_response(f"Person {person_id} deleted", 200) + + # Otherwise, nope, didn't find that person + else: + abort(404, f"Person not found for Id: {person_id}") diff --git a/flask-connexion-rest-part-4/requirements.txt b/flask-connexion-rest-part-4/requirements.txt new file mode 100644 index 0000000000..c9efff71aa --- /dev/null +++ b/flask-connexion-rest-part-4/requirements.txt @@ -0,0 +1,25 @@ +certifi==2018.11.29 +chardet==3.0.4 +Click==7.0 +clickclick==1.2.2 +connexion==2.2.0 +Flask==1.0.2 +flask-marshmallow==0.9.0 +Flask-SQLAlchemy==2.3.2 +idna==2.8 +inflection==0.3.1 +itsdangerous==1.1.0 +Jinja2==2.10 +jsonschema==2.6.0 +MarkupSafe==1.1.0 +marshmallow==2.18.0 +marshmallow-sqlalchemy==0.15.0 +openapi-spec-validator==0.2.4 +pathlib==1.0.1 +PyYAML==4.2b4 +requests==2.21.0 +six==1.12.0 +SQLAlchemy==1.2.17 +swagger-ui-bundle==0.0.3 +urllib3==1.24.1 +Werkzeug==0.14.1 diff --git a/flask-connexion-rest-part-4/server.py b/flask-connexion-rest-part-4/server.py new file mode 100644 index 0000000000..1fcbac807a --- /dev/null +++ b/flask-connexion-rest-part-4/server.py @@ -0,0 +1,60 @@ +""" +Main module of the server file +""" + +# 3rd party moudles +from flask import render_template + +# Local modules +import config + + +# Get the application instance +connex_app = config.connex_app + +# Read the swagger.yml file to configure the endpoints +connex_app.add_api("swagger.yml") + + +# Create a URL route in our application for "/" +@connex_app.route("/") +def home(): + """ + This function just responds to the browser URL + localhost:5000/ + + :return: the rendered template "home.html" + """ + return render_template("home.html") + + +# Create a URL route in our application for "/people" +@connex_app.route("/people") +@connex_app.route("/people/") +def people(person_id=""): + """ + This function just responds to the browser URL + localhost:5000/people + + :return: the rendered template "people.html" + """ + return render_template("people.html", person_id=person_id) + + +# Create a URL route to the notes page +@connex_app.route("/people/") +@connex_app.route("/people//notes") +@connex_app.route("/people//notes/") +def notes(person_id, note_id=""): + """ + This function responds to the browser URL + localhost:5000/notes/ + + :param person_id: Id of the person to show notes for + :return: the rendered template "notes.html" + """ + return render_template("notes.html", person_id=person_id, note_id=note_id) + + +if __name__ == "__main__": + connex_app.run(debug=True) diff --git a/flask-connexion-rest-part-4/static/css/home.css b/flask-connexion-rest-part-4/static/css/home.css new file mode 100644 index 0000000000..fb66c8835b --- /dev/null +++ b/flask-connexion-rest-part-4/static/css/home.css @@ -0,0 +1,65 @@ +/* + * CSS stylesheet for home page + */ + +div.blog { + margin: 10px 10px 10px 10px; + border: 2px solid darkgrey; +} + +.blog table { + border-collapse: collapse; + width: 100%; +} + +.blog table caption { + font-weight: bold; + padding: 10px 0 10px 0; + border-bottom: 2px solid darkgrey; + background-color: antiquewhite; +} + +.blog table thead { + padding: 10px 0 10px 0; +} + +th:not(:last-child), td:not(:last-child) { + border-right: 2px solid darkgrey; +} + +td.name { + text-align: center; + width: 175px; +} + +td.timestamp { + text-align: center; + width: 280px; +} + +td.content { + text-align: left; + padding-left: 5px; +} + +tbody tr:nth-child(odd) { + background-color: gainsboro; +} + +tbody td.name, tbody td.content { + cursor: pointer; + height: 33px; +} + +thead tr { + height: 33px; + border-bottom: 2px solid darkgrey; +} + +tbody tr:not(:last-child){ + border-bottom: 2px solid darkgrey; +} + +tbody tr:hover { + background-color: powderblue; +} diff --git a/flask-connexion-rest-part-4/static/css/notes.css b/flask-connexion-rest-part-4/static/css/notes.css new file mode 100644 index 0000000000..0eaa205560 --- /dev/null +++ b/flask-connexion-rest-part-4/static/css/notes.css @@ -0,0 +1,110 @@ +/* + * This is the CSS stylesheet for the notes creation/update/delete application + */ + +.container { + padding: 10px; +} + +.banner { + text-align: center; +} + +.display, .editor { + width: 50%; + margin-left: auto; + margin-right: auto; + padding: 5px; + border: 2px solid darkgrey; + border-radius: 4px; + margin-bottom: 5px; +} + +display div, .editor div { + margin-bottom: 5px; +} + +label { + display: inline-block; + margin-bottom: 5px; +} + +button { + padding: 5px; + margin-right: 5px; + border-radius: 3px; + background-color: #eee; +} + +input[type=text] { + width: 550px; +} + +div.notes { + margin: 10px 10px 10px 10px; + border: 2px solid darkgrey; +} + +table { + width: 100%; + border-collapse: collapse; +} + +table caption { + font-weight: bold; + padding: 10px 0 10px 0; + border-bottom: 2px solid darkgrey; + background-color: antiquewhite; +} + +.blog table thead { + padding: 10px 0 10px 0; +} + +th:not(:last-child), td:not(:last-child) { + border-right: 2px solid darkgrey; +} + +td.name { + text-align: center; + width: 175px; +} + +td.timestamp { + text-align: center; + width: 230px; +} + +td.content { + text-align: left; + padding-left: 5px; +} + +tbody tr:nth-child(odd) { + background-color: gainsboro; +} + +tbody tr { + cursor: pointer; + height: 33px; +} + +thead tr { + height: 33px; + border-bottom: 2px solid darkgrey; +} + +tbody tr:not(:last-child){ + border-bottom: 2px solid darkgrey; +} + +tbody tr:hover { + background-color: powderblue; +} +.content { + width: 70%; +} + +.timestamp { + width: 30%; +} diff --git a/flask-connexion-rest-part-4/static/css/parent.css b/flask-connexion-rest-part-4/static/css/parent.css new file mode 100644 index 0000000000..de465c69e5 --- /dev/null +++ b/flask-connexion-rest-part-4/static/css/parent.css @@ -0,0 +1,54 @@ +/* + * This is the parent CSS stylesheet that all pages get basic styling from + */ + +@import url(http://fonts.googleapis.com/css?family=Roboto:400,300,500,700); + +body, .ui-btn { + font-family: Roboto; +} + +.navigation { + display: flex; + flex-direction: row; + padding: 15px 10px 15px 10px; + border-bottom: 2px solid darkgrey; + background-color: whitesmoke; +} + +.navigation a { + margin-right: 10px; + padding: 5px; + border: 1px solid darkgrey; + border-radius: 4px; + text-decoration: none; + cursor: pointer; + text: black; + background-color: lightskyblue; +} + +.navigation .page_name { + flex-grow: 2; + text-align: center; + font-size: 1.5em; + font-weight: bold; +} + +.navigation a:hover { + background-color: deepskyblue; +} + +.container { + padding: 10px; +} + +.error { + width: 50%; + margin-left: auto; + margin-right: auto; + padding: 5px; + border: 1px solid lightgrey; + border-radius: 3px; + background-color: #fbb; + visibility: hidden; +} \ No newline at end of file diff --git a/flask-connexion-rest-part-4/static/css/people.css b/flask-connexion-rest-part-4/static/css/people.css new file mode 100644 index 0000000000..285aefc1cf --- /dev/null +++ b/flask-connexion-rest-part-4/static/css/people.css @@ -0,0 +1,95 @@ +/* + * This is the CSS stylesheet for the demo people application + */ + +.container { + padding: 10px; +} + +.editor { + width: 50%; + margin-left: auto; + margin-right: auto; + padding: 5px; + border: 2px solid darkgrey; + border-radius: 4px; + margin-bottom: 5px; +} + +label { + display: inline-block; + margin-bottom: 5px; +} + +button { + padding: 5px; + margin-right: 5px; + border-radius: 3px; + background-color: #eee; +} + +div.people { + margin: 10px 30px 10px 30px; + border: 2px solid darkgrey; +} + +table { + width: 100%; + border-collapse: collapse; +} + +.people table caption { + font-weight: bold; + padding: 10px 0 10px 0; + border-bottom: 2px solid darkgrey; + background-color: antiquewhite; +} + +.people table thead { + padding: 10px 0 10px 0; +} + +th:not(:last-child), td:not(:last-child) { + border-right: 2px solid darkgrey; +} + +tr:nth-child(even) { + background-color: gainsboro; +} + +td { + text-align: center; +} + +tbody td.timestamp { + width: 280px; +} + +tbody td.name { + cursor: pointer; + height: 33px; +} + +thead tr { + height: 33px; + border-bottom: 2px solid darkgrey; +} + +tbody tr:not(:last-child){ + border-bottom: 2px solid darkgrey; +} + +tbody tr:hover { + background-color: powderblue; +} + +.error { + width: 50%; + margin-left: auto; + margin-right: auto; + padding: 5px; + border: 1px solid lightgrey; + border-radius: 3px; + background-color: #fbb; + visibility: hidden; +} \ No newline at end of file diff --git a/flask-connexion-rest-part-4/static/js/home.js b/flask-connexion-rest-part-4/static/js/home.js new file mode 100644 index 0000000000..d7f06d6435 --- /dev/null +++ b/flask-connexion-rest-part-4/static/js/home.js @@ -0,0 +1,192 @@ +/** + * JavaScript file for the Home page + */ + +"use strict"; + + +/** + * This is the model class which provides access to the server REST API + * @type {{}} + */ +class Model { + options; + constructor () { + this.options = { + method: "GET", + cache: "no-cache", + headers: { + "Content-Type": "application/json" + } + }; + } + + read() { + return fetch("/api/notes", this.options) + } +} + + +/** + * This is the view class which provides access to the DOM + */ +class View { + constructor() { + this._table = document.querySelector(".blog table"); + console.log("hello"); + } + + build_table(data) { + let tbody = this._table.createTBody(); + tbody.innerHTML = ` + + Hello + there + + + Doug + Farrell + + `; + } + error(error_msg) { + + } +} + + +/** + * This is the controller which provides for the user interaction + */ +class Controller { + + initialize(model, view) { + this.model = model; + this.view = view; + + this.view.build_table(); + } +} + +/** + * This class defines our namespace container + * @type {{}} + */ +class NameSpace{ + constructor(model, view, controller) { + this._model = model; + this._view = view; + this._controller = controller; + this._controller.initialize(this._model, this._view); + } +}; + +/** + * Create the namespace object + * @type {{}} + */ +const nameSpace = new NameSpace(new Model(), new View(), new Controller()); +debugger; + + + + + +// Create the namespace instance +let ns = {}; + +// Create the model instance +ns.model = (function () { + 'use strict'; + + // Return the API + return { + 'read': function () { + let ajax_options = { + type: 'GET', + url: '/api/notes', + accepts: 'application/json', + dataType: 'json' + }; + return $.ajax(ajax_options); + } + }; +}()); + + +// Create the view instance +ns.view = (function () { + 'use strict'; + + var $table = $(".blog table"); + + // Return the API + return { + build_table: function (data) { + let source = $('#blog-table-template').html(), + template = Handlebars.compile(source), + html; + + // Create the HTML from the template and notes + html = template({notes: data}); + + // Append the rows to the table tbody + $table.append(html); + }, + error: function (error_msg) { + $('.error') + .text(error_msg) + .css('visibility', 'visible'); + setTimeout(function () { + $('.error').fadeOut(); + }, 2000) + } + }; +}()); + + +// Create the controller instance +ns.controller = (function (m, v) { + 'use strict'; + + let model = m, + view = v; + + // Get the note data from the model after the controller is done initializing + setTimeout(function () { + + // Attach event handlers to the promise returned by model.read() + model.read() + .done(function (data) { + view.build_table(data); + }) + .fail(function (xhr, textStatus, errorThrown) { + error_handler(xhr, textStatus, errorThrown); + }); + }, 100); + + // generic error handler + function error_handler(xhr, textStatus, errorThrown) { + let error_msg = `${textStatus}: ${errorThrown} - ${xhr.responseJSON.detail}`; + + view.error(error_msg); + console.log(error_msg); + } + + // handle application events + $('table').on('dblclick', 'tbody td.name', function (e) { + let $target = $(e.target).parent(), + person_id = $target.data('person_id'); + + window.location = `/people/${person_id}`; + + }); + + $('table').on('dblclick', 'tbody td.content', function (e) { + let $target = $(e.target).parent(), + person_id = $target.data('person_id'), + note_id = $target.data('note_id'); + + window.location = `people/${person_id}/notes/${note_id}`; + }); +}(ns.model, ns.view)); \ No newline at end of file diff --git a/flask-connexion-rest-part-4/static/js/home_1.js b/flask-connexion-rest-part-4/static/js/home_1.js new file mode 100644 index 0000000000..829f6347bb --- /dev/null +++ b/flask-connexion-rest-part-4/static/js/home_1.js @@ -0,0 +1,102 @@ +/* + * JavaScript file for the Home page + */ + +// Create the namespace instance +let ns = {}; + +// Create the model instance +ns.model = (function () { + 'use strict'; + + // Return the API + return { + 'read': function () { + let ajax_options = { + type: 'GET', + url: '/api/notes', + accepts: 'application/json', + dataType: 'json' + }; + return $.ajax(ajax_options); + } + }; +}()); + + +// Create the view instance +ns.view = (function () { + 'use strict'; + + var $table = $(".blog table"); + + // Return the API + return { + build_table: function (data) { + let source = $('#blog-table-template').html(), + template = Handlebars.compile(source), + html; + + // Create the HTML from the template and notes + html = template({notes: data}); + + // Append the rows to the table tbody + $table.append(html); + }, + error: function (error_msg) { + $('.error') + .text(error_msg) + .css('visibility', 'visible'); + setTimeout(function () { + $('.error').fadeOut(); + }, 2000) + } + }; +}()); + + +// Create the controller instance +ns.controller = (function (m, v) { + 'use strict'; + + let model = m, + view = v; + + // Get the note data from the model after the controller is done initializing + setTimeout(function () { + + // Attach event handlers to the promise returned by model.read() + model.read() + .done(function (data) { + view.build_table(data); + }) + .fail(function (xhr, textStatus, errorThrown) { + error_handler(xhr, textStatus, errorThrown); + }); + }, 100); + + // generic error handler + function error_handler(xhr, textStatus, errorThrown) { + let error_msg = `${textStatus}: ${errorThrown} - ${xhr.responseJSON.detail}`; + + view.error(error_msg); + console.log(error_msg); + } + + // handle application events + $('table').on('dblclick', 'tbody td.name', function (e) { + let $target = $(e.target).parent(), + person_id = $target.data('person_id'); + + window.location = `/people/${person_id}`; + + }); + + $('table').on('dblclick', 'tbody td.content', function (e) { + let $target = $(e.target).parent(), + person_id = $target.data('person_id'), + note_id = $target.data('note_id'); + + window.location = `people/${person_id}/notes/${note_id}`; + }); +}(ns.model, ns.view)); \ No newline at end of file diff --git a/flask-connexion-rest-part-4/static/js/notes.js b/flask-connexion-rest-part-4/static/js/notes.js new file mode 100644 index 0000000000..4e40926b26 --- /dev/null +++ b/flask-connexion-rest-part-4/static/js/notes.js @@ -0,0 +1,303 @@ +/* + * JavaScript file for the application to demonstrate + * using the API for creating, updating and deleting notes + */ + +// Create the namespace instance +let ns = {}; + +// Create the model instance +ns.model = (function () { + 'use strict'; + + // Return the API + return { + read_one: function (person_id, note_id) { + let ajax_options = { + type: 'GET', + url: `/api/people/${person_id}/notes/${note_id}`, + accepts: 'application/json', + dataType: 'json' + }; + return $.ajax(ajax_options); + }, + read: function (person_id) { + let ajax_options = { + type: 'GET', + url: `/api/people/${person_id}`, + accepts: 'application/json', + dataType: 'json' + }; + return $.ajax(ajax_options); + }, + create: function (person_id, note) { + let ajax_options = { + type: 'POST', + url: `/api/people/${person_id}/notes`, + accepts: 'application/json', + contentType: 'application/json', + dataType: 'json', + data: JSON.stringify(note) + }; + return $.ajax(ajax_options); + }, + update: function (person_id, note) { + let ajax_options = { + type: 'PUT', + url: `/api/people/${person_id}/notes/${note.note_id}`, + accepts: 'application/json', + contentType: 'application/json', + dataType: 'json', + data: JSON.stringify(note) + }; + return $.ajax(ajax_options); + }, + 'delete': function (person_id, note_id) { + let ajax_options = { + type: 'DELETE', + url: `/api/people/${person_id}/notes/${note_id}`, + accepts: 'application/json', + contentType: 'plain/text' + }; + return $.ajax(ajax_options); + } + }; +}()); + +// Create the view instance +ns.view = (function () { + 'use strict'; + + const NEW_NOTE = 0, + EXISTING_NOTE = 1; + + let $person_id = $('#person_id'), + $fname = $('#fname'), + $lname = $('#lname'), + $timestamp = $('#timestamp'), + $note_id = $('#note_id'), + $note = $('#note'), + $create = $('#create'), + $update = $('#update'), + $delete = $('#delete'), + $reset = $('#reset'); + + // return the API + return { + NEW_NOTE: NEW_NOTE, + EXISTING_NOTE: EXISTING_NOTE, + reset: function () { + $note_id.text(''); + $note.val('').focus(); + }, + update_editor: function (note) { + $note_id.text(note.note_id); + $note.val(note.content).focus(); + }, + set_button_states: function (state) { + if (state === NEW_NOTE) { + $create.prop('disabled', false); + $update.prop('disabled', true); + $delete.prop('disabled', true); + } else if (state === EXISTING_NOTE) { + $create.prop('disabled', true); + $update.prop('disabled', false); + $delete.prop('disabled', false); + } + }, + build_table: function (person) { + let source = $('#notes-table-template').html(), + template = Handlebars.compile(source), + html; + + // update the person data + $person_id.text(person.person_id); + $fname.text(person.fname); + $lname.text(person.lname); + $timestamp.text(person.timestamp); + + // clear the table + $('.notes table > tbody').empty(); + + // did we get a note array? + if (person.notes) { + + // Create the HTML from the template and notes + html = template({notes: person.notes}); + + // Append the html to the table + $('table').append(html); + } + }, + error: function (error_msg) { + $('.error') + .text(error_msg) + .css('visibility', 'visible'); + setTimeout(function () { + $('.error').fadeOut(); + }, 2000) + } + }; +}()); + +// Create the controller +ns.controller = (function (m, v) { + 'use strict'; + + let model = m, + view = v, + $url_person_id = $('#url_person_id'), + $url_note_id = $('#url_note_id'), + $note_id = $('#note_id'), + $note = $('#note'); + + // read the person data with notes + setTimeout(function () { + view.reset(); + model.read(parseInt($url_person_id.val())) + .done(function (data) { + view.build_table(data); + view.update_editor(data); + view.set_button_states(view.NEW_NOTE); + }) + .fail(function (xhr, textStatus, errorThrown) { + error_handler(xhr, textStatus, errorThrown); + }); + + if ($url_note_id.val() !== "") { + model.read_one(parseInt($url_person_id.val()), parseInt($url_note_id.val())) + .done(function (data) { + view.update_editor(data); + view.set_button_states(view.EXISTING_NOTE); + }) + .fail(function (xhr, textStatus, errorThrown) { + error_handler(xhr, textStatus, errorThrown); + }); + } + }, 100); + + // generic error handler + function error_handler(xhr, textStatus, errorThrown) { + let error_msg = `${textStatus}: ${errorThrown} - ${xhr.responseJSON.detail}`; + + view.error(error_msg); + console.log(error_msg); + } + + // initialize the button states + view.set_button_states(view.NEW_NOTE); + + // Validate input + function validate(note) { + return note !== ""; + } + + // Create our event handlers + $('#create').click(function (e) { + let note = $note.val(); + + e.preventDefault(); + + if (validate(note)) { + model.create(parseInt($('#url_person_id').val()), { + content: note + }) + .done(function (data) { + model.read(parseInt($('#url_person_id').val())) + .done(function(data) { + view.build_table(data); + }) + .fail(function(xhr, textStatus, errorThrown) { + error_handler(xhr, textStatus, errorThrown); + }); + view.reset(); + view.set_button_states(view.NEW_NOTE); + }) + .fail(function (xhr, textStatus, errorThrown) { + error_handler(xhr, textStatus, errorThrown); + }); + + } else { + alert('Problem with note input'); + } + }); + + $('#update').click(function (e) { + let person_id = parseInt($url_person_id.val()), + note_id = parseInt($note_id.text()), + note = $note.val(); + + e.preventDefault(); + + if (validate(note)) { + model.update(person_id, { + note_id: note_id, + content: note + }) + .done(function (data) { + model.read(data.person.person_id) + .done(function(data) { + view.build_table(data); + }) + .fail(function(xhr, textStatus, errorThrown) { + error_handler(xhr, textStatus, errorThrown); + }); + view.reset(); + view.set_button_states(view.NEW_NOTE); + }) + .fail(function (xhr, textStatus, errorThrown) { + error_handler(xhr, textStatus, errorThrown); + }); + + } else { + alert('Problem with first or last name input'); + } + }); + + $('#delete').click(function (e) { + let person_id = parseInt($url_person_id.val()), + note_id = parseInt($note_id.text()); + + e.preventDefault(); + + if (validate('placeholder', lname)) { + model.delete(person_id, note_id) + .done(function (data) { + model.read(parseInt($('#url_person_id').val())) + .done(function(data) { + view.build_table(data); + }) + .fail(function(xhr, textStatus, errorThrown) { + error_handler(xhr, textStatus, errorThrown); + }); + view.reset(); + view.set_button_states(view.NEW_NOTE); + }) + .fail(function (xhr, textStatus, errorThrown) { + error_handler(xhr, textStatus, errorThrown); + }); + + } else { + alert('Problem with first or last name input'); + } + }); + + $('#reset').click(function () { + view.reset(); + view.set_button_states(view.NEW_NOTE); + }) + + $('table').on('click', 'tbody tr', function (e) { + let $target = $(e.target).parent(), + note_id = $target.data('note_id'), + content = $target.data('content'); + + view.update_editor({ + note_id: note_id, + content: content, + }); + view.set_button_states(view.EXISTING_NOTE); + }); +}(ns.model, ns.view)); + + diff --git a/flask-connexion-rest-part-4/static/js/people.js b/flask-connexion-rest-part-4/static/js/people.js new file mode 100644 index 0000000000..4bd617dcc9 --- /dev/null +++ b/flask-connexion-rest-part-4/static/js/people.js @@ -0,0 +1,307 @@ +/* + * JavaScript file for the application to demonstrate + * using the API for the People SPA + */ + +// Create the namespace instance +let ns = {}; + +// Create the model instance +ns.model = (function () { + 'use strict'; + + // Return the API + return { + read_one: function (person_id) { + let ajax_options = { + type: 'GET', + url: `/api/people/${person_id}`, + accepts: 'application/json', + dataType: 'json' + }; + return $.ajax(ajax_options); + }, + read: function () { + let ajax_options = { + type: 'GET', + url: '/api/people', + accepts: 'application/json', + dataType: 'json' + }; + return $.ajax(ajax_options); + }, + create: function (person) { + let ajax_options = { + type: 'POST', + url: '/api/people', + accepts: 'application/json', + contentType: 'application/json', + dataType: 'json', + data: JSON.stringify(person) + }; + return $.ajax(ajax_options); + }, + update: function (person) { + let ajax_options = { + type: 'PUT', + url: `/api/people/${person.person_id}`, + accepts: 'application/json', + contentType: 'application/json', + dataType: 'json', + data: JSON.stringify(person) + }; + return $.ajax(ajax_options); + }, + 'delete': function (person_id) { + let ajax_options = { + type: 'DELETE', + url: `/api/people/${person_id}`, + accepts: 'application/json', + contentType: 'plain/text' + }; + return $.ajax(ajax_options); + } + }; +}()); + +// Create the view instance +ns.view = (function () { + 'use strict'; + + const NEW_NOTE = 0, + EXISTING_NOTE = 1; + + let $person_id = $('#person_id'), + $fname = $('#fname'), + $lname = $('#lname'), + $create = $('#create'), + $update = $('#update'), + $delete = $('#delete'), + $reset = $('#reset'); + + // return the API + return { + NEW_NOTE: NEW_NOTE, + EXISTING_NOTE: EXISTING_NOTE, + reset: function () { + $person_id.text(''); + $lname.val(''); + $fname.val('').focus(); + }, + update_editor: function (person) { + $person_id.text(person.person_id); + $lname.val(person.lname); + $fname.val(person.fname).focus(); + }, + set_button_state: function (state) { + if (state === NEW_NOTE) { + $create.prop('disabled', false); + $update.prop('disabled', true); + $delete.prop('disabled', true); + } else if (state === EXISTING_NOTE) { + $create.prop('disabled', true); + $update.prop('disabled', false); + $delete.prop('disabled', false); + } + }, + build_table: function (people) { + let source = $('#people-table-template').html(), + template = Handlebars.compile(source), + html; + + // clear the table + $('.people table > tbody').empty(); + + // did we get a people array? + if (people) { + + // Create the HTML from the template and people + html = template({people: people}) + + // Append the html to the table + $('table').append(html); + } + }, + error: function (error_msg) { + $('.error') + .text(error_msg) + .css('visibility', 'visible'); + setTimeout(function () { + $('.error').fadeOut(); + }, 2000) + } + }; +}()); + +// Create the controller +ns.controller = (function (m, v) { + 'use strict'; + + let model = m, + view = v, + $url_person_id = $('#url_person_id'), + $person_id = $('#person_id'), + $fname = $('#fname'), + $lname = $('#lname'); + + // Get the data from the model after the controller is done initializing + setTimeout(function () { + view.reset(); + model.read() + .done(function(data) { + view.build_table(data); + }) + .fail(function(xhr, textStatus, errorThrown) { + error_handler(xhr, textStatus, errorThrown); + }) + + if ($url_person_id.val() !== "") { + model.read_one(parseInt($url_person_id.val())) + .done(function(data) { + view.update_editor(data); + view.set_button_state(view.EXISTING_NOTE); + }) + .fail(function(xhr, textStatus, errorThrown) { + error_handler(xhr, textStatus, errorThrown); + }); + } + }, 100) + + // generic error handler + function error_handler(xhr, textStatus, errorThrown) { + let error_msg = `${textStatus}: ${errorThrown} - ${xhr.responseJSON.detail}`; + + view.error(error_msg); + console.log(error_msg); + } + // initialize the button states + view.set_button_state(view.NEW_NOTE); + + // Validate input + function validate(fname, lname) { + return fname !== "" && lname !== ""; + } + + // Create our event handlers + $('#create').click(function (e) { + let fname = $fname.val(), + lname = $lname.val(); + + e.preventDefault(); + + if (validate(fname, lname)) { + model.create({ + 'fname': fname, + 'lname': lname, + }) + .done(function(data) { + model.read() + .done(function(data) { + view.build_table(data); + }) + .fail(function(xhr, textStatus, errorThrown) { + error_handler(xhr, textStatus, errorThrown); + }); + view.set_button_state(view.NEW_NOTE); + }) + .fail(function(xhr, textStatus, errorThrown) { + error_handler(xhr, textStatus, errorThrown); + }); + + view.reset(); + + } else { + alert('Problem with first or last name input'); + } + }); + + $('#update').click(function (e) { + let person_id = parseInt($person_id.text()), + fname = $fname.val(), + lname = $lname.val(); + + e.preventDefault(); + + if (validate(fname, lname)) { + model.update({ + person_id: person_id, + fname: fname, + lname: lname, + }) + .done(function(data) { + model.read() + .done(function(data) { + view.build_table(data); + }) + .fail(function(xhr, textStatus, errorThrown) { + error_handler(xhr, textStatus, errorThrown); + }); + view.reset(); + view.set_button_state(view.NEW_NOTE); + }) + .fail(function(xhr, textStatus, errorThrown) { + error_handler(xhr, textStatus, errorThrown); + }) + + } else { + alert('Problem with first or last name input'); + } + e.preventDefault(); + }); + + $('#delete').click(function (e) { + let person_id = parseInt($person_id.text()); + + e.preventDefault(); + + if (validate('placeholder', lname)) { + model.delete(person_id) + .done(function(data) { + model.read() + .done(function(data) { + view.build_table(data); + }) + .fail(function(xhr, textStatus, errorThrown) { + error_handler(xhr, textStatus, errorThrown); + }); + view.reset(); + view.set_button_state(view.NEW_NOTE); + }) + .fail(function(xhr, textStatus, errorThrown) { + error_handler(xhr, textStatus, errorThrown); + }); + + } else { + alert('Problem with first or last name input'); + } + }); + + $('#reset').click(function () { + view.reset(); + view.set_button_state(view.NEW_NOTE); + }) + + $('table').on('click', 'tbody tr', function (e) { + let $target = $(e.target).parent(), + person_id = $target.data('person_id'), + fname = $target.data('fname'), + lname = $target.data('lname'); + + view.update_editor({ + person_id: person_id, + fname: fname, + lname: lname, + }); + view.set_button_state(view.EXISTING_NOTE); + }); + + $('table').on('dblclick', 'tbody tr', function (e) { + let $target = $(e.target), + person_id = $target.parent().attr('data-person_id'); + + window.location.href = `/people/${person_id}/notes`; + + }); +}(ns.model, ns.view)); + + diff --git a/flask-connexion-rest-part-4/swagger.yml b/flask-connexion-rest-part-4/swagger.yml new file mode 100644 index 0000000000..596bd61da7 --- /dev/null +++ b/flask-connexion-rest-part-4/swagger.yml @@ -0,0 +1,383 @@ +swagger: "2.0" +info: + description: This is the swagger file that goes with our server code + version: "1.0.0" + title: Swagger Rest Article +consumes: + - application/json +produces: + - application/json + +basePath: /api + +# Paths supported by the server application +paths: + /people: + get: + operationId: people.read_all + tags: + - People + summary: Read the entire set of people, sorted by last name + description: Read the entire set of people, sorted by last name + responses: + 200: + description: Successfully read people set operation + schema: + type: array + items: + properties: + person_id: + type: integer + description: Id of the person + fname: + type: string + description: First name of the person + lname: + type: string + description: Last name of the person + timestamp: + type: string + description: Create/Update timestamp of the person + notes: + type: array + items: + properties: + person_id: + type: integer + description: Id of person this note is associated with + note_id: + type: integer + description: Id of this note + content: + type: string + description: content of this note + timestamp: + type: string + description: Create/Update timestamp of this note + + post: + operationId: people.create + tags: + - People + summary: Create a person + description: Create a new person + parameters: + - name: person + in: body + description: Person to create + required: True + schema: + type: object + properties: + fname: + type: string + description: First name of person to create + lname: + type: string + description: Last name of person to create + responses: + 201: + description: Successfully created person + schema: + properties: + person_id: + type: integer + description: Id of the person + fname: + type: string + description: First name of the person + lname: + type: string + description: Last name of the person + timestamp: + type: string + description: Creation/Update timestamp of the person record + + /people/{person_id}: + get: + operationId: people.read_one + tags: + - People + summary: Read one person + description: Read one person + parameters: + - name: person_id + in: path + description: Id of the person to get + type: integer + required: True + responses: + 200: + description: Successfully read person from people data operation + schema: + type: object + properties: + person_id: + type: string + description: Id of the person + fname: + type: string + description: First name of the person + lname: + type: string + description: Last name of the person + timestamp: + type: string + description: Creation/Update timestamp of the person record + notes: + type: array + items: + properties: + person_id: + type: integer + description: Id of person this note is associated with + note_id: + type: integer + description: Id of this note + content: + type: string + description: content of this note + timestamp: + type: string + description: Create/Update timestamp of this note + + put: + operationId: people.update + tags: + - People + summary: Update a person + description: Update a person + parameters: + - name: person_id + in: path + description: Id the person to update + type: integer + required: True + - name: person + in: body + schema: + type: object + properties: + fname: + type: string + description: First name of the person + lname: + type: string + description: Last name of the person + responses: + 200: + description: Successfully updated person + schema: + properties: + person_id: + type: integer + description: Id of the person in the database + fname: + type: string + description: First name of the person + lname: + type: string + description: Last name of the person + timestamp: + type: string + description: Creation/Update timestamp of the person record + + delete: + operationId: people.delete + tags: + - People + summary: Delete a person from the people list + description: Delete a person + parameters: + - name: person_id + in: path + type: integer + description: Id of the person to delete + required: true + responses: + 200: + description: Successfully deleted a person + + /notes: + get: + operationId: notes.read_all + tags: + - Notes + summary: Read the entire set of notes for all people, sorted by timestamp + description: Read the entire set of notes for all people, sorted by timestamp + responses: + 200: + description: Successfully read notes for all people operation + schema: + type: array + items: + properties: + note_id: + type: integer + description: Id of the note + content: + type: string + description: Content of the note + timestamp: + type: string + description: Create/Update timestamp of the note + person: + type: object + properties: + person_id: + type: integer + description: Id of associated person + fname: + type: string + description: Frist name of associated person + lname: + type: string + description: Last name of associated person + timestamp: + type: string + description: Create/Update timestamp of associated person + + + /people/{person_id}/notes: + post: + operationId: notes.create + tags: + - Notes + summary: Create a note associated with a person + description: Create a note associated with a person + parameters: + - name: person_id + in: path + description: Id of person associated with note + type: integer + required: True + - name: note + in: body + description: Text content of the note to create + required: True + schema: + type: object + properties: + content: + type: string + description: Text of the note to create + responses: + 201: + description: Successfully created a note + schema: + properties: + person_id: + type: integer + description: Id of the person associated with the note + note_id: + type: integer + description: Id of the created note + content: + type: string + description: Text content of the note + timestamp: + type: string + description: Creation/Update timestamp of the person record + + /people/{person_id}/notes/{note_id}: + get: + operationId: notes.read_one + tags: + - Notes + summary: Read a particular note associated with a person + description: Read a particular note associated with a person + parameters: + - name: person_id + in: path + description: Id of person associated with note + type: integer + required: True + - name: note_id + in: path + description: Id of note + type: integer + required: True + responses: + 200: + description: Successfully read note for a person + schema: + type: object + properties: + note_id: + type: integer + description: Id of the note + person_id: + type: integer + description: Id of the person note associated with + content: + type: string + description: Text content of the note + timestamp: + type: string + description: Creation/Update timestamp of the note record + + put: + operationId: notes.update + tags: + - Notes + summary: Update a note associated with a person + description: Update a note associated with a person + parameters: + - name: person_id + in: path + description: Id the person to update + type: integer + required: True + - name: note_id + in: path + description: Id of the note associated with a person + type: integer + required: True + - name: note + in: body + schema: + type: object + properties: + content: + type: string + description: Text content of the note to updated + responses: + 200: + description: Successfully updated note + schema: + properties: + note_id: + type: string + description: Id of the note associated with a person + person_id: + type: integer + description: Id of the person in the database + content: + type: string + description: Text content of the updated note + timestamp: + type: string + description: Creation/Update timestamp of the note record + + delete: + operationId: notes.delete + tags: + - Notes + summary: Delete a note associated with a person + description: Delete a note associated with a person + parameters: + - name: person_id + in: path + description: Id of person associated with note + type: integer + required: True + - name: note_id + in: path + description: Id of note + type: integer + required: True + responses: + 200: + description: Successfully deleted a note + + diff --git a/flask-connexion-rest-part-4/templates/home.html b/flask-connexion-rest-part-4/templates/home.html new file mode 100644 index 0000000000..eb9f4229ab --- /dev/null +++ b/flask-connexion-rest-part-4/templates/home.html @@ -0,0 +1,46 @@ +{% extends "parent.html" %} +{% block title %}Home Blog{% endblock %} +{% block head %} + {{ super() }} + +{% endblock %} + +{% block page_name %}Mini-Blog Page Demo{% endblock %} + +{% block body %} +
+ + + + + + + + + +
People Blog Entries
Creation/Update TimestampPersonNote
+
+
+
+ + +{% raw %} + +{% endraw %} + +{% endblock %} + +{% block javascript %} +{{ super() }} + +{% endblock %} diff --git a/flask-connexion-rest-part-4/templates/notes.html b/flask-connexion-rest-part-4/templates/notes.html new file mode 100644 index 0000000000..3d36ecce63 --- /dev/null +++ b/flask-connexion-rest-part-4/templates/notes.html @@ -0,0 +1,83 @@ +{% extends "parent.html" %} + +{% block title %}Notes{% endblock %} + +{% block head %} +{{ super() }} + +{% endblock %} + +{% block page_name %}Person Note Create/Update/Delete Page{% endblock %} + +{% block body %} +
+ + +
+
+ Person ID: + +
+
+ First Name: + +
+
+ Last Name: + +
+
+ Timestamp: + +
+
+
+
+ Note ID: + +
+ +
+ + + + +
+
+
+ + + + + + + + + + +
Notes
Update TimeContent
+
+
+
+ +{% raw %} + +{% endraw %} + +{% endblock %} + +{% block javascript %} +{{ super() }} + +{% endblock %} \ No newline at end of file diff --git a/flask-connexion-rest-part-4/templates/parent.html b/flask-connexion-rest-part-4/templates/parent.html new file mode 100644 index 0000000000..04cd903901 --- /dev/null +++ b/flask-connexion-rest-part-4/templates/parent.html @@ -0,0 +1,47 @@ + + + + + {% block head %} + {% block title %}{% endblock %} Page + + + + {% endblock %} + + + + +{% block body %} +{% endblock %} + + +{% block javascript %} + +{% endblock %} + + \ No newline at end of file diff --git a/flask-connexion-rest-part-4/templates/people.html b/flask-connexion-rest-part-4/templates/people.html new file mode 100644 index 0000000000..a50f4769af --- /dev/null +++ b/flask-connexion-rest-part-4/templates/people.html @@ -0,0 +1,67 @@ +{% extends "parent.html" %} +{% block title %}People{% endblock %} +{% block head %} + {{ super() }} + +{% endblock %} + +{% block page_name %}Person Create/Update/Delete Page{% endblock %} + +{% block body %} +
+ +
+
+ Person ID: + +
+ +
+ +
+ + + + +
+
+ + + + + + + + +
People
Creation/Update TimestampPerson
+
+
+
+
+
+
+ + +{% raw %} + +{% endraw %} + +{% endblock %} + +{% block javascript %} +{{ super() }} + +{% endblock %} From ec63908e0779debdba01fc976ba984134696880c Mon Sep 17 00:00:00 2001 From: Doug Farrell Date: Mon, 20 May 2019 15:51:33 -0400 Subject: [PATCH 2/7] Re-wrote the JavaScript code for all three SPA's to use native JavaScript rather than jQuery, etc. This is the result, which works! Also, centralized some of the CSS code under the parent.css file. --- .../static/css/notes.css | 45 -- .../static/css/parent.css | 51 +- .../static/css/people.css | 48 -- flask-connexion-rest-part-4/static/js/home.js | 211 +++---- .../static/js/notes.js | 554 +++++++++-------- .../static/js/people.js | 557 +++++++++--------- .../templates/home.html | 18 +- .../templates/notes.html | 16 +- .../templates/parent.html | 17 - .../templates/people.html | 18 +- 10 files changed, 672 insertions(+), 863 deletions(-) diff --git a/flask-connexion-rest-part-4/static/css/notes.css b/flask-connexion-rest-part-4/static/css/notes.css index 0eaa205560..be0652ed3d 100644 --- a/flask-connexion-rest-part-4/static/css/notes.css +++ b/flask-connexion-rest-part-4/static/css/notes.css @@ -45,26 +45,10 @@ div.notes { border: 2px solid darkgrey; } -table { - width: 100%; - border-collapse: collapse; -} - -table caption { - font-weight: bold; - padding: 10px 0 10px 0; - border-bottom: 2px solid darkgrey; - background-color: antiquewhite; -} - .blog table thead { padding: 10px 0 10px 0; } -th:not(:last-child), td:not(:last-child) { - border-right: 2px solid darkgrey; -} - td.name { text-align: center; width: 175px; @@ -79,32 +63,3 @@ td.content { text-align: left; padding-left: 5px; } - -tbody tr:nth-child(odd) { - background-color: gainsboro; -} - -tbody tr { - cursor: pointer; - height: 33px; -} - -thead tr { - height: 33px; - border-bottom: 2px solid darkgrey; -} - -tbody tr:not(:last-child){ - border-bottom: 2px solid darkgrey; -} - -tbody tr:hover { - background-color: powderblue; -} -.content { - width: 70%; -} - -.timestamp { - width: 30%; -} diff --git a/flask-connexion-rest-part-4/static/css/parent.css b/flask-connexion-rest-part-4/static/css/parent.css index de465c69e5..fb08763195 100644 --- a/flask-connexion-rest-part-4/static/css/parent.css +++ b/flask-connexion-rest-part-4/static/css/parent.css @@ -42,13 +42,62 @@ body, .ui-btn { padding: 10px; } +table { + width: 100%; + border-collapse: collapse; +} + +table caption { + font-weight: bold; + padding: 10px 0 10px 0; + border-bottom: 2px solid darkgrey; + background-color: antiquewhite; +} + +th:not(:last-child), td:not(:last-child) { + border-right: 2px solid darkgrey; +} + + +tbody tr:nth-child(odd) { + background-color: gainsboro; +} + +tbody tr { + cursor: pointer; + height: 33px; +} + +thead tr { + height: 33px; + border-bottom: 2px solid darkgrey; +} + +tbody tr:not(:last-child){ + border-bottom: 2px solid darkgrey; +} + +tbody tr:hover { + background-color: powderblue; +} + .error { width: 50%; margin-left: auto; margin-right: auto; padding: 5px; + text-align: center; border: 1px solid lightgrey; border-radius: 3px; background-color: #fbb; - visibility: hidden; + opacity: 0; +} + +.visible { + opacity: 1; +} + +.hidden { + opacity: 0; + transition: visibility 0s 2s, opacity 2s linear; } \ No newline at end of file diff --git a/flask-connexion-rest-part-4/static/css/people.css b/flask-connexion-rest-part-4/static/css/people.css index 285aefc1cf..2395b23a93 100644 --- a/flask-connexion-rest-part-4/static/css/people.css +++ b/flask-connexion-rest-part-4/static/css/people.css @@ -33,30 +33,6 @@ div.people { border: 2px solid darkgrey; } -table { - width: 100%; - border-collapse: collapse; -} - -.people table caption { - font-weight: bold; - padding: 10px 0 10px 0; - border-bottom: 2px solid darkgrey; - background-color: antiquewhite; -} - -.people table thead { - padding: 10px 0 10px 0; -} - -th:not(:last-child), td:not(:last-child) { - border-right: 2px solid darkgrey; -} - -tr:nth-child(even) { - background-color: gainsboro; -} - td { text-align: center; } @@ -69,27 +45,3 @@ tbody td.name { cursor: pointer; height: 33px; } - -thead tr { - height: 33px; - border-bottom: 2px solid darkgrey; -} - -tbody tr:not(:last-child){ - border-bottom: 2px solid darkgrey; -} - -tbody tr:hover { - background-color: powderblue; -} - -.error { - width: 50%; - margin-left: auto; - margin-right: auto; - padding: 5px; - border: 1px solid lightgrey; - border-radius: 3px; - background-color: #fbb; - visibility: hidden; -} \ No newline at end of file diff --git a/flask-connexion-rest-part-4/static/js/home.js b/flask-connexion-rest-part-4/static/js/home.js index d7f06d6435..ceeba494ad 100644 --- a/flask-connexion-rest-part-4/static/js/home.js +++ b/flask-connexion-rest-part-4/static/js/home.js @@ -2,27 +2,25 @@ * JavaScript file for the Home page */ -"use strict"; - +/* jshint esversion: 8 */ /** * This is the model class which provides access to the server REST API * @type {{}} */ class Model { - options; - constructor () { - this.options = { + async read() { + let options = { method: "GET", cache: "no-cache", headers: { "Content-Type": "application/json" } }; - } - - read() { - return fetch("/api/notes", this.options) + // call the REST endpoint and wait for data + let response = await fetch("/api/notes", options); + let data = await response.json(); + return data; } } @@ -32,161 +30,92 @@ class Model { */ class View { constructor() { - this._table = document.querySelector(".blog table"); - console.log("hello"); + this.table = document.querySelector(".blog table"); + this.error = document.querySelector(".error"); } - build_table(data) { - let tbody = this._table.createTBody(); - tbody.innerHTML = ` - - Hello - there - - - Doug - Farrell - - `; + build_table(notes) { + let tbody = this.table.createTBody(); + let html = ""; + + // iterate over the notes and build the table + notes.forEach((note) => { + html += ` + + ${note.timestamp} + ${note.person.fname} ${note.person.lname} + ${note.content} + `; + }); + // replace the tbody with our new content + tbody.innerHTML = html; } - error(error_msg) { - + errorMessage(message) { + this.error.innerHTML = message; + this.error.classList.remove("hidden"); + this.error.classList.add("visible"); + setTimeout(() => { + this.error.classList.remove("visible"); + this.error.classList.add("hidden"); + }, 2000); } } /** - * This is the controller which provides for the user interaction + * This is the controller class for the user interaction */ class Controller { - - initialize(model, view) { + constructor(model, view) { this.model = model; this.view = view; - this.view.build_table(); - } -} - -/** - * This class defines our namespace container - * @type {{}} - */ -class NameSpace{ - constructor(model, view, controller) { - this._model = model; - this._view = view; - this._controller = controller; - this._controller.initialize(this._model, this._view); + this.initialize(); } -}; - -/** - * Create the namespace object - * @type {{}} - */ -const nameSpace = new NameSpace(new Model(), new View(), new Controller()); -debugger; - - + async initialize() { + try { + let notes = await this.model.read(); + this.view.build_table(notes); + } catch(err) { + this.view.errorMessage(err); + } + // handle application events + document.querySelector("table tbody").addEventListener("dblclick", (evt) => { + let target = evt.target, + parent = target.parentElement; + // is this the name td? + if (target) { + let person_id = parent.getAttribute("data-person_id"); -// Create the namespace instance -let ns = {}; + window.location = `/people/${person_id}`; -// Create the model instance -ns.model = (function () { - 'use strict'; + // is this the content td + } else if (true) { + let person_id = parent.getAttribute("data-person_id"), + note_id = parent.getAttribute("data-note_id"); - // Return the API - return { - 'read': function () { - let ajax_options = { - type: 'GET', - url: '/api/notes', - accepts: 'application/json', - dataType: 'json' - }; - return $.ajax(ajax_options); - } - }; -}()); - - -// Create the view instance -ns.view = (function () { - 'use strict'; + window.location = `people/${person_id}/notes/${note_id}`; + } + }); + } +} - var $table = $(".blog table"); +/** + * Create the namespace container for the model, view and controller + */ +const ns = (function() { + "use strict"; - // Return the API + const model = new Model(); + const view = new View(); + const controller = new Controller(model, view); return { - build_table: function (data) { - let source = $('#blog-table-template').html(), - template = Handlebars.compile(source), - html; - - // Create the HTML from the template and notes - html = template({notes: data}); - - // Append the rows to the table tbody - $table.append(html); - }, - error: function (error_msg) { - $('.error') - .text(error_msg) - .css('visibility', 'visible'); - setTimeout(function () { - $('.error').fadeOut(); - }, 2000) - } + model: model, + view: view, + controller: controller }; }()); -// Create the controller instance -ns.controller = (function (m, v) { - 'use strict'; - - let model = m, - view = v; - - // Get the note data from the model after the controller is done initializing - setTimeout(function () { - - // Attach event handlers to the promise returned by model.read() - model.read() - .done(function (data) { - view.build_table(data); - }) - .fail(function (xhr, textStatus, errorThrown) { - error_handler(xhr, textStatus, errorThrown); - }); - }, 100); - - // generic error handler - function error_handler(xhr, textStatus, errorThrown) { - let error_msg = `${textStatus}: ${errorThrown} - ${xhr.responseJSON.detail}`; - - view.error(error_msg); - console.log(error_msg); - } - - // handle application events - $('table').on('dblclick', 'tbody td.name', function (e) { - let $target = $(e.target).parent(), - person_id = $target.data('person_id'); - - window.location = `/people/${person_id}`; - - }); - - $('table').on('dblclick', 'tbody td.content', function (e) { - let $target = $(e.target).parent(), - person_id = $target.data('person_id'), - note_id = $target.data('note_id'); - - window.location = `people/${person_id}/notes/${note_id}`; - }); -}(ns.model, ns.view)); \ No newline at end of file diff --git a/flask-connexion-rest-part-4/static/js/notes.js b/flask-connexion-rest-part-4/static/js/notes.js index 4e40926b26..967695df87 100644 --- a/flask-connexion-rest-part-4/static/js/notes.js +++ b/flask-connexion-rest-part-4/static/js/notes.js @@ -1,303 +1,295 @@ -/* - * JavaScript file for the application to demonstrate - * using the API for creating, updating and deleting notes +/** + * JavaScript file for the Notes page */ -// Create the namespace instance -let ns = {}; +/* jshint esversion: 8 */ -// Create the model instance -ns.model = (function () { - 'use strict'; - - // Return the API - return { - read_one: function (person_id, note_id) { - let ajax_options = { - type: 'GET', - url: `/api/people/${person_id}/notes/${note_id}`, - accepts: 'application/json', - dataType: 'json' - }; - return $.ajax(ajax_options); - }, - read: function (person_id) { - let ajax_options = { - type: 'GET', - url: `/api/people/${person_id}`, - accepts: 'application/json', - dataType: 'json' - }; - return $.ajax(ajax_options); - }, - create: function (person_id, note) { - let ajax_options = { - type: 'POST', - url: `/api/people/${person_id}/notes`, - accepts: 'application/json', - contentType: 'application/json', - dataType: 'json', - data: JSON.stringify(note) - }; - return $.ajax(ajax_options); - }, - update: function (person_id, note) { - let ajax_options = { - type: 'PUT', - url: `/api/people/${person_id}/notes/${note.note_id}`, - accepts: 'application/json', - contentType: 'application/json', - dataType: 'json', - data: JSON.stringify(note) - }; - return $.ajax(ajax_options); - }, - 'delete': function (person_id, note_id) { - let ajax_options = { - type: 'DELETE', - url: `/api/people/${person_id}/notes/${note_id}`, - accepts: 'application/json', - contentType: 'plain/text' - }; - return $.ajax(ajax_options); - } - }; -}()); - -// Create the view instance -ns.view = (function () { - 'use strict'; - - const NEW_NOTE = 0, - EXISTING_NOTE = 1; - - let $person_id = $('#person_id'), - $fname = $('#fname'), - $lname = $('#lname'), - $timestamp = $('#timestamp'), - $note_id = $('#note_id'), - $note = $('#note'), - $create = $('#create'), - $update = $('#update'), - $delete = $('#delete'), - $reset = $('#reset'); - - // return the API - return { - NEW_NOTE: NEW_NOTE, - EXISTING_NOTE: EXISTING_NOTE, - reset: function () { - $note_id.text(''); - $note.val('').focus(); - }, - update_editor: function (note) { - $note_id.text(note.note_id); - $note.val(note.content).focus(); - }, - set_button_states: function (state) { - if (state === NEW_NOTE) { - $create.prop('disabled', false); - $update.prop('disabled', true); - $delete.prop('disabled', true); - } else if (state === EXISTING_NOTE) { - $create.prop('disabled', true); - $update.prop('disabled', false); - $delete.prop('disabled', false); +/** + * This is the model class which provides access to the server REST API + * @type {{}} + */ +class Model { + async read(person_id) { + let options = { + method: "GET", + cache: "no-cache", + headers: { + "Content-Type": "application/json" } - }, - build_table: function (person) { - let source = $('#notes-table-template').html(), - template = Handlebars.compile(source), - html; - - // update the person data - $person_id.text(person.person_id); - $fname.text(person.fname); - $lname.text(person.lname); - $timestamp.text(person.timestamp); - - // clear the table - $('.notes table > tbody').empty(); - - // did we get a note array? - if (person.notes) { - - // Create the HTML from the template and notes - html = template({notes: person.notes}); - - // Append the html to the table - $('table').append(html); + }; + // Call the REST endpoint and wait for data + let response = await fetch(`/api/people/${person_id}`, options); + let data = await response.json(); + return data; + } + async readOne(person_id, note_id) { + let options = { + method: "GET", + cache: "no-cache", + headers: { + "Content-Type": "application/json", + "accepts": "application/json" } - }, - error: function (error_msg) { - $('.error') - .text(error_msg) - .css('visibility', 'visible'); - setTimeout(function () { - $('.error').fadeOut(); - }, 2000) - } - }; -}()); - -// Create the controller -ns.controller = (function (m, v) { - 'use strict'; - - let model = m, - view = v, - $url_person_id = $('#url_person_id'), - $url_note_id = $('#url_note_id'), - $note_id = $('#note_id'), - $note = $('#note'); + }; + // Call the REST endpoint and wait for data + let response = await fetch(`/api/people/${person_id}/notes/${note_id}`, options); + let data = await response.json(); + return data; + } + async create(person_id, note) { + let options = { + method: "POST", + cache: "no-cache", + headers: { + "Content-Type": "application/json", + "accepts": "application/json" + }, + body: JSON.stringify(note) + }; + // Call the REST endpoint and wait for data + let response = await fetch(`/api/people/${person_id}/notes`, options); + let data = await response.json(); + return data; + } + async update(person_id, note) { + let options = { + method: "PUT", + cache: "no-cache", + headers: { + "Content-Type": "application/json", + "accepts": "application/json" + }, + body: JSON.stringify(note) + }; + // Call the REST endpoint and wait for data + let response = await fetch(`/api/people/${person_id}/notes/${note.note_id}`, options); + let data = await response.json(); + return data; + } + async delete(person_id, note_id) { + let options = { + method: "DELETE", + cache: "no-cache", + headers: { + "Content-Type": "application/json", + "accepts": "application/json" + } + }; + // Call the REST endpoint and wait for data + let response = await fetch(`/api/people/${person_id}/notes/${note_id}`, options); + return response; + } +} - // read the person data with notes - setTimeout(function () { - view.reset(); - model.read(parseInt($url_person_id.val())) - .done(function (data) { - view.build_table(data); - view.update_editor(data); - view.set_button_states(view.NEW_NOTE); - }) - .fail(function (xhr, textStatus, errorThrown) { - error_handler(xhr, textStatus, errorThrown); - }); - if ($url_note_id.val() !== "") { - model.read_one(parseInt($url_person_id.val()), parseInt($url_note_id.val())) - .done(function (data) { - view.update_editor(data); - view.set_button_states(view.EXISTING_NOTE); - }) - .fail(function (xhr, textStatus, errorThrown) { - error_handler(xhr, textStatus, errorThrown); - }); +/** + * This is the view class which provides access to the DOM + */ +class View { + constructor() { + this.NEW_NOTE = 0; + this.EXISTING_NOTE = 1; + this.table = document.querySelector(".notes table"); + this.error = document.querySelector(".error"); + this.person_id = document.getElementById("person_id"); + this.fname = document.getElementById("fname"); + this.lname = document.getElementById("lname"); + this.timestamp = document.getElementById("timestamp"); + this.note_id = document.getElementById("note_id"); + this.note = document.getElementById("note"); + this.createButton = document.getElementById("create"); + this.updateButton = document.getElementById("update"); + this.deleteButton = document.getElementById("delete"); + this.resetButton = document.getElementById("reset"); + } + reset() { + this.note_id.textContent = ""; + this.note.value = ""; + this.note.focus(); + } + updateEditor(note) { + this.note_id.textContent = note.note_id; + this.note.value = note.content; + this.note.focus(); + } + setButtonState(state) { + if (state === this.NEW_NOTE) { + this.createButton.disabled = false; + this.updateButton.disabled = true; + this.deleteButton.disabled = true; + } else if (state === this.EXISTING_NOTE) { + this.createButton.disabled = true; + this.updateButton.disabled = false; + this.deleteButton.disabled = false; } - }, 100); - - // generic error handler - function error_handler(xhr, textStatus, errorThrown) { - let error_msg = `${textStatus}: ${errorThrown} - ${xhr.responseJSON.detail}`; - - view.error(error_msg); - console.log(error_msg); } - - // initialize the button states - view.set_button_states(view.NEW_NOTE); - - // Validate input - function validate(note) { - return note !== ""; + buildTable(person) { + let tbody, + html = ""; + + // Update the person data + this.person_id.textContent = person.person_id; + this.fname.textContent = person.fname; + this.lname.textContent = person.lname; + this.timestamp.textContent = person.timestamp; + + // Iterate over the notes and build the table + person.notes.forEach((note) => { + html += ` + + ${note.timestamp} + ${note.content} + `; + }); + // Is there currently a tbody in the table? + if (this.table.tBodies.length !== 0) { + this.table.removeChild(this.table.getElementsByTagName("tbody")[0]); + } + // Update tbody with our new content + tbody = this.table.createTBody(); + tbody.innerHTML = html; } + errorMessage(error_msg) { + let error = document.querySelector(".error"); + + error.innerHTML = error_msg; + error.classList.add("visible"); + error.classList.remove("hidden"); + setTimeout(() => { + error.classList.add("hidden"); + error.classList.remove("visible"); + }, 2000); + } +} - // Create our event handlers - $('#create').click(function (e) { - let note = $note.val(); - - e.preventDefault(); - if (validate(note)) { - model.create(parseInt($('#url_person_id').val()), { - content: note - }) - .done(function (data) { - model.read(parseInt($('#url_person_id').val())) - .done(function(data) { - view.build_table(data); - }) - .fail(function(xhr, textStatus, errorThrown) { - error_handler(xhr, textStatus, errorThrown); - }); - view.reset(); - view.set_button_states(view.NEW_NOTE); - }) - .fail(function (xhr, textStatus, errorThrown) { - error_handler(xhr, textStatus, errorThrown); - }); +/** + * This is the controller class for the user interaction + */ +class Controller { + constructor(model, view) { + this.model = model; + this.view = view; - } else { - alert('Problem with note input'); + this.initialize(); + } + async initialize() { + await this.initializeTable(); + this.initializeTableEvents(); + this.initializeCreateEvent(); + this.initializeUpdateEvent(); + this.initializeDeleteEvent(); + this.initializeResetEvent(); + } + async initializeTable() { + try { + let url_person_id = parseInt(document.getElementById("url_person_id").value), + url_note_id = parseInt(document.getElementById("url_note_id").value), + person = await this.model.read(url_person_id); + + this.view.buildTable(person); + + // Did we navigate here with a note selected? + if (url_note_id) { + let note = await this.model.readOne(url_person_id, url_note_id); + this.view.updateEditor(note); + this.view.setButtonState(this.view.EXISTING_NOTE); + + // Otherwise, nope, so leave the editor blank + } else { + this.view.reset(); + this.view.setButtonState(this.view.NEW_NOTE); + } + this.initializeTableEvents(); + } catch (err) { + this.view.errorMessage(err); } - }); - - $('#update').click(function (e) { - let person_id = parseInt($url_person_id.val()), - note_id = parseInt($note_id.text()), - note = $note.val(); - - e.preventDefault(); + } + initializeTableEvents() { + document.querySelector("table tbody").addEventListener("click", (evt) => { + let target = evt.target.parentElement, + note_id = target.getAttribute("data-note_id"), + content = target.getAttribute("data-content"); - if (validate(note)) { - model.update(person_id, { + this.view.updateEditor({ note_id: note_id, - content: note - }) - .done(function (data) { - model.read(data.person.person_id) - .done(function(data) { - view.build_table(data); - }) - .fail(function(xhr, textStatus, errorThrown) { - error_handler(xhr, textStatus, errorThrown); - }); - view.reset(); - view.set_button_states(view.NEW_NOTE); - }) - .fail(function (xhr, textStatus, errorThrown) { - error_handler(xhr, textStatus, errorThrown); + content: content + }); + this.view.setButtonState(this.view.EXISTING_NOTE); + }); + } + initializeCreateEvent() { + document.getElementById("create").addEventListener("click", async (evt) => { + let url_person_id = parseInt(document.getElementById("person_id").textContent), + note = document.getElementById("note").value; + + evt.preventDefault(); + try { + await this.model.create(url_person_id, { + content: note }); - - } else { - alert('Problem with first or last name input'); - } - }); - - $('#delete').click(function (e) { - let person_id = parseInt($url_person_id.val()), - note_id = parseInt($note_id.text()); - - e.preventDefault(); - - if (validate('placeholder', lname)) { - model.delete(person_id, note_id) - .done(function (data) { - model.read(parseInt($('#url_person_id').val())) - .done(function(data) { - view.build_table(data); - }) - .fail(function(xhr, textStatus, errorThrown) { - error_handler(xhr, textStatus, errorThrown); - }); - view.reset(); - view.set_button_states(view.NEW_NOTE); - }) - .fail(function (xhr, textStatus, errorThrown) { - error_handler(xhr, textStatus, errorThrown); + await this.initializeTable(); + } catch(err) { + this.view.errorMessage(err); + } + }); + } + initializeUpdateEvent() { + document.getElementById("update").addEventListener("click", async (evt) => { + let person_id = parseInt(document.getElementById("person_id").textContent), + note_id = parseInt(document.getElementById("note_id").textContent), + note = document.getElementById("note").value; + + evt.preventDefault(); + try { + await this.model.update(person_id, { + person_id: person_id, + note_id: note_id, + content: note }); + await this.initializeTable(); + } catch(err) { + this.view.errorMessage(err); + } + }); + } + initializeDeleteEvent() { + document.getElementById("delete").addEventListener("click", async (evt) => { + let person_id = parseInt(document.getElementById("person_id").textContent), + note_id = parseInt(document.getElementById("note_id").textContent); + + evt.preventDefault(); + try { + await this.model.delete(person_id, note_id); + await this.initializeTable(); + } catch(err) { + this.view.errorMessage(err); + } + }); + } + initializeResetEvent() { + document.getElementById("reset").addEventListener("click", async (evt) => { + evt.preventDefault(); + this.view.reset(); + this.view.setButtonState(this.view.NEW_NOTE); + }); + } +} - } else { - alert('Problem with first or last name input'); - } - }); - - $('#reset').click(function () { - view.reset(); - view.set_button_states(view.NEW_NOTE); - }) - - $('table').on('click', 'tbody tr', function (e) { - let $target = $(e.target).parent(), - note_id = $target.data('note_id'), - content = $target.data('content'); +/** + * Create the namespace container for the model, view and controller + */ +const ns = (function() { + "use strict"; - view.update_editor({ - note_id: note_id, - content: content, - }); - view.set_button_states(view.EXISTING_NOTE); - }); -}(ns.model, ns.view)); + const model = new Model(); + const view = new View(); + const controller = new Controller(model, view); + return { + model: model, + view: view, + controller: controller + } +}()); diff --git a/flask-connexion-rest-part-4/static/js/people.js b/flask-connexion-rest-part-4/static/js/people.js index 4bd617dcc9..ef842af526 100644 --- a/flask-connexion-rest-part-4/static/js/people.js +++ b/flask-connexion-rest-part-4/static/js/people.js @@ -1,307 +1,302 @@ -/* - * JavaScript file for the application to demonstrate - * using the API for the People SPA +/** + * JavaScript file for the People page */ -// Create the namespace instance -let ns = {}; +/* jshint esversion: 8 */ -// Create the model instance -ns.model = (function () { - 'use strict'; - - // Return the API - return { - read_one: function (person_id) { - let ajax_options = { - type: 'GET', - url: `/api/people/${person_id}`, - accepts: 'application/json', - dataType: 'json' - }; - return $.ajax(ajax_options); - }, - read: function () { - let ajax_options = { - type: 'GET', - url: '/api/people', - accepts: 'application/json', - dataType: 'json' - }; - return $.ajax(ajax_options); - }, - create: function (person) { - let ajax_options = { - type: 'POST', - url: '/api/people', - accepts: 'application/json', - contentType: 'application/json', - dataType: 'json', - data: JSON.stringify(person) - }; - return $.ajax(ajax_options); - }, - update: function (person) { - let ajax_options = { - type: 'PUT', - url: `/api/people/${person.person_id}`, - accepts: 'application/json', - contentType: 'application/json', - dataType: 'json', - data: JSON.stringify(person) - }; - return $.ajax(ajax_options); - }, - 'delete': function (person_id) { - let ajax_options = { - type: 'DELETE', - url: `/api/people/${person_id}`, - accepts: 'application/json', - contentType: 'plain/text' - }; - return $.ajax(ajax_options); - } - }; -}()); - -// Create the view instance -ns.view = (function () { - 'use strict'; - - const NEW_NOTE = 0, - EXISTING_NOTE = 1; - - let $person_id = $('#person_id'), - $fname = $('#fname'), - $lname = $('#lname'), - $create = $('#create'), - $update = $('#update'), - $delete = $('#delete'), - $reset = $('#reset'); - - // return the API - return { - NEW_NOTE: NEW_NOTE, - EXISTING_NOTE: EXISTING_NOTE, - reset: function () { - $person_id.text(''); - $lname.val(''); - $fname.val('').focus(); - }, - update_editor: function (person) { - $person_id.text(person.person_id); - $lname.val(person.lname); - $fname.val(person.fname).focus(); - }, - set_button_state: function (state) { - if (state === NEW_NOTE) { - $create.prop('disabled', false); - $update.prop('disabled', true); - $delete.prop('disabled', true); - } else if (state === EXISTING_NOTE) { - $create.prop('disabled', true); - $update.prop('disabled', false); - $delete.prop('disabled', false); +/** + * This is the model class which provides access to the server REST API + * @type {{}} + */ +class Model { + async read() { + let options = { + method: "GET", + cache: "no-cache", + headers: { + "Content-Type": "application/json" } - }, - build_table: function (people) { - let source = $('#people-table-template').html(), - template = Handlebars.compile(source), - html; - - // clear the table - $('.people table > tbody').empty(); - - // did we get a people array? - if (people) { - - // Create the HTML from the template and people - html = template({people: people}) - - // Append the html to the table - $('table').append(html); + }; + // Call the REST endpoint and wait for data + let response = await fetch(`/api/people`, options); + let data = await response.json(); + return data; + } + async readOne(person_id) { + let options = { + method: "GET", + cache: "no-cache", + headers: { + "Content-Type": "application/json", + "accepts": "application/json" } - }, - error: function (error_msg) { - $('.error') - .text(error_msg) - .css('visibility', 'visible'); - setTimeout(function () { - $('.error').fadeOut(); - }, 2000) - } - }; -}()); - -// Create the controller -ns.controller = (function (m, v) { - 'use strict'; + }; + // Call the REST endpoint and wait for data + let response = await fetch(`/api/people/${person_id}`, options); + let data = await response.json(); + return data; + } + async create(person) { + let options = { + method: "POST", + cache: "no-cache", + headers: { + "Content-Type": "application/json", + "accepts": "application/json" + }, + body: JSON.stringify(person) + }; + // Call the REST endpoint and wait for data + let response = await fetch(`/api/people`, options); + let data = await response.json(); + return data; + } + async update(person) { + let options = { + method: "PUT", + cache: "no-cache", + headers: { + "Content-Type": "application/json", + "accepts": "application/json" + }, + body: JSON.stringify(person) + }; + // Call the REST endpoint and wait for data + let response = await fetch(`/api/people/${person.person_id}`, options); + let data = await response.json(); + return data; + } + async delete(person_id) { + let options = { + method: "DELETE", + cache: "no-cache", + headers: { + "Content-Type": "application/json", + "accepts": "application/json" + } + }; + // Call the REST endpoint and wait for data + let response = await fetch(`/api/people/${person_id}`, options); + return response; + } +} - let model = m, - view = v, - $url_person_id = $('#url_person_id'), - $person_id = $('#person_id'), - $fname = $('#fname'), - $lname = $('#lname'); - // Get the data from the model after the controller is done initializing - setTimeout(function () { - view.reset(); - model.read() - .done(function(data) { - view.build_table(data); - }) - .fail(function(xhr, textStatus, errorThrown) { - error_handler(xhr, textStatus, errorThrown); - }) +/** + * This is the view class which provides access to the DOM + */ +class View { + constructor() { + this.NEW_NOTE = 0; + this.EXISTING_NOTE = 1; + this.table = document.querySelector(".people table"); + this.error = document.querySelector(".error"); + this.person_id = document.getElementById("person_id"); + this.fname = document.getElementById("fname"); + this.lname = document.getElementById("lname"); + this.createButton = document.getElementById("create"); + this.updateButton = document.getElementById("update"); + this.deleteButton = document.getElementById("delete"); + this.resetButton = document.getElementById("reset"); + } - if ($url_person_id.val() !== "") { - model.read_one(parseInt($url_person_id.val())) - .done(function(data) { - view.update_editor(data); - view.set_button_state(view.EXISTING_NOTE); - }) - .fail(function(xhr, textStatus, errorThrown) { - error_handler(xhr, textStatus, errorThrown); - }); + reset() { + this.person_id.textContent = ""; + this.lname.value = ""; + this.fname.value = ""; + this.fname.focus(); + } + updateEditor(person) { + this.person_id.textContent = person.person_id; + this.lname.value = person.lname; + this.fname.value = person.fname; + this.fname.focus(); + } + setButtonState(state) { + if (state === this.NEW_NOTE) { + this.createButton.disabled = false; + this.updateButton.disabled = true; + this.deleteButton.disabled = true; + } else if (state === this.EXISTING_NOTE) { + this.createButton.disabled = true; + this.updateButton.disabled = false; + this.deleteButton.disabled = false; } - }, 100) - - // generic error handler - function error_handler(xhr, textStatus, errorThrown) { - let error_msg = `${textStatus}: ${errorThrown} - ${xhr.responseJSON.detail}`; - - view.error(error_msg); - console.log(error_msg); } - // initialize the button states - view.set_button_state(view.NEW_NOTE); - - // Validate input - function validate(fname, lname) { - return fname !== "" && lname !== ""; + buildTable(people) { + let tbody, + html = ""; + + // iterate over the people and build the table + people.forEach((person) => { + html += ` + + ${person.timestamp} + ${person.fname} ${person.lname} + `; + }); + // Is there currently a tbody in the table? + if (this.table.tBodies.length !== 0) { + this.table.removeChild(this.table.getElementsByTagName("tbody")[0]); + } + // Update tbody with our new content + tbody = this.table.createTBody(); + tbody.innerHTML = html; } + errorMessage(message) { + this.error.innerHTML = message; + this.error.classList.add("visible"); + this.error.classList.remove("hidden"); + setTimeout(() => { + this.error.classList.add("hidden"); + this.error.classList.remove("visible"); + }, 2000); + } +} - // Create our event handlers - $('#create').click(function (e) { - let fname = $fname.val(), - lname = $lname.val(); - - e.preventDefault(); - if (validate(fname, lname)) { - model.create({ - 'fname': fname, - 'lname': lname, - }) - .done(function(data) { - model.read() - .done(function(data) { - view.build_table(data); - }) - .fail(function(xhr, textStatus, errorThrown) { - error_handler(xhr, textStatus, errorThrown); - }); - view.set_button_state(view.NEW_NOTE); - }) - .fail(function(xhr, textStatus, errorThrown) { - error_handler(xhr, textStatus, errorThrown); - }); - - view.reset(); +/** + * This is the controller class for the user interaction + */ +class Controller { + constructor(model, view) { + this.model = model; + this.view = view; - } else { - alert('Problem with first or last name input'); + // initialize the system + this.initialize(); + } + async initialize() { + await this.initializeTable(); + this.initializeTableEvents(); + this.initializeCreateEvent(); + this.initializeUpdateEvent(); + this.initializeDeleteEvent(); + this.initializeResetEvent(); + } + async initializeTable() { + try { + let url_person_id = parseInt(document.getElementById("url_person_id").value), + people = await this.model.read(); + + this.view.buildTable(people); + + // Did we navigate here with a person selected? + if (url_person_id) { + let person = await this.model.readOne(url_person_id); + this.view.updateEditor(person); + this.view.setButtonState(this.view.EXISTING_NOTE); + + // Otherwise, nope, so leave the editor blank + } else { + this.view.reset(); + this.view.setButtonState(this.view.NEW_NOTE); + } + this.initializeTableEvents(); + } catch (err) { + this.view.errorMessage(err); } - }); + } + initializeTableEvents() { + document.querySelector("table tbody").addEventListener("dblclick", (evt) => { + let target = evt.target, + parent = target.parentElement; - $('#update').click(function (e) { - let person_id = parseInt($person_id.text()), - fname = $fname.val(), - lname = $lname.val(); + evt.preventDefault(); - e.preventDefault(); + // Is this the name td? + if (target) { + let person_id = parent.getAttribute("data-person_id"); - if (validate(fname, lname)) { - model.update({ + window.location = `/people/${person_id}/notes`; + } + }); + document.querySelector("table tbody").addEventListener("click", (evt) => { + let target = evt.target.parentElement, + person_id = target.getAttribute("data-person_id"), + fname = target.getAttribute("data-fname"), + lname = target.getAttribute("data-lname"); + + this.view.updateEditor({ person_id: person_id, fname: fname, - lname: lname, - }) - .done(function(data) { - model.read() - .done(function(data) { - view.build_table(data); - }) - .fail(function(xhr, textStatus, errorThrown) { - error_handler(xhr, textStatus, errorThrown); - }); - view.reset(); - view.set_button_state(view.NEW_NOTE); - }) - .fail(function(xhr, textStatus, errorThrown) { - error_handler(xhr, textStatus, errorThrown); - }) - - } else { - alert('Problem with first or last name input'); - } - e.preventDefault(); - }); - - $('#delete').click(function (e) { - let person_id = parseInt($person_id.text()); - - e.preventDefault(); - - if (validate('placeholder', lname)) { - model.delete(person_id) - .done(function(data) { - model.read() - .done(function(data) { - view.build_table(data); - }) - .fail(function(xhr, textStatus, errorThrown) { - error_handler(xhr, textStatus, errorThrown); - }); - view.reset(); - view.set_button_state(view.NEW_NOTE); - }) - .fail(function(xhr, textStatus, errorThrown) { - error_handler(xhr, textStatus, errorThrown); + lname: lname + }); + this.view.setButtonState(this.view.EXISTING_NOTE); + }); + } + initializeCreateEvent() { + document.getElementById("create").addEventListener("click", async (evt) => { + let fname = document.getElementById("fname").value, + lname = document.getElementById("lname").value; + + evt.preventDefault(); + try { + await this.model.create({ + fname: fname, + lname: lname }); - - } else { - alert('Problem with first or last name input'); - } - }); - - $('#reset').click(function () { - view.reset(); - view.set_button_state(view.NEW_NOTE); - }) - - $('table').on('click', 'tbody tr', function (e) { - let $target = $(e.target).parent(), - person_id = $target.data('person_id'), - fname = $target.data('fname'), - lname = $target.data('lname'); - - view.update_editor({ - person_id: person_id, - fname: fname, - lname: lname, + await this.initializeTable(); + } catch(err) { + this.view.errorMessage(err); + } }); - view.set_button_state(view.EXISTING_NOTE); - }); - - $('table').on('dblclick', 'tbody tr', function (e) { - let $target = $(e.target), - person_id = $target.parent().attr('data-person_id'); + } + initializeUpdateEvent() { + document.getElementById("update").addEventListener("click", async (evt) => { + let person_id = parseInt(document.getElementById("person_id").textContent), + fname = document.getElementById("fname").value, + lname = document.getElementById("lname").value; + + evt.preventDefault(); + try { + await this.model.update({ + person_id: person_id, + fname: fname, + lname: lname + }); + await this.initializeTable(); + } catch(err) { + this.view.errorMessage(err); + } + }); + } + initializeDeleteEvent() { + document.getElementById("delete").addEventListener("click", async (evt) => { + let person_id = parseInt(document.getElementById("person_id").textContent); + + evt.preventDefault(); + try { + await this.model.delete(person_id); + await this.initializeTable(); + } catch(err) { + this.view.errorMessage(err); + } + }); + } + initializeResetEvent() { + document.getElementById("reset").addEventListener("click", async (evt) => { + evt.preventDefault(); + this.view.reset(); + this.view.setButtonState(this.view.NEW_NOTE); + }); + } +} - window.location.href = `/people/${person_id}/notes`; +/** + * Create the namespace container for the model, view and controller + */ +const ns = (function() { + "use strict"; - }); -}(ns.model, ns.view)); + const model = new Model(); + const view = new View(); + const controller = new Controller(model, view); + return { + model: model, + view: view, + controller: controller + }; +}()); diff --git a/flask-connexion-rest-part-4/templates/home.html b/flask-connexion-rest-part-4/templates/home.html index eb9f4229ab..c0431cd1a1 100644 --- a/flask-connexion-rest-part-4/templates/home.html +++ b/flask-connexion-rest-part-4/templates/home.html @@ -20,23 +20,7 @@ -
-
- - -{% raw %} - -{% endraw %} + {% endblock %} diff --git a/flask-connexion-rest-part-4/templates/notes.html b/flask-connexion-rest-part-4/templates/notes.html index 3d36ecce63..91ed73045f 100644 --- a/flask-connexion-rest-part-4/templates/notes.html +++ b/flask-connexion-rest-part-4/templates/notes.html @@ -59,21 +59,7 @@ -
-
- -{% raw %} - -{% endraw %} +
{% endblock %} diff --git a/flask-connexion-rest-part-4/templates/parent.html b/flask-connexion-rest-part-4/templates/parent.html index 04cd903901..7f6c0ab43f 100644 --- a/flask-connexion-rest-part-4/templates/parent.html +++ b/flask-connexion-rest-part-4/templates/parent.html @@ -6,15 +6,6 @@ {% block title %}{% endblock %} Page - {% endblock %} @@ -34,14 +25,6 @@ {% block javascript %} - {% endblock %} \ No newline at end of file diff --git a/flask-connexion-rest-part-4/templates/people.html b/flask-connexion-rest-part-4/templates/people.html index a50f4769af..baf800fac0 100644 --- a/flask-connexion-rest-part-4/templates/people.html +++ b/flask-connexion-rest-part-4/templates/people.html @@ -39,26 +39,10 @@ -
-
+
- - -{% raw %} - -{% endraw %} - {% endblock %} {% block javascript %} From cc0ef1938b500e907090f6e5f1cfa8d92901f6cb Mon Sep 17 00:00:00 2001 From: Doug Farrell Date: Fri, 24 May 2019 13:34:13 -0400 Subject: [PATCH 3/7] Made changes based on Dan's comments, mostly formating and naming convention changes --- flask-connexion-rest-part-4/static/js/home.js | 35 +++--- .../static/js/home_1.js | 102 ----------------- .../static/js/notes.js | 106 ++++++++++-------- .../static/js/people.js | 81 +++++++------ .../templates/home.html | 5 +- .../templates/notes.html | 5 +- .../templates/people.html | 5 +- 7 files changed, 132 insertions(+), 207 deletions(-) delete mode 100644 flask-connexion-rest-part-4/static/js/home_1.js diff --git a/flask-connexion-rest-part-4/static/js/home.js b/flask-connexion-rest-part-4/static/js/home.js index ceeba494ad..b8bad57d25 100644 --- a/flask-connexion-rest-part-4/static/js/home.js +++ b/flask-connexion-rest-part-4/static/js/home.js @@ -34,7 +34,7 @@ class View { this.error = document.querySelector(".error"); } - build_table(notes) { + buildTable(notes) { let tbody = this.table.createTBody(); let html = ""; @@ -50,6 +50,7 @@ class View { // replace the tbody with our new content tbody.innerHTML = html; } + errorMessage(message) { this.error.innerHTML = message; this.error.classList.remove("hidden"); @@ -75,7 +76,7 @@ class Controller { async initialize() { try { let notes = await this.model.read(); - this.view.build_table(notes); + this.view.buildTable(notes); } catch(err) { this.view.errorMessage(err); } @@ -86,13 +87,13 @@ class Controller { parent = target.parentElement; // is this the name td? - if (target) { + if (target.classList.contains("name")) { let person_id = parent.getAttribute("data-person_id"); window.location = `/people/${person_id}`; // is this the content td - } else if (true) { + } else if (target.classList.contains("content")) { let person_id = parent.getAttribute("data-person_id"), note_id = parent.getAttribute("data-note_id"); @@ -102,20 +103,16 @@ class Controller { } } -/** - * Create the namespace container for the model, view and controller - */ -const ns = (function() { - "use strict"; - - const model = new Model(); - const view = new View(); - const controller = new Controller(model, view); - return { - model: model, - view: view, - controller: controller - }; -}()); +// create the MVC components +const model = new Model(); +const view = new View(); +const controller = new Controller(model, view); + +// export the MVC components as the default +export default { + model, + view, + controller +}; diff --git a/flask-connexion-rest-part-4/static/js/home_1.js b/flask-connexion-rest-part-4/static/js/home_1.js deleted file mode 100644 index 829f6347bb..0000000000 --- a/flask-connexion-rest-part-4/static/js/home_1.js +++ /dev/null @@ -1,102 +0,0 @@ -/* - * JavaScript file for the Home page - */ - -// Create the namespace instance -let ns = {}; - -// Create the model instance -ns.model = (function () { - 'use strict'; - - // Return the API - return { - 'read': function () { - let ajax_options = { - type: 'GET', - url: '/api/notes', - accepts: 'application/json', - dataType: 'json' - }; - return $.ajax(ajax_options); - } - }; -}()); - - -// Create the view instance -ns.view = (function () { - 'use strict'; - - var $table = $(".blog table"); - - // Return the API - return { - build_table: function (data) { - let source = $('#blog-table-template').html(), - template = Handlebars.compile(source), - html; - - // Create the HTML from the template and notes - html = template({notes: data}); - - // Append the rows to the table tbody - $table.append(html); - }, - error: function (error_msg) { - $('.error') - .text(error_msg) - .css('visibility', 'visible'); - setTimeout(function () { - $('.error').fadeOut(); - }, 2000) - } - }; -}()); - - -// Create the controller instance -ns.controller = (function (m, v) { - 'use strict'; - - let model = m, - view = v; - - // Get the note data from the model after the controller is done initializing - setTimeout(function () { - - // Attach event handlers to the promise returned by model.read() - model.read() - .done(function (data) { - view.build_table(data); - }) - .fail(function (xhr, textStatus, errorThrown) { - error_handler(xhr, textStatus, errorThrown); - }); - }, 100); - - // generic error handler - function error_handler(xhr, textStatus, errorThrown) { - let error_msg = `${textStatus}: ${errorThrown} - ${xhr.responseJSON.detail}`; - - view.error(error_msg); - console.log(error_msg); - } - - // handle application events - $('table').on('dblclick', 'tbody td.name', function (e) { - let $target = $(e.target).parent(), - person_id = $target.data('person_id'); - - window.location = `/people/${person_id}`; - - }); - - $('table').on('dblclick', 'tbody td.content', function (e) { - let $target = $(e.target).parent(), - person_id = $target.data('person_id'), - note_id = $target.data('note_id'); - - window.location = `people/${person_id}/notes/${note_id}`; - }); -}(ns.model, ns.view)); \ No newline at end of file diff --git a/flask-connexion-rest-part-4/static/js/notes.js b/flask-connexion-rest-part-4/static/js/notes.js index 967695df87..795b051f4f 100644 --- a/flask-connexion-rest-part-4/static/js/notes.js +++ b/flask-connexion-rest-part-4/static/js/notes.js @@ -9,7 +9,7 @@ * @type {{}} */ class Model { - async read(person_id) { + async read(personId) { let options = { method: "GET", cache: "no-cache", @@ -18,11 +18,12 @@ class Model { } }; // Call the REST endpoint and wait for data - let response = await fetch(`/api/people/${person_id}`, options); + let response = await fetch(`/api/people/${personId}`, options); let data = await response.json(); return data; } - async readOne(person_id, note_id) { + + async readOne(personId, noteId) { let options = { method: "GET", cache: "no-cache", @@ -32,11 +33,12 @@ class Model { } }; // Call the REST endpoint and wait for data - let response = await fetch(`/api/people/${person_id}/notes/${note_id}`, options); + let response = await fetch(`/api/people/${personId}/notes/${noteId}`, options); let data = await response.json(); return data; } - async create(person_id, note) { + + async create(personId, note) { let options = { method: "POST", cache: "no-cache", @@ -47,11 +49,12 @@ class Model { body: JSON.stringify(note) }; // Call the REST endpoint and wait for data - let response = await fetch(`/api/people/${person_id}/notes`, options); + let response = await fetch(`/api/people/${personId}/notes`, options); let data = await response.json(); return data; } - async update(person_id, note) { + + async update(personId, note) { let options = { method: "PUT", cache: "no-cache", @@ -62,11 +65,12 @@ class Model { body: JSON.stringify(note) }; // Call the REST endpoint and wait for data - let response = await fetch(`/api/people/${person_id}/notes/${note.note_id}`, options); + let response = await fetch(`/api/people/${personId}/notes/${note.noteId}`, options); let data = await response.json(); return data; } - async delete(person_id, note_id) { + + async delete(personId, noteId) { let options = { method: "DELETE", cache: "no-cache", @@ -76,7 +80,7 @@ class Model { } }; // Call the REST endpoint and wait for data - let response = await fetch(`/api/people/${person_id}/notes/${note_id}`, options); + let response = await fetch(`/api/people/${personId}/notes/${noteId}`, options); return response; } } @@ -91,27 +95,30 @@ class View { this.EXISTING_NOTE = 1; this.table = document.querySelector(".notes table"); this.error = document.querySelector(".error"); - this.person_id = document.getElementById("person_id"); + this.personId = document.getElementById("person_id"); this.fname = document.getElementById("fname"); this.lname = document.getElementById("lname"); this.timestamp = document.getElementById("timestamp"); - this.note_id = document.getElementById("note_id"); + this.noteId = document.getElementById("note_id"); this.note = document.getElementById("note"); this.createButton = document.getElementById("create"); this.updateButton = document.getElementById("update"); this.deleteButton = document.getElementById("delete"); this.resetButton = document.getElementById("reset"); } + reset() { - this.note_id.textContent = ""; + this.noteId.textContent = ""; this.note.value = ""; this.note.focus(); } + updateEditor(note) { - this.note_id.textContent = note.note_id; + this.noteId.textContent = note.noteId; this.note.value = note.content; this.note.focus(); } + setButtonState(state) { if (state === this.NEW_NOTE) { this.createButton.disabled = false; @@ -123,12 +130,13 @@ class View { this.deleteButton.disabled = false; } } + buildTable(person) { let tbody, html = ""; // Update the person data - this.person_id.textContent = person.person_id; + this.personId.textContent = person.person_id; this.fname.textContent = person.fname; this.lname.textContent = person.lname; this.timestamp.textContent = person.timestamp; @@ -149,6 +157,7 @@ class View { tbody = this.table.createTBody(); tbody.innerHTML = html; } + errorMessage(error_msg) { let error = document.querySelector(".error"); @@ -173,6 +182,7 @@ class Controller { this.initialize(); } + async initialize() { await this.initializeTable(); this.initializeTableEvents(); @@ -181,17 +191,18 @@ class Controller { this.initializeDeleteEvent(); this.initializeResetEvent(); } + async initializeTable() { try { - let url_person_id = parseInt(document.getElementById("url_person_id").value), - url_note_id = parseInt(document.getElementById("url_note_id").value), - person = await this.model.read(url_person_id); + let urlPersonId = parseInt(document.getElementById("url_person_id").value), + urlNoteId = parseInt(document.getElementById("url_note_id").value), + person = await this.model.read(urlPersonId); this.view.buildTable(person); // Did we navigate here with a note selected? - if (url_note_id) { - let note = await this.model.readOne(url_person_id, url_note_id); + if (urlNoteId) { + let note = await this.model.readOne(urlPersonId, urlNoteId); this.view.updateEditor(note); this.view.setButtonState(this.view.EXISTING_NOTE); @@ -205,27 +216,29 @@ class Controller { this.view.errorMessage(err); } } + initializeTableEvents() { document.querySelector("table tbody").addEventListener("click", (evt) => { let target = evt.target.parentElement, - note_id = target.getAttribute("data-note_id"), + noteId = target.getAttribute("data-note_id"), content = target.getAttribute("data-content"); this.view.updateEditor({ - note_id: note_id, + noteId: noteId, content: content }); this.view.setButtonState(this.view.EXISTING_NOTE); }); } + initializeCreateEvent() { document.getElementById("create").addEventListener("click", async (evt) => { - let url_person_id = parseInt(document.getElementById("person_id").textContent), + let urlPersonId = parseInt(document.getElementById("person_id").textContent), note = document.getElementById("note").value; evt.preventDefault(); try { - await this.model.create(url_person_id, { + await this.model.create(urlPersonId, { content: note }); await this.initializeTable(); @@ -234,17 +247,18 @@ class Controller { } }); } + initializeUpdateEvent() { document.getElementById("update").addEventListener("click", async (evt) => { - let person_id = parseInt(document.getElementById("person_id").textContent), - note_id = parseInt(document.getElementById("note_id").textContent), + let personId = parseInt(document.getElementById("person_id").textContent), + noteId = parseInt(document.getElementById("note_id").textContent), note = document.getElementById("note").value; evt.preventDefault(); try { - await this.model.update(person_id, { - person_id: person_id, - note_id: note_id, + await this.model.update(personId, { + personId: personId, + noteId: noteId, content: note }); await this.initializeTable(); @@ -253,20 +267,22 @@ class Controller { } }); } + initializeDeleteEvent() { document.getElementById("delete").addEventListener("click", async (evt) => { - let person_id = parseInt(document.getElementById("person_id").textContent), - note_id = parseInt(document.getElementById("note_id").textContent); + let personId = parseInt(document.getElementById("person_id").textContent), + noteId = parseInt(document.getElementById("note_id").textContent); evt.preventDefault(); try { - await this.model.delete(person_id, note_id); + await this.model.delete(personId, noteId); await this.initializeTable(); } catch(err) { this.view.errorMessage(err); } }); } + initializeResetEvent() { document.getElementById("reset").addEventListener("click", async (evt) => { evt.preventDefault(); @@ -276,20 +292,16 @@ class Controller { } } -/** - * Create the namespace container for the model, view and controller - */ -const ns = (function() { - "use strict"; - - const model = new Model(); - const view = new View(); - const controller = new Controller(model, view); - return { - model: model, - view: view, - controller: controller - } -}()); +// create the MVC components +const model = new Model(); +const view = new View(); +const controller = new Controller(model, view); + +// export the MVC components as the default +export default { + model, + view, + controller +}; diff --git a/flask-connexion-rest-part-4/static/js/people.js b/flask-connexion-rest-part-4/static/js/people.js index ef842af526..0d2234963f 100644 --- a/flask-connexion-rest-part-4/static/js/people.js +++ b/flask-connexion-rest-part-4/static/js/people.js @@ -14,15 +14,17 @@ class Model { method: "GET", cache: "no-cache", headers: { - "Content-Type": "application/json" + "Content-Type": "application/json", + "accepts": "application/json" } }; // Call the REST endpoint and wait for data - let response = await fetch(`/api/people`, options); + let response = await fetch("/api/people", options); let data = await response.json(); return data; } - async readOne(person_id) { + + async readOne(personId) { let options = { method: "GET", cache: "no-cache", @@ -32,10 +34,11 @@ class Model { } }; // Call the REST endpoint and wait for data - let response = await fetch(`/api/people/${person_id}`, options); + let response = await fetch(`/api/people/${personId}`, options); let data = await response.json(); return data; } + async create(person) { let options = { method: "POST", @@ -51,6 +54,7 @@ class Model { let data = await response.json(); return data; } + async update(person) { let options = { method: "PUT", @@ -62,11 +66,12 @@ class Model { body: JSON.stringify(person) }; // Call the REST endpoint and wait for data - let response = await fetch(`/api/people/${person.person_id}`, options); + let response = await fetch(`/api/people/${person.personId}`, options); let data = await response.json(); return data; } - async delete(person_id) { + + async delete(personId) { let options = { method: "DELETE", cache: "no-cache", @@ -76,7 +81,7 @@ class Model { } }; // Call the REST endpoint and wait for data - let response = await fetch(`/api/people/${person_id}`, options); + let response = await fetch(`/api/people/${personId}`, options); return response; } } @@ -91,7 +96,7 @@ class View { this.EXISTING_NOTE = 1; this.table = document.querySelector(".people table"); this.error = document.querySelector(".error"); - this.person_id = document.getElementById("person_id"); + this.personId = document.getElementById("person_id"); this.fname = document.getElementById("fname"); this.lname = document.getElementById("lname"); this.createButton = document.getElementById("create"); @@ -101,17 +106,19 @@ class View { } reset() { - this.person_id.textContent = ""; + this.personId.textContent = ""; this.lname.value = ""; this.fname.value = ""; this.fname.focus(); } + updateEditor(person) { - this.person_id.textContent = person.person_id; + this.personId.textContent = person.person_id; this.lname.value = person.lname; this.fname.value = person.fname; this.fname.focus(); } + setButtonState(state) { if (state === this.NEW_NOTE) { this.createButton.disabled = false; @@ -123,6 +130,7 @@ class View { this.deleteButton.disabled = false; } } + buildTable(people) { let tbody, html = ""; @@ -143,6 +151,7 @@ class View { tbody = this.table.createTBody(); tbody.innerHTML = html; } + errorMessage(message) { this.error.innerHTML = message; this.error.classList.add("visible"); @@ -163,9 +172,9 @@ class Controller { this.model = model; this.view = view; - // initialize the system this.initialize(); } + async initialize() { await this.initializeTable(); this.initializeTableEvents(); @@ -174,16 +183,17 @@ class Controller { this.initializeDeleteEvent(); this.initializeResetEvent(); } + async initializeTable() { try { - let url_person_id = parseInt(document.getElementById("url_person_id").value), + let urlPersonId = parseInt(document.getElementById("url_person_id").value), people = await this.model.read(); this.view.buildTable(people); // Did we navigate here with a person selected? - if (url_person_id) { - let person = await this.model.readOne(url_person_id); + if (urlPersonId) { + let person = await this.model.readOne(urlPersonId); this.view.updateEditor(person); this.view.setButtonState(this.view.EXISTING_NOTE); @@ -197,6 +207,7 @@ class Controller { this.view.errorMessage(err); } } + initializeTableEvents() { document.querySelector("table tbody").addEventListener("dblclick", (evt) => { let target = evt.target, @@ -206,9 +217,9 @@ class Controller { // Is this the name td? if (target) { - let person_id = parent.getAttribute("data-person_id"); + let personId = parent.getAttribute("data-person_id"); - window.location = `/people/${person_id}/notes`; + window.location = `/people/${personId}/notes`; } }); document.querySelector("table tbody").addEventListener("click", (evt) => { @@ -225,6 +236,7 @@ class Controller { this.view.setButtonState(this.view.EXISTING_NOTE); }); } + initializeCreateEvent() { document.getElementById("create").addEventListener("click", async (evt) => { let fname = document.getElementById("fname").value, @@ -242,16 +254,17 @@ class Controller { } }); } + initializeUpdateEvent() { document.getElementById("update").addEventListener("click", async (evt) => { - let person_id = parseInt(document.getElementById("person_id").textContent), + let personId = parseInt(document.getElementById("person_id").textContent), fname = document.getElementById("fname").value, lname = document.getElementById("lname").value; evt.preventDefault(); try { await this.model.update({ - person_id: person_id, + personId: personId, fname: fname, lname: lname }); @@ -261,19 +274,21 @@ class Controller { } }); } + initializeDeleteEvent() { document.getElementById("delete").addEventListener("click", async (evt) => { - let person_id = parseInt(document.getElementById("person_id").textContent); + let personId = parseInt(document.getElementById("person_id").textContent); evt.preventDefault(); try { - await this.model.delete(person_id); + await this.model.delete(personId); await this.initializeTable(); } catch(err) { this.view.errorMessage(err); } }); } + initializeResetEvent() { document.getElementById("reset").addEventListener("click", async (evt) => { evt.preventDefault(); @@ -283,20 +298,14 @@ class Controller { } } -/** - * Create the namespace container for the model, view and controller - */ -const ns = (function() { - "use strict"; - - const model = new Model(); - const view = new View(); - const controller = new Controller(model, view); - return { - model: model, - view: view, - controller: controller - }; -}()); - +// create the MVC components +const model = new Model(); +const view = new View(); +const controller = new Controller(model, view); +// export the MVC components as the default +export default { + model, + view, + controller +}; diff --git a/flask-connexion-rest-part-4/templates/home.html b/flask-connexion-rest-part-4/templates/home.html index c0431cd1a1..d5854078e6 100644 --- a/flask-connexion-rest-part-4/templates/home.html +++ b/flask-connexion-rest-part-4/templates/home.html @@ -26,5 +26,8 @@ {% block javascript %} {{ super() }} - + {% endblock %} diff --git a/flask-connexion-rest-part-4/templates/notes.html b/flask-connexion-rest-part-4/templates/notes.html index 91ed73045f..e537e02be5 100644 --- a/flask-connexion-rest-part-4/templates/notes.html +++ b/flask-connexion-rest-part-4/templates/notes.html @@ -65,5 +65,8 @@ {% block javascript %} {{ super() }} - + {% endblock %} \ No newline at end of file diff --git a/flask-connexion-rest-part-4/templates/people.html b/flask-connexion-rest-part-4/templates/people.html index baf800fac0..90b2917e98 100644 --- a/flask-connexion-rest-part-4/templates/people.html +++ b/flask-connexion-rest-part-4/templates/people.html @@ -47,5 +47,8 @@ {% block javascript %} {{ super() }} - + {% endblock %} From 0aafb92533e98910332a9e17452de94f739c1f13 Mon Sep 17 00:00:00 2001 From: Doug Farrell Date: Mon, 27 May 2019 18:31:40 -0400 Subject: [PATCH 4/7] Simplified the CSS files across the board, and replaced with `parseInt()` function with a simple `+`, as that's all that's needed to convert a string to an integer. Also clarified the namespace importing. --- .../static/css/home.css | 47 ------------------- .../static/css/people.css | 4 -- .../static/js/notes.js | 14 +++--- .../static/js/people.js | 6 +-- .../templates/home.html | 29 +++++++----- .../templates/notes.html | 5 +- .../templates/people.html | 5 +- 7 files changed, 35 insertions(+), 75 deletions(-) diff --git a/flask-connexion-rest-part-4/static/css/home.css b/flask-connexion-rest-part-4/static/css/home.css index fb66c8835b..c991616111 100644 --- a/flask-connexion-rest-part-4/static/css/home.css +++ b/flask-connexion-rest-part-4/static/css/home.css @@ -7,26 +7,6 @@ div.blog { border: 2px solid darkgrey; } -.blog table { - border-collapse: collapse; - width: 100%; -} - -.blog table caption { - font-weight: bold; - padding: 10px 0 10px 0; - border-bottom: 2px solid darkgrey; - background-color: antiquewhite; -} - -.blog table thead { - padding: 10px 0 10px 0; -} - -th:not(:last-child), td:not(:last-child) { - border-right: 2px solid darkgrey; -} - td.name { text-align: center; width: 175px; @@ -36,30 +16,3 @@ td.timestamp { text-align: center; width: 280px; } - -td.content { - text-align: left; - padding-left: 5px; -} - -tbody tr:nth-child(odd) { - background-color: gainsboro; -} - -tbody td.name, tbody td.content { - cursor: pointer; - height: 33px; -} - -thead tr { - height: 33px; - border-bottom: 2px solid darkgrey; -} - -tbody tr:not(:last-child){ - border-bottom: 2px solid darkgrey; -} - -tbody tr:hover { - background-color: powderblue; -} diff --git a/flask-connexion-rest-part-4/static/css/people.css b/flask-connexion-rest-part-4/static/css/people.css index 2395b23a93..0a2da61c5f 100644 --- a/flask-connexion-rest-part-4/static/css/people.css +++ b/flask-connexion-rest-part-4/static/css/people.css @@ -2,10 +2,6 @@ * This is the CSS stylesheet for the demo people application */ -.container { - padding: 10px; -} - .editor { width: 50%; margin-left: auto; diff --git a/flask-connexion-rest-part-4/static/js/notes.js b/flask-connexion-rest-part-4/static/js/notes.js index 795b051f4f..e31f69cf1a 100644 --- a/flask-connexion-rest-part-4/static/js/notes.js +++ b/flask-connexion-rest-part-4/static/js/notes.js @@ -194,8 +194,8 @@ class Controller { async initializeTable() { try { - let urlPersonId = parseInt(document.getElementById("url_person_id").value), - urlNoteId = parseInt(document.getElementById("url_note_id").value), + let urlPersonId = +document.getElementById("url_person_id").value, + urlNoteId = +document.getElementById("url_note_id").value, person = await this.model.read(urlPersonId); this.view.buildTable(person); @@ -233,7 +233,7 @@ class Controller { initializeCreateEvent() { document.getElementById("create").addEventListener("click", async (evt) => { - let urlPersonId = parseInt(document.getElementById("person_id").textContent), + let urlPersonId = +document.getElementById("person_id").textContent, note = document.getElementById("note").value; evt.preventDefault(); @@ -250,8 +250,8 @@ class Controller { initializeUpdateEvent() { document.getElementById("update").addEventListener("click", async (evt) => { - let personId = parseInt(document.getElementById("person_id").textContent), - noteId = parseInt(document.getElementById("note_id").textContent), + let personId = +document.getElementById("person_id").textContent, + noteId = +document.getElementById("note_id").textContent, note = document.getElementById("note").value; evt.preventDefault(); @@ -270,8 +270,8 @@ class Controller { initializeDeleteEvent() { document.getElementById("delete").addEventListener("click", async (evt) => { - let personId = parseInt(document.getElementById("person_id").textContent), - noteId = parseInt(document.getElementById("note_id").textContent); + let personId = +document.getElementById("person_id").textContent, + noteId = +document.getElementById("note_id").textContent; evt.preventDefault(); try { diff --git a/flask-connexion-rest-part-4/static/js/people.js b/flask-connexion-rest-part-4/static/js/people.js index 0d2234963f..7e69b59b06 100644 --- a/flask-connexion-rest-part-4/static/js/people.js +++ b/flask-connexion-rest-part-4/static/js/people.js @@ -186,7 +186,7 @@ class Controller { async initializeTable() { try { - let urlPersonId = parseInt(document.getElementById("url_person_id").value), + let urlPersonId = +document.getElementById("url_person_id").value, people = await this.model.read(); this.view.buildTable(people); @@ -257,7 +257,7 @@ class Controller { initializeUpdateEvent() { document.getElementById("update").addEventListener("click", async (evt) => { - let personId = parseInt(document.getElementById("person_id").textContent), + let personId = +document.getElementById("person_id").textContent, fname = document.getElementById("fname").value, lname = document.getElementById("lname").value; @@ -277,7 +277,7 @@ class Controller { initializeDeleteEvent() { document.getElementById("delete").addEventListener("click", async (evt) => { - let personId = parseInt(document.getElementById("person_id").textContent); + let personId = +document.getElementById("person_id").textContent; evt.preventDefault(); try { diff --git a/flask-connexion-rest-part-4/templates/home.html b/flask-connexion-rest-part-4/templates/home.html index d5854078e6..84220ae7a8 100644 --- a/flask-connexion-rest-part-4/templates/home.html +++ b/flask-connexion-rest-part-4/templates/home.html @@ -8,17 +8,19 @@ {% block page_name %}Mini-Blog Page Demo{% endblock %} {% block body %} -
- - - - - - - - - -
People Blog Entries
Creation/Update TimestampPersonNote
+
+
+ + + + + + + + + +
People Blog Entries
Creation/Update TimestampPersonNote
+
@@ -28,6 +30,9 @@ {{ super() }} {% endblock %} diff --git a/flask-connexion-rest-part-4/templates/notes.html b/flask-connexion-rest-part-4/templates/notes.html index e537e02be5..13db7716a5 100644 --- a/flask-connexion-rest-part-4/templates/notes.html +++ b/flask-connexion-rest-part-4/templates/notes.html @@ -67,6 +67,9 @@ {{ super() }} {% endblock %} \ No newline at end of file diff --git a/flask-connexion-rest-part-4/templates/people.html b/flask-connexion-rest-part-4/templates/people.html index 90b2917e98..fe146e2653 100644 --- a/flask-connexion-rest-part-4/templates/people.html +++ b/flask-connexion-rest-part-4/templates/people.html @@ -49,6 +49,9 @@ {{ super() }} {% endblock %} From 155f59afdee04ac8991a38577d8c6d6c4407026b Mon Sep 17 00:00:00 2001 From: Doug Farrell Date: Mon, 27 May 2019 19:27:25 -0400 Subject: [PATCH 5/7] Changed the beginning character of some comments to begin with an uppercase character. --- flask-connexion-rest-part-4/templates/home.html | 4 ++-- flask-connexion-rest-part-4/templates/notes.html | 4 ++-- flask-connexion-rest-part-4/templates/people.html | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/flask-connexion-rest-part-4/templates/home.html b/flask-connexion-rest-part-4/templates/home.html index 84220ae7a8..f6fd6f79d7 100644 --- a/flask-connexion-rest-part-4/templates/home.html +++ b/flask-connexion-rest-part-4/templates/home.html @@ -29,10 +29,10 @@ {% block javascript %} {{ super() }} {% endblock %} diff --git a/flask-connexion-rest-part-4/templates/notes.html b/flask-connexion-rest-part-4/templates/notes.html index 13db7716a5..1779b39080 100644 --- a/flask-connexion-rest-part-4/templates/notes.html +++ b/flask-connexion-rest-part-4/templates/notes.html @@ -66,10 +66,10 @@ {% block javascript %} {{ super() }} {% endblock %} \ No newline at end of file diff --git a/flask-connexion-rest-part-4/templates/people.html b/flask-connexion-rest-part-4/templates/people.html index fe146e2653..e310a5496d 100644 --- a/flask-connexion-rest-part-4/templates/people.html +++ b/flask-connexion-rest-part-4/templates/people.html @@ -48,10 +48,10 @@ {% block javascript %} {{ super() }} {% endblock %} From 26b6e49a2a2fcd3f8aae3b8ff2e6b2808720e644 Mon Sep 17 00:00:00 2001 From: Doug Farrell Date: Tue, 28 May 2019 12:49:24 -0400 Subject: [PATCH 6/7] Changed the beginning character of some comments to begin with an uppercase character, again... :) --- flask-connexion-rest-part-4/static/js/home.js | 2 +- flask-connexion-rest-part-4/static/js/notes.js | 2 +- flask-connexion-rest-part-4/static/js/people.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/flask-connexion-rest-part-4/static/js/home.js b/flask-connexion-rest-part-4/static/js/home.js index b8bad57d25..86f42aa115 100644 --- a/flask-connexion-rest-part-4/static/js/home.js +++ b/flask-connexion-rest-part-4/static/js/home.js @@ -103,7 +103,7 @@ class Controller { } } -// create the MVC components +// Create the MVC components const model = new Model(); const view = new View(); const controller = new Controller(model, view); diff --git a/flask-connexion-rest-part-4/static/js/notes.js b/flask-connexion-rest-part-4/static/js/notes.js index e31f69cf1a..9f29aee2ef 100644 --- a/flask-connexion-rest-part-4/static/js/notes.js +++ b/flask-connexion-rest-part-4/static/js/notes.js @@ -292,7 +292,7 @@ class Controller { } } -// create the MVC components +// Create the MVC components const model = new Model(); const view = new View(); const controller = new Controller(model, view); diff --git a/flask-connexion-rest-part-4/static/js/people.js b/flask-connexion-rest-part-4/static/js/people.js index 7e69b59b06..e1d7dd3e17 100644 --- a/flask-connexion-rest-part-4/static/js/people.js +++ b/flask-connexion-rest-part-4/static/js/people.js @@ -298,7 +298,7 @@ class Controller { } } -// create the MVC components +// Create the MVC components const model = new Model(); const view = new View(); const controller = new Controller(model, view); From 5f49d840e91103e0ceaa3907d23a7739bb11a8ba Mon Sep 17 00:00:00 2001 From: Doug Farrell Date: Tue, 28 May 2019 12:56:59 -0400 Subject: [PATCH 7/7] Changed the beginning character of some comments to begin with an uppercase character, again... :) --- flask-connexion-rest-part-4/static/js/home.js | 4 ++-- flask-connexion-rest-part-4/static/js/people.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/flask-connexion-rest-part-4/static/js/home.js b/flask-connexion-rest-part-4/static/js/home.js index 86f42aa115..b30c83e368 100644 --- a/flask-connexion-rest-part-4/static/js/home.js +++ b/flask-connexion-rest-part-4/static/js/home.js @@ -38,7 +38,7 @@ class View { let tbody = this.table.createTBody(); let html = ""; - // iterate over the notes and build the table + // Iterate over the notes and build the table notes.forEach((note) => { html += ` @@ -47,7 +47,7 @@ class View { ${note.content} `; }); - // replace the tbody with our new content + // Replace the tbody with our new content tbody.innerHTML = html; } diff --git a/flask-connexion-rest-part-4/static/js/people.js b/flask-connexion-rest-part-4/static/js/people.js index e1d7dd3e17..e9f949817b 100644 --- a/flask-connexion-rest-part-4/static/js/people.js +++ b/flask-connexion-rest-part-4/static/js/people.js @@ -135,7 +135,7 @@ class View { let tbody, html = ""; - // iterate over the people and build the table + // Iterate over the people and build the table people.forEach((person) => { html += `