[PATCH v3 06/10] static: add rest.js to handle PATCH requests & respective responses

Raxel Gutierrez raxel at google.com
Fri Aug 13 15:31:23 AEST 2021


Add `rest.js` file to have a utilities JavaScript module that can be
reused by any Patchwork JS files that make requests to the REST API. In
particular, this patch provides the following function:

 - `updateProperty`: make PATCH requests that partially update the
   fields of an object given it's REST API endpoint specified by the
   caller. Also, the caller can specify the field(s) to modify and the
   associated content for update messages in the case of both failed
   successful requests that render to the current webpage.

The `rest.js` module can be further expanded to support and provide
functions that allow for other requests (e.g. GET, POST, PUT) to the
REST API.

Also, add functions that handle update & error messages for these PATCH
requests that match the Django messages framework format and form error
styling. These functions are internal to the module and aren't exposed
outside of the `rest.js` file.

Error and accompanying failed update messages are replaced by successful
update messages and vice versa. Consecutive successful update messages
add to counter of updated objects. Consecutive error messages stack up.

Signed-off-by: Raxel Gutierrez <raxel at google.com>
---
 htdocs/README.rst   |   7 +++
 htdocs/js/rest.js   | 119 ++++++++++++++++++++++++++++++++++++++++++++
 templates/base.html |   3 ++
 3 files changed, 129 insertions(+)
 create mode 100644 htdocs/js/rest.js

diff --git a/htdocs/README.rst b/htdocs/README.rst
index 128dc7c..ced6bb8 100644
--- a/htdocs/README.rst
+++ b/htdocs/README.rst
@@ -131,6 +131,13 @@ js
   :GitHub: https://github.com/js-cookie/js-cookie/
   :Version: 3.0.0
 
+``rest.js.``
+
+  Utility module for REST API requests to be used by other Patchwork js files
+  (fetch requests, handling update & error messages).
+
+  Part of Patchwork.
+
 ``selectize.min.js``
 
   Selectize is the hybrid of a ``textbox`` and ``<select>`` box. It's jQuery
diff --git a/htdocs/js/rest.js b/htdocs/js/rest.js
new file mode 100644
index 0000000..faac05d
--- /dev/null
+++ b/htdocs/js/rest.js
@@ -0,0 +1,119 @@
+/**
+ * Sends PATCH requests to update objects' properties using the REST API.
+ * @param {string} url Path to the REST API endpoint.
+ * @param {{field: string, value: string}} data
+ *     field: Name of the property field to update.
+ *     value: Value to update the property field to.
+ * @param {{none: string, some: string}} updateMessage
+ *     none: Message when object update failed due to errors.
+ *     some: Message when object update successful.
+ */
+async function updateProperty(url, data, updateMessage) {
+    const request = new Request(url, {
+        method: 'PATCH',
+        mode: "same-origin",
+        headers: {
+            "X-CSRFToken": Cookies.get("csrftoken"),
+            "Content-Type": "application/json",
+        },
+        body: JSON.stringify(data),
+    });
+
+    return await fetch(request)
+        .then(response => {
+            let message = updateMessage.some;
+            let success = true;
+            if (!response.ok) {
+                response.text().then(text => {
+                    const responseObject = JSON.parse(text);
+                    // Add error messages from response to page
+                    for (const [key,value] of Object.entries(responseObject)) {
+                        if (Array.isArray(value)) {
+                            for (const error of value) {
+                                handleErrorMessages(key + ": " + error);
+                            }
+                        } else {
+                            handleErrorMessages(key + ": " + value);
+                        }
+                    }
+                    // Update message to be unsuccessful
+                    message = updateMessage.none;
+                    success = false;
+                });
+            }
+            handleUpdateMessages(message, success);
+            return response.ok
+        });
+}
+
+/**
+ * Populates update messages for API REST requests.
+ * @param {string} messageContent Text for update message.
+ */
+function handleUpdateMessages(messageContent, success=true) {
+    // Replace error and failure update messages with success update message
+    const errorList = document.getElementsByClassName("error-list")[0];
+    const errorContainer = document.getElementById("errors");
+    let messages = document.getElementById("messages");
+    if (success && errorList != null) {
+        errorContainer.replaceChildren();
+        messages.replaceChildren();
+    }
+
+    // Create messages container if it doesn't exist
+    if (messages == null) {
+        messages = document.createElement("div");
+        messages.setAttribute("id", "messages");
+        $(messages).insertAfter("nav");
+    } else {
+        // Increment counter of consecutive success update messages
+        if (messages.firstChild != null) {
+            const newMessageCount = parseInt(messages.firstChild.textContent.slice(0,1)) + 1
+            messageContent = newMessageCount + messageContent.slice(1);
+        }
+    }
+
+    // Create new message element and add to list
+    const message = document.createElement("div");
+    message.setAttribute("class", "message");
+    message.textContent = messageContent;
+    messages.replaceChildren(...[message]);
+}
+
+/**
+ * Populates error messages for API REST requests.
+ * @param {string} errorMessage Text for error message.
+ */
+function handleErrorMessages(errorMessage) {
+    // Replace success update message with error and failure update messages
+    const messages = document.getElementById("messages");
+    messages.replaceChildren()
+
+    let errorContainer = document.getElementById("errors");
+    let errorHeader = document.getElementById("errors-header");
+    let errorList = document.getElementsByClassName("error-list")[0];
+    // Create errors container if it doesn't exist
+    if (errorContainer == null) {
+        errorContainer = document.createElement("div");
+    }
+    // Create errors list and error header if container contents removed
+    if (errorList == null) {
+        errorHeader = document.createElement("p");
+        errorList = document.createElement("ul");
+        errorContainer.setAttribute("id", "errors")
+        errorHeader.setAttribute("id", "errors-header")
+        errorHeader.textContent = "The following errors were encountered while updating comments:";
+        errorList.setAttribute("class", "error-list");
+        errorContainer.appendChild(errorHeader);
+        errorContainer.appendChild(errorList);
+    }
+
+    const contentContainer = document.getElementById("main-content");
+    const error = document.createElement("li");
+
+    error.textContent = errorMessage;
+    errorList.appendChild(error);
+    contentContainer.prepend(errorContainer);
+}
+
+export { updateProperty };
diff --git a/templates/base.html b/templates/base.html
index e57e2d5..313a72d 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -105,7 +105,9 @@
     </div>
    </div>
   </nav>
+
 {% if messages %}
+  {# TODO(raxel): Change to always have messages container #}
   <div id="messages">
   {% for message in messages %}
   {# TODO(stephenfin): Make use of message.tags when completely #}
@@ -115,6 +117,7 @@
   </div>
 {% endif %}
   <div id="main-content" class="container-fluid">
+  {# TODO(raxel): Change to always have errors container #}
 {% block body %}
 {% endblock %}
   </div>
-- 
2.33.0.rc1.237.g0d66db33f3-goog



More information about the Patchwork mailing list