Главное меню в виде дерева

Посоветуйте готовые решения: слева главное меню в виде дерева, справа статья.
Логика:

  • пользователь кликает по меню;
  • справа появляется статья соответствующая узлу меню.
    Желательно без обновления всей страницы, т.е. после клика по меню обновляется только фрагмент страницы, где располагается статья.

Подозреваю, нужно использовать AJAX?
Может есть готовые решения без наворотов?
Чтобы пощёлкать для изучения.

Источник данных БД MySql.

Примечание.
Node.js изучаю.
Имею минимальный опыт C# ASP.NET, MVC.

create_db-2.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);

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>                                            
                                          
                                        </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>

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
        });
    });
};

Слишком много реализаций связки сервер+клиент с разными подходами, но примеры готовых решений с использованием PHP точно быстрее найдешь.

Ссылки из древа меню должны ссылаться на урл с идентификатопром статьи. Например ссылка вида https://domain.com/articles/1. При получении запроса, сервер понимает, что нужна статья с идентификатором 1, после чего твой контроллер должен сделать выборку этой статьи из базы данных и подготовить соответствующие данные, которые будут отданы в ответ на запрос. Если обновление статьи нужно точечно без обновления страницы, смотри в сторону ajax.

Все это очень поверхностное описание, потому что это объемный вопрос, на который не получится дать казуальный ответ.

1 симпатия

Буду переваривать ваше предложение.

Что-нибудь такое прикрутить к своему проекту возможно?

или не мучаться?
Это решение бесплатно?
Для меня это решение (“w3.org”) сложно… пока разбираюсь…

Это не решение. На странице расписана спецификация как организовать правильно ARIA-разметку с аттрибутами элементов для древовидной структуры навигации. А сами спецификации ARIA направлены на создание максимально семантической (смысловой, логической) разметки, которая сделает себя удобоваримой для определенных алгоритмов поисковых систем, специального софта для людей с ограниченными возможностями и т.п. Это же не то, что тебе надо? Я правильно понял? Тебе требуется правильно организовать логику хранения и выборки статей на серверной части и их отображения на клиенте. Это ты просто увидел глазами как это на тех страницах выглядит.

Для того чтобы понять как это все приготовить, возможно, лучше начать с чего-то поменьше, что будет частью общей картины. Тогда, завершив малое, у тебя будет намного меньше вопросов как вписать его в большее. Посмотри как работают стандартные реализации CRUD (Create, Read, Update, Delete) для ноды на готовых примерах. Как правило в них включены примеры с навигацией. Это базовый паттерн, который так или иначе будет находиться в сердце любой бизнес-логики на сервере+клиенте.

Я уже с CRUD более менее разобрался…
Как раз по навигации никогда ничего нормального не попадалось… Хотя может не обращал внимание… Постараюсь вникнуть…

На начальном этапе вопрос: как построить дерево при первой загрузке страницы?
1. Нужна рекурсия?
Где размещать функцию?
в index.ejs?
Типа так?

<% 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>
    </li>
<%})%>

Только как это сделать с рекурсией?

2. Или рекурсию размещать в index.ejs в тегах <script></script>?
И далее через JS?
3. Или рекурсию размещать в home.controller.js?
Там формировать html разметку <ul> и <li> и далее вставлять в index.ejs?
4. Или есть библиотеки, которым можно скормить список из БД, а библиотека далее сама всё сделает?

5. Что из этого брать?

Или это не подходит?
6. Что искать?
Поиск: “js treeview main menu”?

Это зависит от того, какая нужна реализация на клиенте. Нужно ли показывать все ссылки на все статьи сразу? Или нужна подгрузка по типу “Показать еще”? Или нужна постраничная пагинация по ссылкам? Или нужна иерархия?

Почему ты считаешь, что тебе нужна рекурсия?

Не думаю, что такое есть, по крайней точно не в библиотеке. Возможно в каком-то фреймворке. Изучи разницу между фреймворком и библиотекой и поймешь почему.

Бери то что больше подходит под твою задачу. Но это только реализация древовидного меню на клиенте, тебе же нужно это связать все в один организм с сервером. В первом сообщении ты написал, что нужно меню в виде древа, но на эскизе показываешь просто ссылки. Все-таки тебе нужны простые ссылки или иерархия?

1 симпатия

Иерархия…
Меню будет использоваться в CMS. Для эксперимента… Пробы сил…
Вот идеальный аналог по функционалу.

К примеру модель меню может быть что-то типа такого:

const menues = [
  {
    title: "Foobar 1",
    url: "https://foobar.com/articles/1",
    expanded: true,
    items: [
      {
        title: "Foobar 1.1",
        url: "https://foobar.com/articles/1-1",
      },
      {
        title: "Foobar 1.2",
        url: "https://foobar.com/articles/1-2",
        expanded: false,
        items: [
          {
            title: "Foobar 1.2.1",
            url: "https://foobar.com/articles/1-2-1",
          },
          {
            title: "Foobar 1.2.2",
            url: "https://foobar.com/articles/1-2-2",
          },
        ],
      },
    ],
  },
  {
    title: "Foobar 2",
    url: "https://foobar.com/articles/2",
  },
  {
    title: "Foobar 3",
    url: "https://foobar.com/articles/3",
  },
];

Имея такую структуру можно построить древовидное меню.