Tree-Based Navigation (CMS)

Tree-Based Navigation (CMS)

Wenn deine Rails-Anwendung viel Inhalt enthält, erwäge, sie in einer Baumstruktur für das Menü zu organisieren. Hier zeige ich, wie man oberste Registerkarten, verschachtelte Links in einer Seitenleiste und Breadcrumbs zum Zurücknavigieren in der Hierarchie hinzufügen kann.
Wenn wir mit einer Anwendung arbeiten, die viel Inhalt hat, möchten wir möglicherweise ein verschachteltes Menüsystem erstellen, bei dem mehrere Hauptmenüpunkte oben angezeigt werden und jeder Hauptmenüpunkt eine Seitenleiste mit verschachtelten Elementen anzeigt. Wir könnten sogar einen Brotkrumenpfad hinzufügen, damit der Benutzer sehen kann, an welcher Stelle der Navigation er sich gerade befindet, und leicht zu den Hauptelementen zurückkehren kann.

neue Rails-Anwendung

In dieser Episode werden wir dieses Projekt von Grund auf erstellen, und wir beginnen damit, eine neue Rails-Anwendung zu erstellen, die wir "cms" nennen werden.
$ rails new cms
$ cd cms
Also, wie sollten wir beginnen, unsere Anwendung zu erstellen? Wir werden die Benutzeroberfläche verwenden, um das Design voranzutreiben, und dazu werden wir nach Hinweisen auf Ressourcen darin suchen. Ein gutes Indiz ist eine Liste, besonders wenn es etwas ist, dessen Inhalte in der Datenbank gespeichert werden sollten. Die Liste der Seiten in der linken Seitenleiste ist so etwas; wir möchten sie durch eine administrative Oberfläche verwalten können, und deshalb werden wir sie zu einer Ressource machen.

Die nächste Frage lautet: "Welche Attribute sollen für jedes der Elemente vorhanden sein?" Jede Seite sollte einen Namen haben und in diesem Fall auch etwas Inhalt, obwohl wir stattdessen ein URL-Attribut verwenden könnten, wenn wir möchten, dass der Link zu einer dynamischeren Seite führt, falls wir keine App vom Typ CMS haben. Die Liste wird in einer Baumstruktur angezeigt, sodass wir den Platz jedes Elements verfolgen müssen. Wir könnten diese Baumstruktur selbst anzeigen, aber stattdessen werden wir eine Gem nutzen. Es gibt viele verschiedene Gems zur Auswahl, die alle eine ähnliche Schnittstelle bieten, aber in ihrer Implementierung und Leistung sehr unterschiedlich sind. Wir werden ein Gem namens Ancestry verwenden. Wenn wir uns das README für dieses Gem ansehen, können wir die Liste der bereitgestellten Methoden sehen, von denen einige sehr hilfreich beim Aufbau unserer baumbasierten Navigation sein werden, wie z.B. root, um den Wurzelknoten zu erhalten, ancestors, um die Vorfahren eines Knotens zu erhalten, und descendants, um die Kinder eines Knotens zu erhalten. Wir werden das Gem auf die übliche Weise installieren, indem wir es zur Gemfile hinzufügen und dann bundle ausführen, um es zu installieren.
// Gemfile

gem 'ancestry'
gem 'jquery-rails'
gem 'jquery-ui-rails'
gem 'sassc-rails'

$ bundle
$ ./bin/importmap pin jquery
$ ./bin/importmap pin jquery-ui
Beides noch schnell importieren in unsers application.js.
// app/javascript/application.js

import "jquery";
import "jquery-ui";

Anwendung zu erstellen

Jetzt können wir damit beginnen, unsere Anwendung zu erstellen. Wir werden ein Scaffold für ein Page-Modell generieren, um schnell eine Benutzeroberfläche einzurichten, und ihm die Attribute name, content und ancestry geben.
$ rails g scaffold page name content:text ancestry:string:index
$ rails db:migrate
Wir haben jetzt ein Scaffold eingerichtet, mit dem wir neue Seiten erstellen können. Das Formular für eine neue Seite enthält ein Ancestry-Feld, das ein Textfeld ist. Wir werden dies durch eine Dropdown-Liste aller vorhandenen Seiten ersetzen, damit wir ein übergeordnetes Element für die neue Seite auswählen können. Wir werden die generierte Vorlage ändern, um das Textfeld durch eine collection_select zu ersetzen.
// app/views/pages/_form.html.erb
<div>
  <%= form.label :parent_id, style: "display: block" %>
  <%= form.collection_select :parent_id, Page.order(:name), :id, :name, include_blank: true %>
</div>
Beachten Sie, dass wir den Namen in parent_id geändert haben. Als nächstes werden wir das Page-Modell ändern, damit es das neue parent_id-Feld für Massenzuweisung zulässt, und einen Aufruf von has_ancestry hinzufügen, um das gesamte baumbasierte Verhalten einzufügen, einschließlich der Getter- und Setter-Methoden für parent_id.
// app/models/page.rb

class Page < ApplicationRecord
  has_ancestry
end

// app/controllers/pages_controller.rb

class PagesController < ApplicationController

...

    # Only allow a list of trusted parameters through.
    def page_params
      params.require(:page).permit(:name, :content, :parent_id)
    end
end
Jetzt haben wir ein Dropdown-Menü, wenn wir eine Seite erstellen, obwohl es derzeit noch nichts enthält. Wenn wir eine Seite erstellen und dann zurückgehen, um eine weitere zu erstellen, können wir die erste Seite, die wir erstellt haben, als übergeordnete Seite auswählen.


1.png 635 KB



2.png 747 KB

Aufbau der Navigation

Jetzt haben wir mehrere Seiten, von denen einige unter anderen verschachtelt sind, und wir haben genügend Daten, um mit dem Aufbau unserer Navigation zu beginnen. Wenn wir eine Seite besuchen, möchten wir einige Links oben, die uns zu den verschiedenen Hauptseiten führen. Diese fügen wir oben in die Vorlage ein, die eine einzelne Seite anzeigt.
// app/views/pages/show.html.erb

<ul id="menu">
  <% Page.roots.each do |page| %>
  <li><%= link_to page.name, page %></li>
  <% end %>
</ul>
Wir erhalten die Hauptseiten, indem wir Page.roots verwenden, was Ancestry bereitstellt. Für jede Hauptseite zeigen wir ein Listenelement mit einem Link zur Seite an. Wir haben bereits einige CSS hinzugefügt, sodass beim Neuladen der Seite jetzt die Navigation angezeigt wird und gut aussieht.

3.png 574 KB


Es wäre besser, wenn der Tab für die aktuelle Seite als aktiv erscheinen würde. Wir werden die Vorlage ändern, um der Registerkarte, deren Seite die aktuelle ist, eine aktive CSS-Klasse hinzuzufügen.
// app/views/pages/show.html.erb

<ul id="menu">
  <% Page.roots.each do |page| %>
  <li><%= link_to page.name, page, class: ("active" if @page.root == page) %></li>
  <% end %>
</ul>
Jetzt wird die Registerkarte als aktiv angezeigt, wenn die aktuelle Seite oder eines ihrer untergeordneten Elemente angezeigt wird.

Erstellen der Seitenleiste

Als Nächstes werden wir an der Seitenleiste mit verschachtelten Menüpunkten arbeiten. Dies wird wie das Hauptmenü funktionieren, jedoch anstelle der Anzeige der Hauptseiten werden wir durch die Kinder der aktuellen Seite iterieren. Dies können wir durch Verwendung der children-Methode von Ancestry erreichen. Der knifflige Teil hier ist, dass wir möchten, dass dieses Menü verschachtelt ist, sodass es die children der children in einer anderen Liste enthält. Wir können dies erreichen, indem wir den Code, der die children der aktuellen Seite rendert, in ein Partial verschieben und dieses Partial dann von der Show-Seite aufrufen. Wir werden dieses Partial submenu_pages nennen und damit beginnen, die children der Hauptseite zu rendern, sodass das Menü unabhängig von der Seite, auf der wir uns befinden, konsistent ist.
// app/views/pages/show.html.erb

<div id="submenu">
  <%= render 'pages/submenu_pages', pages: @page.root.children %>
</div>
Das durchläuft die übergebenen Seiten und rendert sie aus. Für Seiten mit children wird das Partial erneut aufgerufen und auch ihre children werden gerendert.
// app/views/pages/_submenu_pages.html.erb

<ul>
  <% pages.each do |page| %>
  <li>
    <%= link_to_unless_current page.name, page %>
    <%= render 'pages/submenu_pages', pages: page.children if page.children.present? %>
  </li>
  <% end %>
</ul>
Wenn wir die Seite jetzt neu laden, wird das Untermenü auf der linken Seite der Seite angezeigt, und wenn wir eine Seite besuchen, wird ihr Link entfernt.
4.png 651 KB


Jedes Mal, wenn diese Seitenleiste gerendert wird, überprüft sie die children der Seiten. Es wäre gut, wenn wir die zweite Überprüfung für jedes Element vermeiden könnten, da dies die Leistung beeinträchtigen könnte, wenn es eine große Anzahl von Elementen gibt. Ancestry bietet eine Methode namens arrange, die dabei helfen kann. Diese Methode nimmt ein Array von Knoten entgegen und generiert eine Gruppe von verschachtelten Hashes für den Elternknoten und seine children. Um dies zum Laufen zu bringen, müssen wir den Code ändern, der das Partial submenu_pages rendert. Anstatt die children der Hauptseite in dieses Partial zu übergeben, werden wir stattdessen ihre Nachkommen übergeben.
// app/views/pages/show.html.erb

<div id="submenu">
  <%= render 'pages/submenu_pages', pages: @page.root.descendants.arrange %>
</div>
Anstatt nur die direkten children abzurufen, ruft descendants alle sub-children in einem riesigen Array ab. Das Aufrufen von arrange auf diesem Array arrangiert es in eine Gruppe von verschachtelten Hashes, die wir verwenden können, um alle Daten für das linke Menü zu erhalten, ohne mehrere Abfragen machen zu müssen. Wir müssen unser Partial aktualisieren, da es jetzt die children übergeben bekommt.
// app/views/pages/_submenu_pages.html.erb

<ul>
  <% pages.each do |page, children| %>
  <li>
    <%= link_to_unless_current page.name, page %>
    <%= render 'pages/submenu_pages', pages: children if children.present? %>
  </li>
  <% end %>
</ul>
Wenn wir die Seite jetzt neu laden, sieht sie genauso aus wie zuvor, jedoch führt sie besser aus, da sie weniger Datenbankabfragen macht.

Das Hinzufügen einer Breadcrumb-Navigation

Das letzte Element der Navigation, das wir zur Seite hinzufügen werden, ist der Breadcrumb-Trail. Um dies zu rendern, möchten wir durch die Vorfahren der aktuellen Seite iterieren, und Ancestry stellt eine Methode ancestors bereit, die genau das tut.
// app/views/pages/show.html.erb

<div id="breadcrumbs">
  <% @page.ancestors.each do |page| %>
    <%= link_to page.name, page %> &gt;
  <% end %>
</div>
Wenn wir eine Unterseite besuchen, sehen wir den Breadcrumb-Trail oben.
5.png 667 KB


Wir sind mit dem Navigationsbereich unserer Anwendung so gut wie fertig, aber um abzuschließen, werden wir die Seite ein wenig aufräumen, damit sie fertiger aussieht. Wir werden den Namen der Seite in ein h1-Element setzen, damit er besser sichtbar ist, und simple_format für den Inhalt verwenden, damit wir Absätze und so weiter hinzufügen können. Wir werden die Ahneninformationen entfernen, da diese nicht wirklich relevant sind, und schließlich werden wir den Namen der Seite im Titel platzieren.
// app/views/pages/show.html.erb

<% content_for :title, @page.name %>

// app/views/pages/_page.htm.erb

<div id="<%= dom_id page %>">
  <h1><%= page.name %></h1>
  <%= page.content %>
</div>

Änderung der Layout-Datei

Um den Titel anzuzeigen, müssen wir eine Änderung an der Layout-Datei vornehmen.
// app/views/layouts/application.html.erb

<!DOCTYPE html>
<html>

  <head>
    <title><%= content_for?(:title) ? yield(:title) : "CMS" %></title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
    <%= javascript_importmap_tags %>
  </head>

  <body>
    <main id="container">
      <%= yield %>
    </main>
  </body>

</html>
Wenn wir die Seite jetzt neu laden, sieht sie viel besser aus.

6.png 668 KB


Das ist alles für diese Episode. Die hier gezeigten Techniken können in jeder Anwendung verwendet werden, auch wenn es keine CMS-ähnliche Anwendung ist. Wir könnten das Page-Modell in MenuItem umbenennen und ihm ein url-Attribut geben, um anstelle zum Controller der Seite zu gehen. Auf diese Weise können die Menüpunkte weiterhin dynamisch aus der Datenbank generiert werden. Wenn wir dies tun, sollten wir überlegen, die Daten des Menüs zu zwischenspeichern, da sie sich nicht sehr oft ändern werden.
Meld dich an und schreibe ein Kommentar