Django & JavaScript – Part 4 Vue.js & Axios

Blog Detail

This is the fourth post in a multipart series that explores web front-end technologies and how they can be used with Django. You can find the previous post here: Django & JavaScript – Part 3 JavaScript & jQuery.

To be able to do React JS & Vue.js justice, I have decided to break them into separate posts with this post focusing on Vue.js and using Axios for API calls.

Requirements

  1. Single process to host the UI & API
  2. Django + Django REST framework
  3. Overarching web framework from day 1
  4. Intuitive UI/UX that can be dynamically updated without loading a new page

DRF API Class

This API class will be used for all examples using the API, in which DRF handles all PUT/PATCH/POST/GET/DELETE operations.

Vue.js

Vue.js is a JavaScript framework that builds on top of HTML, CSS, and JavaScript to provide a declarative model that helped me to develop the simple examples I am using to compare each framework. Vue.js is a lot more powerful than I will be demonstrating today and has been used to build some very robust single-page and multi-page webpages.

Creating a Vue Object

Since my examples are based on a Python back end and I will not be running node.js, I will be creating all of my Vue.js case as inline JavaScript using <script> tags in my HTML. The code could also be easily served via separate static .js files.

To instantiate my Vue objects for the examples, I will need a few pieces of information. I will need to know what my mount point is for Vue. A mount point is what informs Vue.js what parent element will be in scope of this Vue object. I will be using the ID of the element as my mount point. Next, I will be defining my set of data attributes that I will use when interacting with Vue and the DOM. Because I will be intermingling Django templating and Vue.js templating, I will also need to overload the default delimiters from double curly braces to something that will not conflict. Lastly, I will be defining a set of methods or functions that will be invoked based on triggers in my HTML code.

This initial Vue object will not do much of anything without wiring it up to some HTML and throwing in some trigger events. I will be tying var1 to a click event that will update the innerHTML of a <span> element and var2 will be updated based on keyup events in an <input> element to replace its respective innerHTML. I am informing the Vue object of the trigger events by specifying v-on:<trigger>="<function to call>". For example, v-on:click="update_var1" in the example below is notifying the Vue object that on clicking the button element I would like to run the update_var function that is declared in methods.

The end result without CSS making it look fancy is the following.

Vue Object

Axios

Axios is a JavaScript library used to make HTTP promise-based requests from a browser (or server if using node.js). A JavaScript Promise is a construct where you have execution code and callbacks that allows asynchronous methods to return values similar to synchronous methods. The promise is to supply the value at some point in the future. There are three states pertaining to a Promise (pendingfulfilled, and rejected), with fulfilled being a successful completion of the execution code.

In Axios once the Promise is fulfilled it passes the response to the .then(response) method, which is where we implement some magic. In the event the request has an error, we have the ability to .catch(error) and handle the error appropriately.

In my opinion Axios has done an elegant job creating a simple API client that integrated with my Vue.js code flawlessly.

Example 1 – Build DOM from Button Click

Initial Page Without Profile Data

Axios

Page with Profile Data

user profile

Initial HTML

Vue.js

In the first example I am creating a Vue object with a mount point of the <div> that has an ID of user-profile. Within my first nested element I have also introduced if/else Vue statements as attributes of the child <div> elements, v-if="<conditional>"/v-else="<same conditional>". This will translate as: IF the name attribute is truthy (empty string evaluates as false in JavaScript) the table will be visible, ELSE the button to load the profile will be visible.

I have also intermixed Django templating by passing in the user ID of the user making the initial HTTP request to load the page and passing it into v-on:click event function call. While the Vue object has the delimiters set to {( <var name> )} to avoid conflicts.

Lastly, I use Axios to perform an HTTP GET to /api/users/users/<user id>/ and use the response data in performing Vue templating. As soon as I set the name attribute, the Vue object will remove the initial <div> with the button element and replace it with a new <div> of the table that I am building. I don’t have to worry about selecting elements to then inject HTML, or changing attributes of the <div>s to hide one and unhide the other. It’s all handled with the Vue object and the three v- attributes inside the HTML elements.

Example 2 – Input Validation

User Does Not Exist

Input Validation

User Does Exist

Input Validation

HTML Form

Vue.js

new Vue({
  el: "#create-user",
  delimiters: ["{(", ")}"],
  data: {
    user_err: "",
    button_enabled: false
  },
  methods: {
    get_user: function (event){
      if (event.target.value) {
        axios
          .get("/api/users/users/?username=".concat(event.target.value))
          .then(response => {
            if (response.data.count == 1) {
              this.user_err = "This username already exists";
              this.button_enabled = false;
            } else {
              this.user_err = "";
              this.button_enabled = true;
            }
          })
          .catch(error =>{
            this.button_enabled = false;
            this.user_err = error;
          });
      } else {
        this.button_enabled = false;
        this.user_err = "";
      }
    }
  }
});

In this example I decided to implement error handling, which I did not do on the previous two blog posts. The ease of use and more object-oriented programming make me feel like demonstrating the rejected status of the Promise. One difference is that I am not mixing templating languages. I still keep the delimiters overloaded, as this page would most likely be processed by some form of render in Django and I still want to avoid conflicts.

For input validation, if a user backspaces to completely empty out the <input> field, I am resetting the user_err attribute and removing the Create User button. This is meant to prevent unneeded error messages AND remove the user’s ability to click the button IF the user field is empty.

On the Axios call, I implemented similar implied logic as before—that if one user is returned, I have an exact match on the query params and I CANNOT create the user. The difference here is that if this conditional is hit, I not only display the error but I also remove the Create User button to prevent a user from submitting a known invalid user for creation. I have also implemented a catch that will remove the button; and the error will be the error encountered by Axios during the call, resulting in a rejected state of the Promise.


Conclusion

The further along in this series I get, the more I am realizing I never gave JavaScript frameworks the credit they deserve, it’s always been eww JavaScript. So far, having a zero JavaScript solution like HTMX, I am thrilled at the idea of all processing being done server-side. I left last week’s post on jQuery feeling like “heck it might not be so bad.” BUT this week as I reflect on jQuery, it feels as though I spent more time than I would like worrying about DOM element selection/manipulation and less time in development. That’s where getting to Vue.js has really stood out to me. Even in the simplistic examples provided, I never felt like I was building selectors to manipulate the DOM or access a value. As someone who is more Python back-end focused, Vue.js felt more native to me compared to my previous interactions in writing JavaScript.

~ Jeremy



ntc img
ntc img

Contact Us to Learn More

Share details about yourself & someone from our team will reach out to you ASAP!

Django & JavaScript – Part 3 JavaScript & jQuery

Blog Detail

This is the third post in a multipart series that explores web front-end technologies and how they can be used with Django. You can find the previous post here: Django & JavaScript – Part 2 HTMX.

Requirements

  1. Single process to host the UI & API
  2. Django + Django REST framework
  3. Overarching web framework from day 1
  4. Intuitive UI/UX that can be dynamically updated without loading a new page

DRF API Class

This API class will be used for all examples using the API, in which DRF handles all PUT/PATCH/POST/GET/DELETE operations.

class UserViewSet(ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    filterset_class = UserFilterSet

Raw JavaScript + jQuery

By building the site based on raw JavaScript enriched by jQuery I am able to provide a tremendous amount of flexibility due to being beholden to fewer opinionated constraints. This simplistic solution pushed the the data processing and rendering to the client side, which is common practice in rich modern webpages. Where issues start to become apparent is as a project scales out. The continual need to add large amounts of JavaScript that is not constrained by an opinionated framework can lead to inconsistencies and tech debt to manage long-term.

Have you ever taken a look at a webpage’s JavaScript and seen $ used preceding a function call or a tuple with a string? There is a high likelihood that this was jQuery and the $ is the jQuery object. An example of invoking a function on the jQuery object would be $.ajax({....}). Whereas using jQuery to access and interact with an HTML element would be using $(<selector>, <content>...) syntax. If I wanted to select all <p> HTML elements and hide them via jQuery, I would use $("p").hide(). For the scope of this blog post we will be taking a simplified approach and focusing on jQuery selectors and the function calls that can be performed on the returned element(s). I will be using a mixture of JavaScript and jQuery in my blog post examples.

jQuery Selectors

jQuery selectors are very much as the name suggests—a method of selecting HTML element(s) via some type of descriptor. Keep in mind ANY function performed against a selector will apply to ALL items selected. This makes for quick updates to large amounts of elements, but you must be sure you are not changing elements that are not meant to be changed.

ID Selector

In HTML it is best practice for ID attributes of HTML elements be globally unique for everything in the DOM and to be able to select just the one element by the ID attribute it would be represented by with #<id attribute value> as the selector. In the example of <p id="jquery">My fun paragraph</p> I can access this element via $("#jquery").

Comparison
// jQuery
$("#jquery")

// JavaScript
document.getElementByID("jquery");

Element Selector

To select ALL HTML elements of an element type, you pass in a string representation of the element type. For instance, if I wanted to select all paragraph elements which are represented by <p> the jQuery selector would be $("p"). And if I wanted to select just the first appearance of a <p> element, I would use the same selector but add :first to the element type $("p:first").

Comparison
// jQuery
$("p")

// JavaScript
document.getElementsByTag("p");

Class Selector

Similar to element selectors, class selectors are meant to return all HTML elements, but in this scenario it is any element with a specific class attribute. To represent the class selector, use a . followed by the class name, as such $(.table).

Comparison
// jQuery
$(".table")

// JavaScript
document.getElementsByClassName("table");

Create Selector

Create selectors are done as the HTML element type in <> symbols and will create a single instance of an HTML element in memory that is not directly applied to the DOM. This can be done via selecting another element and manipulating the HTML from that element.

Comparison
<span role="button" tabindex="0" data-code="// jQuery $("tr:last").append( // Selects the last table row $("
// jQuery
$("tr:last").append( // Selects the last table row
  $("<td>").html("Table Data innerHTML") // Adds a table data to the table row with innerHTML
)

// JavaScript
let tr = document.getElementsByTagName("tr"); // Selects all table rows
tr[-1].innerHTML = document.createElement("td").innerHTML = "Table Data innerHTML"; // Creates and applies table data with innerHTML to the last index of table rows selected

jQuery Events

Applying a jQuery event to a jQuery selector will make it so on the event trigger. For that element the browser will execute JavaScript defined inside the event. Common event types are clickkeyupsubmitready, and change, however there are several more.

Paragraph Highlight Example

In this example, every time the mouse cursor enters a <p> HTML element it will change the background of that element to a light gray; and upon leaving, the background color will become unset for that element.

$(document).ready(function(){
  $("p").on({
    mouseenter: function(){
      $(this).css("background-color", "lightgray");
    },
    mouseleave: function(){
      $(this).css("background-color", "unset");
    },
  });
});

Example 1 – Build DOM from Button Click

Initial Page Without Profile Data

Profile Data

Page with Profile Data

Profile Data

Initial HTML

<span role="button" tabindex="0" data-code=" <div class="container"> <div class="col" id="dom-container"> <h1 id="container-heading">Waiting to load user profile.</h1> <button class="btn btn-primary mt-2" onclick="load_user_profile(this, '{{ request.user.id }}')"> Load User Profile </button> </div>

<div class="container">
  <div class="col" id="dom-container">
    <h1 id="container-heading">Waiting to load user profile.</h1>
    <button
      class="btn btn-primary mt-2"
      onclick="load_user_profile(this, '{{ request.user.id }}')">
      Load User Profile
    </button>
  </div>
</div>

JavaScript/jQuery

<span role="button" tabindex="0" data-code="function build_row(thead, tdata) { let row = $("<tr>"); row.append($("<th>").attr("scope","row").html(thead)); row.append($("<td>").html(tdata)); return row } function load_user_profile(field, user_id) { $.get("/api/users/users/".concat(user_id, "/"), function(data, status) { // Delete the `Load User Profile` button element field.remove() // Change header $("#container-heading").html = "User Profile"; // Build table let table = $("
function build_row(thead, tdata) {
  let row = $("<tr>");
  row.append($("<th>").attr("scope","row").html(thead));
  row.append($("<td>").html(tdata));
  return row
}

function load_user_profile(field, user_id) {
  $.get("/api/users/users/".concat(user_id, "/"), function(data, status) {
    // Delete the `Load User Profile` button element
    field.remove()

    // Change header
    $("#container-heading").html = "User Profile";

    // Build table
    let table = $("<table>").addClass("table");
    table.append(build_row("Name", data.display));
    table.append(build_row("Email", data.email));
    table.append(
      build_row(
        "Admin Panel Access",
        (data.is_staff || data.is_superuser) ? "Enabled" : "Disabled"
      )
    );
    table.append(
      build_row(
        "Super User",
        (data.is_superuser) ? "Enabled" : "Disabled"
      )
    );

    // Append table to div
    $("#dom-container").append(table);
  });
}

In this example we have almost the same base HTML as we did for the HTMX post. However, we do not have a User Profile template. Instead we are applying an onclick event to the button and passing in the button element and templating the user ID from Django. On click, I trigger the load_user_profile, and the first task is to remove the button from the DOM with a remove() function call. Next, I access the <h1> element via a jQuery ID Selector and change the innerHTML to User Profile. After changing the <h1>, I start building the table in memory with jQuery Create Selectors, in which the table row creation is wrapped with another function that creates the <tr><th>, and <td> elements. Once they are returned, I append them to the table. Lastly, after the table is fully built, I append the table to the <div> with an ID of dom-container. This is a fairly simplistic mix of jQuery and business logic in JavaScript to accomplish the same end result we had working with HTMX.

Example 2 – Input Validation

User Does Not Exist

Input Validation

User Does Exist

Input Validation

HTML Form

<span role="button" tabindex="0" data-code="<form method="POST" action="/create-user/" autocomplete='off'> <div class="form-group mb-3"> <label>Username</label> <input type="text" id="check_user" class="form-control" name="user"> <div class="text-danger mt-2" style="color:red;" id="user-err"></div> </div> <button type="submit" class="btn btn-success mt-2">Create User</button>
<form method="POST" action="/create-user/" autocomplete='off'>
  <div class="form-group mb-3">
    <label>Username</label>
    <input type="text"
      id="check_user"
      class="form-control"
      name="user">
    <div class="text-danger mt-2" style="color:red;" id="user-err"></div>
  </div>
  <button type="submit" class="btn btn-success mt-2">Create User</button>
</form>

JavaScript/jQuery

$("#check_user").keyup(function() {
  $.get("/api/users/users/?username=".concat(this.value), function(data, status){
    let err_div = $("#user-err")
    if (data.count == 1) {
      err_div.html("This username already exists");
    } else if (data.count == 0) {
      err_div.empty();
    };
  });
})

In this example I am using the keyup jQuery event applied to the check_user input via a jQuery event to inform the browser to trigger a JavaScript function that calls the underlying Users API with a query param of the username field passed in. This does make an assumption that the query param should return only 1 instance of the User object when we have an exact match, else there should be 0 instances returned. I could have also performed the selector via an element selector and limited it based on the name attribute, $("input[name='user']"). But this could in theory return more than one element; and when I access a specific element, I prefer to access it via an ID.


Conclusion

It’s been a moment since I had the opportunity to write jQuery, and I have been surprised by how much I enjoyed writing predominantly jQuery with a small amount of raw JavaScript sprinkled in. For those that know me, you know that although I know some JavaScript, it is not my favorite language to develop in. Maybe I will warm up to JavaScript a little more by the end of this evaluation??? Or I could forever stay in Python development.

~ Jeremy



ntc img
ntc img

Contact Us to Learn More

Share details about yourself & someone from our team will reach out to you ASAP!

CookieDurationDescription
__hssc30 minutesHubSpot sets this cookie to keep track of sessions and to determine if HubSpot should increment the session number and timestamps in the __hstc cookie.
__hssrcsessionThis cookie is set by Hubspot whenever it changes the session cookie. The __hssrc cookie set to 1 indicates that the user has restarted the browser, and if the cookie does not exist, it is assumed to be a new session.
cookielawinfo-checkbox-advertisement1 yearSet by the GDPR Cookie Consent plugin, this cookie records the user consent for the cookies in the "Advertisement" category.
cookielawinfo-checkbox-analytics11 monthsThis cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Analytics".
cookielawinfo-checkbox-functional11 monthsThe cookie is set by GDPR cookie consent to record the user consent for the cookies in the category "Functional".
cookielawinfo-checkbox-necessary11 monthsThis cookie is set by GDPR Cookie Consent plugin. The cookies is used to store the user consent for the cookies in the category "Necessary".
cookielawinfo-checkbox-others11 monthsThis cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Other.
cookielawinfo-checkbox-performance11 monthsThis cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Performance".
CookieLawInfoConsent1 yearCookieYes sets this cookie to record the default button state of the corresponding category and the status of CCPA. It works only in coordination with the primary cookie.
viewed_cookie_policy11 monthsThe cookie is set by the GDPR Cookie Consent plugin and is used to store whether or not user has consented to the use of cookies. It does not store any personal data.