WebFund 2024F: Tutorial 7: Difference between revisions

From Soma-notes
Line 20: Line 20:


<syntaxhighlight lang="html" line>
<syntaxhighlight lang="html" line>
<!DOCTYPE html>
<html>
  <head>
    <title>COMP 2406 Submissions Demo</title>
    <link rel="stylesheet" href="/style.css">
  </head>
  <body>
    <h1>COMP 2406 Submissions Demo</h1>


    <p>Please select what you would like to do:
      <ol>
        <li><a href="/upload.html">Upload a submission.</a></li>
        <li><a href="/list.html">List submissions.</a></li>
        <li><a href="/analyze">Analyze submissions.</a></li>       
      </ol>
      </p>
  </body>
</html>
</syntaxhighlight>
</syntaxhighlight>



Revision as of 02:39, 7 November 2024

In this tutorial you will be playing with Submission Demo, which is an integration of the code from Tutorial 5 and Tutorial 6.

Tasks

  1. Create a file that validates correctly and upload it. Then, check the listing and analysis pages: is your upload reflected there?
  2. Create a file that does not validate and attempt to upload it. What happens? Why?
  3. Are the validation and error messages the same as in Tutorial 6?
  4. Change the app so it accepts a field "Section: " at the top of of the assignment just after the Student ID. This section should be added to the output of the validation, the output after a submission is uploaded, and the submission listing. What files did you change? How did you change them?

Code

Code for Submission Demo

submitdemo.js

static/index.html

<!DOCTYPE html>
<html>
  <head>
    <title>COMP 2406 Submissions Demo</title>
    <link rel="stylesheet" href="/style.css">
  </head>
  <body>
    <h1>COMP 2406 Submissions Demo</h1>

    <p>Please select what you would like to do:
      <ol>
        <li><a href="/upload.html">Upload a submission.</a></li>
        <li><a href="/list.html">List submissions.</a></li>
        <li><a href="/analyze">Analyze submissions.</a></li>        
      </ol>
      </p>
  </body>
</html>

static/upload.html

<!DOCTYPE html>
<html>
  <head>
    <title>COMP 2406 Submissions Demo: Upload</title>
    <link rel="stylesheet" href="/style.css">
    <script type="text/javascript" src="/validator.js"></script>
  </head>
  <body onload="hideAnalysis()">
    <h1>Upload Submission</h1>

    <form>
      <input type="file" id="assignmentFile"
             onchange="loadAssignment(this, false)" />
      <button type="button" onclick="uploadSubmission()">Upload</button>
    </form>

    <div id="analysis">
      <p><b>Status:</b> <span id="status">UNKNOWN</span></p>
      <p>Filename: <span id="filename"></span></p>
      <p>Name: <span id="name"></span><br>
        Student ID: <span id="studentID"></span> <i>(Please check that your ID number is correct!)</i></p>
      <hr>
      <div id="questions"><p><b>Questions:</b> <i>(Please check that your answers are numbered correctly!)</i></p></div>
    </div>
  </body>
</html>

static/validator.js

// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2024 Anil Somayaji
//
// validator.js, part of submitdemo
// for COMP 2406 (Fall 2024), Carleton University
// 
// Initial version: November 6, 2024
//

const filePrefix = "comp2406-tutorial6";
const submissionName = "COMP 2406 2024F Tutorial 6"
const expectedQuestionList = "1,2,3,4,5";
var analysisSection;

function loadAssignment(fileInput, upload) {
    analysisSection.hidden = false;

    if(fileInput.files[0] == undefined) {
        updateTag("status", "ERROR: No files to examine.")
        return;
    }
    
    var reader = new FileReader();
    reader.onload = function(ev) {
        var content = ev.target.result;
        var fn = fileInput.files[0].name
        var q = checkSubmission(fn, content);
        if (upload) {
            doUploadSubmission(fn, q);
        }
    };
    reader.onerror = function(err) {
        updateTag("status", "ERROR: Failed to read file");
    }
    reader.readAsText(fileInput.files[0]);

}

var numQuestions = expectedQuestionList.split(",").length;

function updateTag(id, val) {
    document.getElementById(id).innerHTML = val;
}

function lineEncoding(lines) {
    var n, i, s;
    
    for (i = 0; i < lines.length; i++) {
        s = lines[i];
        n = s.length;
        if (s[n-1] === '\r') {
            return "DOS/Windows Text (CR/LF)";
        }
    }
    
    return "UNIX";
}

function checkSubmission(fn, f) {
    var lines = f.split('\n')
    var c = 0;
    var questionList = [];
    var questionString;
    var q = {};
    var i;
    var lastQuestion = null;
    const fnPattern = filePrefix + "-[a-zA-Z0-9]+\.txt";
    const fnRexp = new RegExp(fnPattern);
    
    if (!fnRexp.test(fn)) {
        updateTag("status", "ERROR " + fn +
                  " doesn't follow the pattern " + fnRexp);
        return;
    }

    if (fn === filePrefix + "-template.txt") {
        updateTag("status", "ERROR " + fn +
                  " has the default name, please change template to your mycarletonone username");
        return;
    }
    
    updateTag("filename", fn);
    
    let encoding = lineEncoding(lines);
    if (encoding !== "UNIX") {
        updateTag("status", "ERROR " + fn +
                  " is not a UNIX textfile, it is a "
                  + encoding + " file.");
        return;
    }
    
    if (submissionName !== lines[0]) {
        updateTag("status", "ERROR " + fn +
                  " doesn't start with \"" + submissionName + "\"");
        return;
    }
    
    try {
        q.name = lines[1].match(/^Name:(.+)/m)[1].trim();
        q.studentID = lines[2].match(/^Student ID:(.+)/m)[1].trim();
    } catch (error) {
        updateTag("status", "ERROR " + fn +
                  " has bad Name or Student ID field");
        return;
    }

    updateTag("name", q.name);
    updateTag("studentID", q.studentID);
    
    var questionRE = /^([0-9a-g]+)\.(.*)/;
    
    for (i = 4; i < lines.length; i++) {
        if (typeof(lines[i]) === 'string') {
            lines[i] = lines[i].replace('\r','');
        }
        
        let m = lines[i].match(questionRE);
        if (m) {
            c++;
            questionList.push(m[1]);
            q[m[1]] = m[2];
            lastQuestion = m[1];
        } else {
            if (lastQuestion !== null) {
                if ((q[lastQuestion] === '') || (q[lastQuestion] === ' ')) {
                    q[lastQuestion] = lines[i];
                } else {
                    q[lastQuestion] = q[lastQuestion] + "\n" + lines[i];
                }
            }
        }
    }

    console.log(JSON.stringify(q, null, '   '));

    questionString = questionList.toString();
    if (questionString !== expectedQuestionList) {
        updateTag("status", "ERROR expected questions " +
                  expectedQuestionList + " but got questions " +
                  questionString);
    } else {
        updateTag("status", "PASSED " +
                  fn + ": " + q.name + " (" + q.studentID + ")");
    }
    
    var newP, newText, newPre, newPreText;
    let questionDiv = document.getElementById("questions");
    for (let qName of questionList) {
        let qText = q[qName];
        newP = document.createElement("p");
        newText = document.createTextNode(qName + ":");
        newP.appendChild(newText);
        questionDiv.appendChild(newP);
        newPre = document.createElement("pre");
        newPreText = document.createTextNode(qText);
        newPre.appendChild(newPreText);
        newP.appendChild(newPre);
    }
    
    return q;
}

function hideAnalysis() {
    analysisSection = document.getElementById("analysis");
    analysisSection.hidden = true;
}

function uploadSubmission() {
    var assignmentFile = document.getElementById("assignmentFile");
    loadAssignment(assignmentFile, true);
}

function doUploadSubmission(fn, q) {
    console.log("sending data to server...");
    if (q) {
        const request = new Request("/uploadSubmission", {
            method: "POST",
            body: JSON.stringify(q),
            headers: {
                'Content-Type': 'application/json'
            }
        });

        fetch(request).then((response) => {
            if (response.status === 200) {
                updateTag("status", "UPLOAD COMPLETE of " + fn);
            } else {
                updateTag("status", "UPLOAD ERROR of " + fn +
                          ", failed with status " +
                          response.status);
            }
        });
    } else {
        updateTag("status", "ERROR No valid data to upload!");
    }
}

static/list.html

<!DOCTYPE html>
<html>
  <head>
    <title>COMP 2406 Submission Demo: Submission Listing</title>
    <link rel="stylesheet" href="/style.css">
    <script src="/list.js" defer></script>
  </head>
  <body>
    <h1>Submission Listing</h1>
    <div>
      <div></div>
      <table>
        <thead>
          <th>DB ID</th>
          <th>Student ID</th>
          <th>Name</th>
          <th>Question 1</th>
          <th>Question 2</th>
          <th>Question 3</th>
          <th>Question 4</th>
          <th>Question 5</th>
        </thead>
        <tbody id="table"/>
      </table>
    </div>
    <form method="get" action="/">
      <button type="submit">Home</button>
    </form>
  </body>
</html>

static/list.js

// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2024 Anil Somayaji
//
// list.js, part of submitdemo
// for COMP 2406 (Fall 2024), Carleton University
// 
// Initial version: November 6, 2024
//

function insertTableData(tableData) {
    var t = document.querySelector("#table");
    var row = [];
   
    function rowMarkup(s) {
        return "<td>" + s + "</td>";
    }

    for (let r of tableData) {
        row.push("<tr>")
        row.push(rowMarkup(r.id));
        row.push(rowMarkup(r.studentID));
        row.push(rowMarkup(r.name));
        row.push(rowMarkup(r.q1));
        row.push(rowMarkup(r.q2));
        row.push(rowMarkup(r.q3));
        row.push(rowMarkup(r.q4));
        row.push(rowMarkup(r.q5));
        row.push("</tr>")
    }

    t.innerHTML = row.join("\n");
}


async function updateTable() {
    console.log("Updating Table...");
    
    try {
        const response = await fetch("/list");
        if (response.ok) {
            const tableData = await response.json();
            insertTableData(tableData);
        } else {
            console.error("Table loading error response: " + response.status);
        }
    } catch (error) {
        console.error("Table loading fetch error: " + error.message);
    }
}

updateTable().then(() => {console.log("Table updated!");});

static/style.css

body {
  padding: 50px;
  font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}

a {
  color: #00B7FF;
}