Отобразить детали без перезагрузки страницы

Как отобразить детали без перезагрузки страницы?

Логика
- пользователь кликает по ссылкам;
- результат: в области Placeholder отображается поле content;


12.08.2022_17-55-53

home.controller.js

const fs = require('fs');

exports.getHomePage = (req, res) => {
    let query = "SELECT * FROM `articles` ORDER BY id ASC"; // query database to get all the players

    // execute query
    db.query(query, (err, result) => {
        if (err) {
            res.redirect('/');
        }

        res.render('index.ejs', {
            title: "Welcome to DB",
            articles: result
        });
    });
};

index.routes.js

const express = require("express");
const router = express.Router();
const homeController = require('../controllers/home.controller');
router.get('/', homeController.getHomePage);
module.exports = router;

app.js

const express = require('express');
const fileUpload = require('express-fileupload');
const bodyParser = require('body-parser');
const mysql = require('mysql');
const path = require('path');

const app = express();

const articleRoutes = require('./routes/article.routes');
const homeRoutes = require('./routes/index.routes');
const port = 2000;


// create connection to database
// the mysql.createConnection function takes in a configuration object which contains host, user, password and the database name.
const db = mysql.createConnection ({
    host: 'localhost',
    user: 'root',
    password: '',
    database: 'socka'
});

// connect to database
db.connect((err) => {
    if (err) {
        throw err;
    }
    console.log('Connected to database');
});
global.db = db;

// configure middleware
app.set('port', process.env.port || port);                  // set express to use this port
app.set('views', __dirname + '/views');                     // set express to look in this folder to render our view
app.set('view engine', 'ejs');                              // configure template engine
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());                                 // parse form data client
app.use(express.static(path.join(__dirname, 'public')));    // configure express to use public folder
app.use(fileUpload());                                      // configure fileupload

// routes for the app
app.use('/', homeRoutes);
// app.use('/player', playerRoutes);
app.use('/article', articleRoutes);
app.get('*', function(req, res, next){
    res.status(404);

    res.render('404.ejs', {
        title: "Page Not Found",
    });

});

// set the app to listen on the port
app.listen(port, () => {
    console.log(`Server running on port: ${port}`);
});

index.ejs

<!doctype html>
<html lang="ru">
<head>
    <!-- Кодировка веб-страницы -->
    <meta charset="utf-8">
    <!-- Настройка viewport -->
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>...</title>
    
    <!-- Bootstrap CSS (jsDelivr CDN) -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
    <!-- Bootstrap Bundle JS (jsDelivr CDN) -->
    <!--
    <script defer src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
    -->
</head>
<!-- <body> -->
    <body>
        <!-- class="container" -->
        <div class="container">
            
            <!-- div -->
            <div class="border border-success border-3 p-2 vh-100 d-flex flex-column">
                <!-- HEADER  --- --- -- --- --- -- --- --- -- --- --- -- --- --- -- --- --- -- --- --- -- --- --- -- --- --- ---->
                <!-- <header class="py-4 text-center text-uppercase fs-4 bg-primary bg-gradient text-white">
                    Header
                    </header> -->
                    
                    <!-- ROW  --- --- --- -- --- --- -- --- --- -- --- --- -- --- --- -- --- --- -- --- --- -- --- --- --- --- -->
                    <div class="row py-3 flex-grow-1">
                        
                        <!-- LEFT MENU  --- --- --- -- --- --- -- --- --- -- --- --- -- --- --- -- --- --- -- --- --- -- --- --- -- -->
                        <div class="col-3 d-flex">
                            <aside class="bg-danger bg-gradient w-100 d-flex justify-content-left align-items-center text-dark fs-5">                                
                                <% if (articles.length > 0) { %>
                                    <ul>
                                        <% articles.forEach((article, index) => { %>
                                        <li>
                                            <input type="hidden" class="articles" name="articles[]" value="<%= article.id %>">     
                                            <span>
                                                <a style="font-weight: bold;" href="#"><%=article.title%></a>
                                            </span>                                
                                            <span>
                                                <a style="font-weight: bold;" href="#"><%=article.owner%></a>
                                            </span>                                
                                            <span>
                                                <a style="font-weight: bold;" href="#"><%=article.number%></a>
                                            </span>                                
                                            <span>
                                                <a style="font-weight: bold;" href="#"><%=article.pos%></a>
                                            </span>
                                            
                                            <!-- <th scope="row"><%= article.id %></th> -->                                                                        
                                            <!-- <td><%= article.title%></td> -->
                                            <!-- <td><%= article.content %></td>
                                            <td><%= article.background %></td> -->
                                            <!-- <td><%= article.owner %></td> -->
                                            <!-- <td>@<%= article.number %></td> -->
                                            <!-- <td>@<%= article.pos %></td>
                                            <td>@<%= article.lang %></td> -->
                                        </li>
                                        <%})%>
                                    </ul>
                                <%}%>
                                </ul>
                            </aside>
                        </div>
                        <!-- PLACEHOLDER  --- --- --- -- --- --- -- --- --- -- --- --- -- --- --- -- --- --- -- --- --- -- --- --- -- -->
                        <div class="col-9 d-flex">
                            <main class="bg-light bg-gradient w-100 d-flex justify-content-center align-items-center text-dark fs-5 border border-success border-3">
                            Placeholder
                            </main>
                        </div>
                        <!-- RIGHTBAR  --- --- --- -- --- --- -- --- --- -- --- --- -- --- --- -- --- --- -- --- --- -- --- --- -- -->
                        <!-- <div class="col-3 d-flex">
                            <aside class="bg-danger bg-gradient w-100 d-flex justify-content-center align-items-center text-white fs-5">
                            Right Bar
                            </aside>
                        </div> -->
                        
                    </div>
                    
                    <!-- FOOTER -->
                    <!-- <footer class="py-4 bg-info bg-gradient text-center text-uppercase fs-4 text-white">
                    Footer
                    </footer> -->
                    
                </div>
                
            </div> <!-- .container -->
            
        </body>
        </html>

SQL

CREATE DATABASE socka;

--
-- Структура таблицы `articles`
--
DROP TABLE IF EXISTS `articles`;
CREATE TABLE IF NOT EXISTS `articles` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) NOT NULL,
  `content` text,
  `background` text,
  `owner` int(11) DEFAULT NULL,
  `pos` int(11) DEFAULT '0',
  `lang` int(11) DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=275 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;


--
-- Структура таблицы `articles`
--
INSERT INTO `articles` (`id`, `title`, `content`, `background`, `owner`, `pos`, `lang`) VALUES
(1, 'Articles-1', 'Content--Articles-1', NULL, NULL, 0, 1),
(2, 'Articles-1-1', 'Content--Articles-1-1', NULL, 1, 0, 1),
(3, 'Articles-1-2', 'Content--Articles-1-2', NULL, 1, 0, 1),
(4, 'Articles-1-3', 'Content--Articles-1-3', NULL, 1, 0, 1),
(5, 'Articles-1-1-1', 'Content--Articles-1-1-1', NULL, 2, 0, 1),
(6, 'Articles-1-1-2', 'Content--Articles-1-1-2', NULL, 2, 0, 1),
(7, 'Articles-1-1-3', 'Content--Articles-1-1-3', NULL, 2, 0, 1),
(8, 'Articles-2', 'Content--Articles-2', NULL, NULL, 0, 1),
(9, 'Articles-3', 'Content--Articles-3', NULL, NULL, 0, 1),
(10, 'Articles-4', 'Content--Articles-4', NULL, NULL, 0, 1),
(11, 'Articles-5', 'Content--Articles-5', NULL, NULL, 0, 2);

Сейчас у тебя страница перезагружается, потому что переход оформлен с помощью ссылок (что семантически правильно, пусть так и будет).
Чтобы все взлетело так как ты хочешь, придется добавлять функционал и на фронтенде и на бэкенде.
Бэкенд:

  1. Новый роут, который будет возвращать данные для каждого Article (хоть JSON, хоть HTML-строка).
  2. Каждая ссылка должна знать, откуда ей брать данные. Туда можно, например, добавить атрибут с каким-нибудь articleId.

Фронт:

  1. При нажатии на ссылку смотреть, какой у статьи ID
  2. Отправлять запрос на новый бэкенд роут
  3. Рендерить полученные данные в твой VIEW
1 симпатия

А ниже описанное сделать нельзя?
Я до конца не понимаю как это работает…

Текущая логика:

  • контролер передаёт articles в страницу index.ejs;
  • выполняет <% articles.forEach((article, index) => { %>;
  • результат: в LEFT MENU получаем список Article.

Моя фантазия: (на правах бреда)

  • на странице index.ejs создать скрипт с функцией setMessage(idArticle), которая принимает параметр id;
  • каждому тегу <a> прописать onclick="setMessage(<%=article.id%>)";
  • функция по id получает контент статьи;

Код.
index.ejs

// Произвольный код....
 <!-- LEFT MENU  --- --- --- -- --- --- -- --- --- -- --- --- -- --- --- -- --- --- -- --- --- -- --- --- -- -->
                        <div class="col-3 d-flex">
                            <aside class="bg-danger bg-gradient w-100 d-flex justify-content-left align-items-center text-dark fs-5">                                
                                <% if (articles.length > 0) { %>
                                    <ul>
                                        <% articles.forEach((article, index) => { %>
                                        <li>
                                            <input type="hidden" class="articles" name="articles[]" value="<%= article.id %>">     
                                            <span>
                                                <a style="font-weight: bold;" href="#" onclick="setMessage(<%=article.id%>)"><%=article.title%></a>
                                            </span>                                

/// произвольный код .... ... 
  <script>
                function setMessage(id) {
                var msg = document.querySelector('main');
                    msg.textContent = articles[id].content;                            
                }
            </script>
        </body>
        </html>

Да, так тоже можно. Но тогда у тебя в клиентском JS где-то должен быть объект articles, чтобы articles[id] сработало. Я не уверен, что ejs его сам разрулит.

Я уж не помню, как это там работает, но скорее всего articles, который у тебя в <% articles %>, это не тот же самый articles, который в скрипте. В скрипте переменная нигде не объявлена, так что при обращении по id она выкинет ошибку.

1 симпатия

Я уже проверил…
Вроде потиху работает…
Недостаток: Поле content будет в HTML коде страницы… А там для каждой статьи может быть многострочный текст. Текст объёмом на одну страницу + 1шт. сроклинг.
Или нормально?

Посмотрите, я правильно объявил переменную?

function setMessage3(articles, id) {
                var msg = document.querySelector('main');
                var thisArticles = articles
                console.log(id);
                console.log(thisArticles[id].content);
                }

Обновление-1

<a style="font-weight: bold;" href="#" onclick="setMessage3('<%=articles[article.id]%>', '<%=article.id%>')"><%=article.title%></a>

<aside class="bg-danger bg-gradient w-100 d-flex justify-content-left align-items-center text-dark fs-5">                                
                                <% if (articles.length > 0) { %>
                                    <ul>
                                        <% articles.forEach((article, index) => { %>
                                        <li>
                                            <input type="hidden" class="articles" name="articles[]" value="<%= article.id %>">     
                                            <span>                                                                                                
                                                <a style="font-weight: bold;" href="#" onclick="setMessage3('<%=articles[article.id]%>', '<%=article.id%>')"><%=article.title%></a>

Ага, смотри что получается:

<a style="font-weight: bold;" href="#" onclick="setMessage3('<%=articles[article.id]%>', '<%=article.id%>')"><%=article.title%></a>

вот здесь ты уже передаешь контент статьи:

setMessage3('<%=articles[article.id]%>'

то есть все эти манипуляции с var thisArticles = articles вообще не нужны.
Можно просто:

function setMessage3(articleContent) {
    var msg = document.querySelector('main');
    console.log(articleContent);
}

Недостаток: Поле content будет в HTML коде страницы…

Точно. Если это прям на прод для живых клиентов то так, конечно, лучше не делать, потому что это лишний трафик для клиентов. Если это тестовое, то, вероятно, сгодится, но я бы перепроверил задание.

1 симпатия