DEV Community

Cover image for Building a Simple Ticket Tracker with Vanilla JavaScript
ebuka anthony
ebuka anthony

Posted on • Edited on

Building a Simple Ticket Tracker with Vanilla JavaScript

Learning and creating local storage apps in JavaScript before delving into databases offers several important benefits and insights that lay a solid foundation for understanding databases and more complex data management systems. Here's why it's crucial to start with local storage apps:

Fundamental Concepts

Local storage apps introduce you to core concepts of data storage, retrieval, and manipulation in a simplified environment. This includes understanding data structures, managing data lifecycles, and interacting with data through code.


Synchronous Operations

Local storage operations are synchronous, meaning they happen in a predictable sequence. This helps you understand the flow of data operations and how they impact the user experience.

Isolation from Network and Server Complexity

By working with local storage, you can concentrate solely on client-side code and data manipulation. This isolates you from the complexities of network communication, server setup, and database configuration, which can be overwhelming for beginners.


Data Modeling

Creating local storage apps requires you to think about data modeling – deciding what data to store, how to structure it, and how to retrieve it efficiently. This skill is directly transferable to designing database schemas later on.


Error Handling and Debugging

You'll encounter errors and bugs while working with local storage. Learning how to debug and handle these issues is a valuable skill that directly applies to working with databases.


Applicable Principles

Many principles you learn while working with local storage, such as data validation, security considerations, and handling different data types, directly carry over to database management.

In today's fast-paced world, effective task management is crucial for maintaining productivity and organization. Whether you're managing a small project or tracking customer support requests, having a system to keep track of tickets can greatly simplify your workflow. In this article, we'll guide you through the process of creating a simple ticket tracker using HTML, Bootstrap, and JavaScript.

With that said, let’s get started!

Requirements

Before we begin, make sure you have a basic understanding of HTML, Bootstrap, and JavaScript. We'll also be using Bootstrap to style our application, so having some familiarity with it would be beneficial.

Setting Up the Project

  • HTML Structure: Start by creating a new folder for your project and inside it, create an index.html file. Set up the basic HTML structure and include necessary libraries:

We’ll start by creating the HTML markup structure containing the DOM content of the ticket tracker. To keep things simple, we will need just a few HTML elements:

  • Link tag for css bootstrap integration for styling ,
  • A body onload tag to enable ticket issues to remain in DOM after each page reload
  • A section for ticket form data input divs

the following code below outlines the first two points;

At the bottom of the code, you link the javascript , bootstrap styling , and chanceJS for ticket ID Issuance.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <title>Document</title>
</head>
<body onload="fetchIssues()">  <!--This will be populated by JS-->


    <script src="main.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
    <script src="http://chancejs.com/chance.min.js"></script>
</body>
</html>

Enter fullscreen mode Exit fullscreen mode

Note : You can also use vanilla CSS to style your layouts, we are using bootstrap to ease time constraints in styling out.

Creating the Ticket Tracker Interface

  • Form for Ticket Entry: Within the of your index.html file, create a form to input new tickets:
<section class="container">   
        <h1>Ticktrak</h1>
        <section class="jumbotron">
            <h3>Add New Tracking Issue:</h3>
            <form id="issueInputForm">
                <div class="form-group">
                    <label for="issueSubjInput">Subject</label>
                    <input type="text" class="form-control" id="issueSubjInput" placeholder="Issue Subject...">
                </div>
                <div class="form-group">
                    <label for="issueDescInput">Description</label>
                    <input type="text" class="form-control" id="issueDescInput" placeholder="Issue Description...">
                </div>
                <div class="form-group">
                    <label for="issueSeverityInput">Severity</label>
                    <select class="form-control" id="issueSeverityInput">
                        <option value="Low">Low</option>
                        <option value="Medium">Medium</option>
                        <option value="High" selected>High</option>
                    </select>
                </div>
                <div class="form-group">
                    <label for="issueAssignedToInput">Assigned To</label>
                    <input type="text" class="form-control" id="issueAssignedToInput" placeholder="Enter Person Responsible...">
                </div>
                <button type="submit" class="btn btn-warning">Add</button> 
            </form>
        </section>
        <section class="row">
            <div class="col-lg-12">
                <div id="issuesList">


       <!-- empty div to be occupied  by JS when event listener is triggered—>


                </div>
            </div>


Enter fullscreen mode Exit fullscreen mode

Implementing JavaScript Logic:

Our ticket tracker isn’t working right now because we have yet to add some interactivity using JavaScript, which is exactly what we are going to do next.

We will have to add some event listeners to the input button for form submission and also storage of form data in local storage, and also automating listing of open issues in the DOM whenever they are clicked on.

To achieve this, we will tackle the JavaScript code needed to get our form input working section by section so that it’s easy to understand and follow along.

  • JavaScript for Ticket Handling: Create a main.js file in your project folder. This is where we'll handle the ticket creation and display logic.

  • Then we will write a function called fetchIssues() to facilitate display of stored form in local storage in the DOM. Under it, We create two variables respectively;

    • The first variable issues; for grabbing data stored as an Object stored in local storage, then parse it from a string into DOM data
      • The Second Variable issuesList; for grabbing data from issues variable and display in the empty html div in the DOM

    We log the console for emergency debugging and confirming the parsed data contents that was displayed;

function fetchIssues() {  
    let issues = JSON.parse(localStorage.getItem('issues'))
    let issuesList = document.getElementById('issuesList')
    console.log(issues)

}

Enter fullscreen mode Exit fullscreen mode
  • We then add a method under the variables for enabling displaying the data in html format
function fetchIssues() {  
    let issues = JSON.parse(localStorage.getItem('issues'))
    let issuesList = document.getElementById('issuesList')
    console.log(issues)

issuesList.innerHTML = '';

}

Enter fullscreen mode Exit fullscreen mode
  • We will run a for loop to get all available input data in each ticket stored as objects in local storage
function fetchIssues() {  
    let issues = JSON.parse(localStorage.getItem('issues'))
    let issuesList = document.getElementById('issuesList')
    console.log(issues)

issuesList.innerHTML = '';

for (let i = 0; i < issues.length; i++) { // run loop to get all available data in each ticket stored as objects in local storage
     let id = issues[i].id
        let subject = issues[i].subject
        let description = issues[i].description
        let severity = issues[i].severity
        let assignedTo = issues[i].assignedTo
        let status = issues[i].status
        let statusColor = status == "Closed" ? 'label-success' : 'label-info'

    }  


}

Enter fullscreen mode Exit fullscreen mode
  • We then style how our (now empty) form data of issues in html will appear in the DOM
function fetchIssues() {  
         let issues = JSON.parse(localStorage.getItem('issues'))
         let issuesList = document.getElementById('issuesList')
         console.log(issues)

         issuesList.innerHTML = '';

        for (let i = 0; i < issues.length; i++) {  // run loop to get all available data in each ticket stored as objects in local storage
        let id = issues[i].id
        let subject = issues[i].subject
        let description = issues[i].description
        let severity = issues[i].severity
        let assignedTo = issues[i].assignedTo
        let status = issues[i].status
        let statusColor = status == "Closed" ? 'label-success' : 'label-info'

        issuesList.innerHTML +=  //style your empty form data of issues in html 
        '<div class="well">' +
        '<h6>Issue ID:' + id + '</h6>' +
        '<p><span class= "label ' + statusColor + ' ">' + status + '</span></p>' +
        '<h3>' + subject + '</h3>' +
        '<p>' + description + '</p>' + 
        '<p><span class="glyphicon glyphicon-time"></span> ' + severity + ' ' + '<span class="glyphicon glyphicon-user"></span>' + assignedTo + '</p>' +
        '<a href="#" class="btn btn-warning" onclick="setStatusClosed(\''+id+'\')">Close</a> ' + //pass ticket id to be changed to closed from id through onclick function in button
        '<a href="#" class="btn btn-danger" onclick="deleteIssue(\''+id+'\')">Delete</a> ' //pass ticket id to be deleted through onclick function in button
        + '</div>'
    }  


}

Enter fullscreen mode Exit fullscreen mode
  • We then write another function saveIssues to be triggered by event listener to capture all data from the form of issues and save to local storage’

    The first variable issueID contains a function method chance.guid() which is an open source random ID number generator to assign form id with form data to variables to be stored in an object which is then to be stored in local storage

Then we create an object storing form data value with key-value pair of the variables created above the object

function saveIssue(e) {
              let issueId = chance.guid()
              let issueSubject = document.getElementById('issueSubjInput').value
              let issueDesc = document.getElementById('issueDescInput').value
              let issueSeverity = document.getElementById('issueSeverityInput').value
              let issueAssignedTo = document.getElementById('issueAssignedToInput').value
             let issueStatus = 'Open'

             let issue = {   // object storing form data value
                  id: issueId,
                  subject: issueSubject,
                 description: issueDesc,
                 severity: issueSeverity,
                 assignedTo: issueAssignedTo,
                 status: issueStatus
           }

        } 

Enter fullscreen mode Exit fullscreen mode
  • then we have to create our good old conditional statements to first create an empty array to contain the newly created object, then pushing new input data to empty array, then set the value identified by key to value pair, convert array elements to string & save the data in local storage.

OR if data already exists grab the existing issues, parse them & push them to an array, convert back to string & store in local storage

function saveIssue(e) { // function to be triggered by event listener to capture all data from the form of issues and save to local storage
    let issueId = chance.guid()   //assign form id with form data to variables to be stored in an object which is then to be stored in local storage
    let issueSubject = document.getElementById('issueSubjInput').value
    let issueDesc = document.getElementById('issueDescInput').value
    let issueSeverity = document.getElementById('issueSeverityInput').value
    let issueAssignedTo = document.getElementById('issueAssignedToInput').value
    let issueStatus = 'Open'

    let issue = {   // object storing form data value
        id: issueId,
        subject: issueSubject,
        description: issueDesc,
        severity: issueSeverity,
        assignedTo: issueAssignedTo,
        status: issueStatus
    }

    if(localStorage.getItem('issues')===null) { //if there is no data served up from local storage when user opens for first time 
        let issues = []// set up an array in this case
        issues.push(issue) //push new data to empty array
        localStorage.setItem('issues', JSON.stringify(issues)) // set the value identified by key to value pair, convert array elements to string & save the data
    } else {
        let issues = JSON.parse(localStorage.getItem('issues')) //if it exists grab existing issues, parse them  & convert to array
        issues.push(issue) //push to array
        localStorage.setItem('issues', JSON.stringify(issues)) //convert back to string & store in local storage
    }

}

Enter fullscreen mode Exit fullscreen mode
  • Under the Conditionals add a document read method to reset form after data is inputed, the adding to local storage to be saved
function saveIssue(e) { // function to be triggered by event listener to capture all data from the form of issues and save to local storage
    let issueId = chance.guid()   //assign form id with form data to variables to be stored in an object which is then to be stored in local storage
    let issueSubject = document.getElementById('issueSubjInput').value
    let issueDesc = document.getElementById('issueDescInput').value
    let issueSeverity = document.getElementById('issueSeverityInput').value
    let issueAssignedTo = document.getElementById('issueAssignedToInput').value
    let issueStatus = 'Open'

    let issue = {   // object storing form data value
        id: issueId,
        subject: issueSubject,
        description: issueDesc,
        severity: issueSeverity,
        assignedTo: issueAssignedTo,
        status: issueStatus
    }

    if(localStorage.getItem('issues')===null) { //if there is no data served up from local storage when user opens for first time 
        let issues = []// set up an array in this case
        issues.push(issue) //push new data to empty array
        localStorage.setItem('issues', JSON.stringify(issues)) // set the value identified by key to value pair, convert array elements to string & save the data
    } else {
        let issues = JSON.parse(localStorage.getItem('issues')) //if it exists grab existing issues, parse them  & convert to array
        issues.push(issue) //push to array
        localStorage.setItem('issues', JSON.stringify(issues)) //convert back to string & store in local storage
    }

    document.getElementById('issueInputForm').reset(); //reset form after input data & adding to local storage to be saved

    fetchIssues() //display, style new form issues

    e.preventDefault() //for the event listener to trigger the saveIssues function only and nothing else
}

Enter fullscreen mode Exit fullscreen mode
  • We will then write another function setStatusClosed to grab outform issue , display issue in html in the DOM, then change status & resave in local storage, we will loop through the issues ,then we write a conditional to flip the status if issue id matches id to be passed in, then we “stringify” the issue to convert issue with flipped status back to string & store in local storage
function saveIssue(e) { // function to be triggered by event listener to capture all data from the form of issues and save to local storage
    let issueId = chance.guid()   //assign form id with form data to variables to be stored in an object which is then to be stored in local storage
    let issueSubject = document.getElementById('issueSubjInput').value
    let issueDesc = document.getElementById('issueDescInput').value
    let issueSeverity = document.getElementById('issueSeverityInput').value
    let issueAssignedTo = document.getElementById('issueAssignedToInput').value
    let issueStatus = 'Open'

    let issue = {   // object storing form data value
        id: issueId,
        subject: issueSubject,
        description: issueDesc,
        severity: issueSeverity,
        assignedTo: issueAssignedTo,
        status: issueStatus
    }

    if(localStorage.getItem('issues')===null) { //if there is no data served up from local storage when user opens for first time 
        let issues = []// set up an array in this case
        issues.push(issue) //push new data to empty array
        localStorage.setItem('issues', JSON.stringify(issues)) // set the value identified by key to value pair, convert array elements to string & save the data
    } else {
        let issues = JSON.parse(localStorage.getItem('issues')) //if it exists grab existing issues, parse them  & convert to array
        issues.push(issue) //push to array
        localStorage.setItem('issues', JSON.stringify(issues)) //convert back to string & store in local storage
    }

    document.getElementById('issueInputForm').reset(); //reset form after input data & adding to local storage to be saved

    fetchIssues() //display, style new form issues

    e.preventDefault() //for the event listener to trigger the saveIssues function only and nothing else

function setStatusClosed(id) { //to grab out issue , display issue, change status & resave in local storage
    let issues = JSON.parse(localStorage.getItem('issues')) 
    for(let i=0; i < issues.length; i++) { //loop through issues
        if(issues[i].id === id) { //if issue id matches id to be passed in, grab it out to dom, flip status 
            issues[i].status = "Closed"
        }
    }

    localStorage.setItem('issues', JSON.stringify(issues)) //convert issue with flipped status back to string & store in local storage, 

    fetchIssues()
}

}

Enter fullscreen mode Exit fullscreen mode
  • We then write a function deleteIssue to pull issue out, attend to the form issue, get the data we want, delete & save back in local storage
 function saveIssue(e) { // function to be triggered by event listener to capture all data from the form of issues and save to local storage
    let issueId = chance.guid()   //assign form id with form data to variables to be stored in an object which is then to be stored in local storage
    let issueSubject = document.getElementById('issueSubjInput').value
    let issueDesc = document.getElementById('issueDescInput').value
    let issueSeverity = document.getElementById('issueSeverityInput').value
    let issueAssignedTo = document.getElementById('issueAssignedToInput').value
    let issueStatus = 'Open'

    let issue = {   // object storing form data value
        id: issueId,
        subject: issueSubject,
        description: issueDesc,
        severity: issueSeverity,
        assignedTo: issueAssignedTo,
        status: issueStatus
    }

    if(localStorage.getItem('issues')===null) { //if there is no data served up from local storage when user opens for first time 
        let issues = []// set up an array in this case
        issues.push(issue) //push new data to empty array
        localStorage.setItem('issues', JSON.stringify(issues)) // set the value identified by key to value pair, convert array elements to string & save the data
    } else {
        let issues = JSON.parse(localStorage.getItem('issues')) //if it exists grab existing issues, parse them  & convert to array
        issues.push(issue) //push to array
        localStorage.setItem('issues', JSON.stringify(issues)) //convert back to string & store in local storage
    }

    document.getElementById('issueInputForm').reset(); //reset form after input data & adding to local storage to be saved

    fetchIssues() //display, style new form issues

    e.preventDefault() //for the event listener to trigger the saveIssues function only and nothing else

function setStatusClosed(id) { //to grab out issue , display issue, change status & resave in local storage
    let issues = JSON.parse(localStorage.getItem('issues')) 
    for(let i=0; i < issues.length; i++) { //loop through issues
        if(issues[i].id === id) { //if issue id matches id to be passed in, grab it out to dom, flip status 
            issues[i].status = "Closed"
        }
    }

    localStorage.setItem('issues', JSON.stringify(issues)) //convert issue with flipped status back to string & store in local storage, 

    fetchIssues()
} 

    function deleteIssue (id) { //pull issue out cut out what we want to delete & save back
    let issues = JSON.parse(localStorage.getItem('issues'))
    for(let i=0; i < issues.length; i++) {
        if(issues[i].id === id) {
            issues.splice(i,1) //splicing at current indexin the array , cutting it in half, extract element & rejoin
        }
    }

    localStorage.setItem('issues', JSON.stringify(issues))

    fetchIssues()


}

Enter fullscreen mode Exit fullscreen mode

Testing the Ticket Tracker

  • Testing: Open your index.html file in a web browser. You should see the ticket entry form and an empty list below it. Fill out the form and click the "Submit Ticket" button. Your entered ticket should appear in the list.

Image description

  • Congratulations! You've successfully created a simple ticket tracker using HTML, Bootstrap, and JavaScript. This basic implementation can serve as a starting point for further enhancements such as adding features like ticket status, due dates, or even connecting the tracker to a backend for data persistence.

You can find the full source code associated with this tutorial on  Github.

Remember that this is just a simple example, and real-world applications might require additional validation, security measures, and more advanced functionality. Happy coding!

Top comments (0)