Michael
Michael

Reputation: 335

Can't Integrate HTML's DOM in NodeJS/ExpressJS

Good day, hoomans! I badly need your help!

My question: Is there any way (in NodeJS/ExpressJS) to dynamically alter an element such as adding new li upon POST request like after clicking the submit button (without using jsdom though)?

I am developing a very simple application which should allow a client (student) to keep track of the lessons in his/her every class, monitor his/her grades, etc., with simple dynamic and real-time features. I am using Node.js EXPRESS, mySQL and AngularJS for this.


I created a feature where a student can search for a class then receive a list of results real-time, which basically means I want to dynamically add li elements upon request without reloading the page or redirecting the client to another page.

The process is: (1) the student types in the name of a class, (2) pass the textfield's value upon request (post), (3) use the value as key for database search, (4) then return the data extracted from the database by populating the ul element.

The database thing works. It is able to retrieve data from the database using the value entered in the textfield. However, when I add the code to dynamically add li element, I receive a runtime error every time the request is made. The code for this is found inside routes/grades.js.

The error I get is:

throw err; // Rethrow non-MySQL errors ^

TypeError: req.body.getElementById is not a function

and that is caused by this code block

var ul = req.body.getElementById("search");

var li = req.body.createElement("li");

li.appendChild(req.body.createTextNode(reqObj["className"]));

I believe createElement and createTextNode will also cause the same error message.

I think this can be solved with jsdom but when I tried to install it through npm, I only received lots of errors so I gave up. Besides, I believe there is a simpler solution to this that I don't know yet.

Here are the primary files for the functionality I mentioned above.

views/grades.ejs

<!DOCTYPE html>
<html ng-app="ClassSaber">
<head>
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
    <script src="javascripts/app.js" type="text/javascript"></script>
</head>

<body ng-controller="classController">
<% include template/header2.ejs %>

<div class="lessons" id="homebranding_top">
    <section>
        <h1>Grades</h1>
        <hr>
    </section>

    <section>
        <p>
            Keeping track of your progress eh? That's great! Search your class and we'll show your grade.
        </p>
    </section>

    <form>
        <section id="class_search">
            <input type="text" id="search" ng-model="data.className" placeholder="Enter your class here...">
            <input type="submit" value="Search" ng-click="classFun()">

            <ul id="class_list">
              <li ng-repeat="item in list | filter: data.className">
                  {{item.Class_Name}}
              </li>
            </ul>
        </section>
    </form>
</div>
</body>
</html>

routes/grades.js

    var express = require('express');
var router = express.Router();
var path = require('path');

/* GET lessons page. */
router.get('/grades', function(req, res, next) {
    res.render('grades', { title: 'Class Saber | Grades' });
});

module.exports = router;

//search class in the database
router.post('/grades', function(req, res, next){
    try{
        var reqObj = req.body;
        console.log("Request Object: " + reqObj["className"]);
        req.getConnection(function(err, conn){
            if(err){
                console.error('SQL Connection error: ', err);
                return next(err);
            }
            else{
                var insertSql = "SELECT Class_Name FROM classes WHERE Class_Name LIKE ?";
                var insertValues = [
                    '%' + reqObj["className"] + '%'
                ];
                var query = conn.query(insertSql, insertValues, function(err, result){
                    if(err){
                        console.error('SQL error: ', err);
                        return next(err);
                    }
                    var ul = req.body.getElementById("search");
                    var li = req.body.createElement("li");
                    li.appendChild(req.body.createTextNode(reqObj["className"]));
                });
            }
        });
    }
    catch(ex){
        console.err("Internal error: " + ex);
        return next(ex);
    }
});

public/javascripts/app.js

var app = angular.module('ClassSaber', []);

app.controller('classController', function($scope, $http){
    $scope.data = {};

    $scope.list = [
        {Class_Name: 'Math 101'},
        {Class_Name: 'Physics 101'},
        {Class_Name: 'Major Elective: Quantum Mechanics'}
    ];

    $scope.classFun = function(){
        console.log('Client triggered class search function...');
        $http({
            url: 'http://localhost:3000/grades',
            method: 'POST',
            data: $scope.data
        }).then(function (httpResponse){
            console.log('response', httpResponse);
        })
    }
});

Upvotes: 0

Views: 1459

Answers (2)

Michael
Michael

Reputation: 335

The concept that t.niese gave me was really helpful and I was able to fix the problem.

Here are the changes I made as per the suggested answer:

In my routes

var query = conn.query(insertSql, insertValues, function(err, result){
    if(err){
          console.error('SQL error: ', err);
          return next(err);
    }
    var class_array = [];
    for(var i=0; i<result.length; i++){
          class_array.push(result[i]);
    }
    console.log(class_array.valueOf());
    res.send([{
        info: class_array.valueOf()
    }])
});

and in my client-side code

$scope.classFun = function(){
    console.log('Client triggered class search function...');
    $http({
        url: 'http://localhost:3000/grades',
        method: 'POST',
        data: $scope.data
    }).then(function (httpResponse){
        console.log('response', httpResponse);
        var tbody = document.getElementById("class_list_data");
        while(tbody.firstElementChild){
            tbody.removeChild(tbody.firstChild);
        }
        for(var i=0; i<httpResponse.data.length; i++){
            for(var j=0; j<httpResponse.data[i].info.length; j++){
                var tr = document.createElement("tr");
                var td = document.createElement("td");
                td.appendChild(document.createTextNode(httpResponse.data[i].info[j].Class_Name.toString()));
                tr.appendChild(td);
                tbody.appendChild(tr);
            }
        }
    })
}

Also, I made little changes with my template. Instead of list, I use table.

<div id="homebranding_middle">
    <table id="class_list">
        <thead>
            <th>CLASS DESCRIPTION</th>
            <th>SCHEDULE</th>
            <th>CLASSROOM ASSIGNMENT</th>
            <th>INSTRUCTOR</th>
        </thead>

        <tbody id="class_list_data">
            <tr id="class_list_row" ng-repeat="item in list | filter: {Class_Name: data.className}">
                <td><a href="">{{item.Class_Name}}</a></td>
                <td>{{item.Class_Code}}</td>
                <td>{{item.Class_Room}}</td>
                <td>{{item.Class_Instructor}}</td>
                <dynamic></dynamic>
            </tr>
        </tbody>
    </table>
</div>

All in all, my /grades page can actually do something now. I get status code 200 and all retrieved data from the DB could be played out.

localhost:3000/grades

Going pretty well.

Many thanks to t.niese! Cheers!

Upvotes: 1

t.niese
t.niese

Reputation: 40852

req.body contains the body of the request (the data you pass with your $http call) , and not the body element of the DOM. You cannot access client side elements (like DOM) from a server side environment (like nodejs) and the other way round. If you want to make changes to the DOM then return the instructions what has to be changed with your response, and to the corresponding changes in the browser.

I do not use angular so I can't tell you directly how to do it. But in general it would be something like that:

var query = conn.query(insertSql, insertValues, function(err, result) {
  if (err) {
    console.error('SQL error: ', err);
    return next(err);
  }

  // response with a list of action to be applied
  res.send([{
     action : 'append-to-dom',
     info : {
       /* all informations you need for this e.g. reqObj["className"] */
     }
  }]);
});

In your client side code you would check for the actions you have to do:

$http({
  url: 'http://localhost:3000/grades',
  method: 'POST',
  data: $scope.data
}).then(function(httpResponse) {
   // check the content of your response if it contains actions
   /*
    var ul = req.body.getElementById("search");
    var li = req.body.createElement("li");
    li.appendChild(req.body.createTextNode( .. the data from your response ...  ))
   */
})

How you would structure the response is up to you and depends on the environment. Angular might already have a specific pattern how to do this, but as I said, I do not use angular so I can't tell you.

Upvotes: 1

Related Questions