Beiträge, neu geordnet
Seit 2015 werden auf der Seite von ClubComputer Beiträge aller Art gesammelt. 946 sind es bisher, und durch diese große Zahl wird es schwieriger etwas zu finden. Die WordPress-Suche ist recht gut, doch was man vermisst, ist Überblick.
Einen interaktiven Überblick über alle Artikel gibt die Seite
Man kann die Beiträge chronologisch oder systematisch ordnen. Ein Accordion ermöglicht es, die Anzeige auf einen bestimmten Jahrgang oder auf ein bestimmtes Thema zu konzentrieren.
Die weißen Streifen zwischen den Kapiteln sind die Folge der Wechselwirkung zwischen den beteiligten Stylesheets. Wenn man die Seite in einem eigenen Fenster öffnet, verschwinden diese Streifen.
Bedienungselemente
Zur systematischen Darstellung
Zur jahrgangsweisen Darstellung
Alle Jahrgänge/Kapitel expandieren
Alle Jahrgänge/Kapitel zuklappen
In eigenem Fenster öffnen
Programmierung
Ein serverseitiges C#-Programm sendet die Daten der Artikel in Form JSON-Arrays zum Client. Die Darstellung auf der Webseite übernimmt ein JavaScript-Programm. Zu Vereinfachung der Programmierung werden folgende Bibliotheken verwendet:
- jQuery vereinfacht die Ansprache der Ansprache der HTML-Objekte der Seite
- Google-Fonts ermöglicht die einfache Verwendung von Symbolen auf Webseiten
- Bootstrap stellt CSS-Klassen zur bildschirmunabhängigen Darstellung zur Verfügung, Die Komponente „Accordion stammt ebenfalls aus dieser Bibliothek
jQuery https://jquery.com/
Ansprache eines HTML-Objekts object
mit ursprünglichen JavaScript
document.getElementById("object").innerHTML = "neuer Text"
Ansprache eines HTML-Objekts object
mit jQuery-Unterstützung
$("#object").html("neuer Text")
Google Fonts https://fonts.google.com/icons
Im Header einer Seite wird eingefügt:
<link href="https://fonts.googleapis.com/css2?family=Material+Icons" rel="stylesheet">
Um das Symbol
einzufügen, schreibt man: in den HTML-Text:<span class="material-icons title-symbol">open_in_new</span>
Bootstrap https://getbootstrap.com/
Bootstrap besteht aus einem Javascript- und einer Styleshett-Bibliothek und wird im Header einer Seite eingebunden:
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
Um eine Seite „responsiv“ zu gestalten, verpackt man den Code in folgendes Konstrukt:
<div class="container-sm">
<div class="row">
<div class="col header">
hier ist der eigene Code
</div>
</div>
</div>
Damit passt sich der Inhalt perfekt jedem Endgerät an.
HTML-Teil
Der HTML-Teil des Projekts „Beiträge“ besteht aus den oben beschriebenen Kopfzeilen zur Einbindung der Bibliotheken, einigen Style-Sheets und folgendem Code:
<div class="container-sm">
<div class="row">
<div class="col header">
<h2 style="color:white">
<span id="but_darstellung" title="chronologisch/systematisch" data-bs-toggle="title" class="material-icons title-symbol">abc</span>
<span id="but_OpenClose" title="Alles öffnen/schließen" data-bs-toggle="title" class="material-icons title-symbol">open_in_full</span>
<a id="newwinLink"
href="https://buero.clubcomputer.at/beitraege-acc.htm"
rel="noopener" data-bs-toggle="title" title="In eigenem Fenster öffnen" target="_blank">
<span class="material-icons title-symbol">open_in_new</span></a> Beiträge
<span id="beitraege_total" data-bs-toggle="title" title="Anzahl der Beiträge/Kapitel" class="beitraege_header"></span>
</h2>
<div class="accordion" id="accordionContent">
</div>
</div>
</div>
</div>
Es beginnt mit einer Überschrift h2, die mit drei Symbolen beginnt und mit dem Text „Beiträge“ endet. Der eigentliche Inhalt wird durch das JavaScript-Programm in den Abschnitt div class="accordion"
eingefügt.
Daten
Die Daten werden am Server durch das Programm JSON-db.aspx.cs
als eine Folge von JSON-Arrays erzeugt.
using MySql.Data.MySqlClient;
using System;
using System.Collections.Generic;
using System.Data;
using System.Text;
using buero.App_Code;
namespace buero
{
public partial class JSON_db : System.Web.UI.Page
{
MySql.Data.MySqlClient.MySqlConnection conn = null;
Encoding utf8 = Encoding.UTF8;
Encoding unicode = Encoding.Unicode;
string myConnectionString;
public class Category
{
public int id;
public string name;
public string slug;
public int parent;
public int count;
public string description;
public int level;
public int taxomony_id;
public Category
(
int id,
string name,
string slug,
int parent,
int count,
string description,
int level,
int taxomony_id
)
{
this.id = id;
this.name = name;
this.slug = slug;
this.parent = parent;
this.count = count;
this.description = description;
this.level = level;
this.taxomony_id = taxomony_id;
}
}
List<category> list = new List<category>();
protected void Page_Load()
{
myConnectionString = System.Configuration.ConfigurationManager.ConnectionStrings["ConnectionWordPressClubComputer"].ConnectionString;
try
{
conn = new MySql.Data.MySqlClient.MySqlConnection(myConnectionString);
conn.Open();
}
catch (MySql.Data.MySqlClient.MySqlException ex)
{
switch (ex.Number)
{
default:
case 0:
Response.Write("Cannot connect to server. Contact administrator. " + ex.Message);
return;
case 1045:
Response.Write("Invalid username/password, please try again. " + ex.Message);
return;
}
}
string s = "";
s += Get_Systematisch() + "\n";
s += Get_Chronologisch() + "\n";
Response.Clear();
Response.AddHeader("Content-Type", "text/js");
Response.Write(s);
}
protected string Get_Chronologisch()
{
string sql = "SELECT"
+ " post_date AS date, post_title AS title, ID AS id"
+ " FROM wp_6_posts"
+ " WHERE "
+ " post_type='post' "
+ " AND post_title<>'Automatisch gespeicherter Entwurf'"
+ " AND post_status<>'draft'"
+ " ORDER BY post_date DESC";
MySqlDataAdapter da = new MySqlDataAdapter(sql, conn);
DataTable dt = new DataTable();
da.Fill(dt);
string s = "var beitraege_chr = " + json.GetJSON(dt);
return s;
}
protected DataTable GetLinks(int id, int level)
{
string sql = "SELECT"
+ " wp_6_term_relationships.object_id AS id, " + level + " AS level, wp_6_posts.post_title AS title, wp_6_posts.post_date AS date"
+ " FROM wp_6_term_relationships INNER JOIN wp_6_posts ON wp_6_term_relationships.object_id = wp_6_posts.id"
+ " WHERE wp_6_term_relationships.term_taxonomy_id=" + id;
MySqlDataAdapter da = new MySqlDataAdapter(sql, conn);
DataTable dt = new DataTable();
da.Fill(dt);
return dt;
}
protected void GetChildOfParent(int ParentId, int Level)
{
string sql = "SELECT"
+ " wp_6_terms.term_id, wp_6_terms.name, wp_6_terms.slug, "
+ " wp_6_term_taxonomy.parent, wp_6_term_taxonomy.count, wp_6_term_taxonomy.description, wp_6_term_taxonomy.term_taxonomy_id"
+ " FROM wp_6_terms INNER JOIN wp_6_term_taxonomy ON wp_6_terms.term_id = wp_6_term_taxonomy.term_id"
+ " WHERE wp_6_term_taxonomy.taxonomy='category' AND wp_6_terms.term_group=0 AND wp_6_term_taxonomy.parent=" + ParentId
+ " ORDER BY parent, name";
MySqlDataAdapter da = new MySqlDataAdapter(sql, conn);
DataTable dt = new DataTable();
da.Fill(dt);
int Count = dt.Rows.Count;
if (Count == 0) return;
for (int i = 0; i < Count; i++)
{
DataRow dr = dt.Rows[i];
int term_id = int.Parse(dr["term_id"].ToString());
string name = dr["name"].ToString();
string slug = dr["slug"].ToString();
int parent = int.Parse(dr["parent"].ToString());
int count = int.Parse(dr["count"].ToString());
string description = dr["description"].ToString();
int term_taxonomy_id = int.Parse(dr["term_taxonomy_id"].ToString());
list.Add(new Category(term_id, name, slug, parent, count, description, Level, term_taxonomy_id));
GetChildOfParent(term_id, Level + 1);
}
}
protected string Get_Systematisch()
{
string s = "";
GetChildOfParent(0, 0);
DataTable dt = new DataTable();
DataColumn dc = new DataColumn("title", Type.GetType("System.String"));
dt.Columns.Add(dc);
dc = new DataColumn("slug", Type.GetType("System.String"));
dt.Columns.Add(dc);
dc = new DataColumn("level", Type.GetType("System.Int32"));
dt.Columns.Add(dc);
dc = new DataColumn("id", Type.GetType("System.Int32"));
dt.Columns.Add(dc);
foreach (Category cat in list)
{
DataRow dr = dt.NewRow();
dr["title"] = cat.name;
dr["slug"] = cat.slug;
dr["level"] = cat.level;
dr["id"] = cat.id;
dt.Rows.Add(dr);
DataTable chap = GetLinks(cat.taxomony_id, cat.level);
s += "var beitraege_chap_"+cat.taxomony_id+" = " + json.GetJSON(chap) + "\n";
}
s += "var beitraege_chap = " + json.GetJSON(dt);
return s;
}
}
}
Die Daten werden mit dem folgenden Konstrukt in die HTML-Datei eingefügt:
<script src="https://buero.clubcomputer.at/json-db.aspx" type="text/javascript"></script>
Chronologisches Array
Die Datenbank der Beiträge beginnt mit einer chronologischen Liste aller Beiträge, wobei der jüngste Beitrag zuerst kommt. Diese Liste ist ein einziges Array beitraege_chr
.
var beitraege_chr = [{"date":"3/10/2023 10:36:01 AM", "title":"Mobilfunk Orientierung", "id":"152287"},
{"date":"3/8/2023 12:09:52 AM", "title":"Computerwoche hat gute Browser-Tipps auf Lager", "id":"152209"},
{"date":"3/4/2023 10:01:11 AM", "title":"PCNEWS-176 'Netzwerke'", "id":"152026"},
{"date":"3/4/2023 12:18:28 AM", "title":"How to E-Mail Today", "id":"151295"},
{"date":"2/26/2023 10:45:39 AM", "title":"Buchhaltung 'Freefinance'", "id":"151778"},
...
Systematisches Array
Die systematische Liste benutzt die Zuordnung eines Beitrags zu einem Kapitel und beginnt so:
var beitraege_chap = [{"title":"Allgemein", "slug":"allgemein", "level":"0", "id":"1"},
{"title":"Barrierefrei", "slug":"barrierefrei", "level":"1", "id":"1327"},
{"title":"Bezahlsysteme", "slug":"bezahlsysteme", "level":"1", "id":"1119"},
{"title":"Buchbesprechung", "slug":"buchbesprechung", "level":"1", "id":"1363"},
{"title":"Corona", "slug":"corona", "level":"1", "id":"1316"},
{"title":"Energieversorgung", "slug":"energieversorgung", "level":"1", "id":"1448"},
...
Jedes Unterkapitel enthält seinerseits wieder eine Variable mit allen Beiträgen dieses Unterkapitels. Das erste Kapitel "Allgemein"
hat mehrere Unterkapitel, das erste ist "Barrierefrei"
mit der id 1327
, und für jede dieser Ids gibt es ein weiteres Array mit allen Beiträgen dieses Kapitels, für "Barrierefreiheit"
ist es daher beitraege_chap_1327
.
var beitraege_chap_1327 = [{"id":"70", "level":"1", "title":"Barrierefreiheit, Accessibility", "date":"10/20/2015 3:08:07 PM"},
{"id":"90837", "level":"1", "title":"Mein Weg zum Hörbuch", "date":"6/24/2018 1:24:59 PM"},
{"id":"131962", "level":"1", "title":"Jünger werd' ma Nimmer", "date":"6/15/2020 6:46:31 PM"},
{"id":"132007", "level":"1", "title":"Digitaler 'Kassetten'-recorder", "date":"6/16/2020 10:05:31 AM"},
{"id":"132025", "level":"1", "title":"Edge spricht", "date":"6/17/2020 4:36:37 PM"},
{"id":"132163", "level":"1", "title":"Autofahrt nutzen", "date":"7/10/2020 11:33:36 AM"},
...
Anzeige der Daten
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1.0, maximum-scale=2.0, user-scalable=yes">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Material+Icons" rel="stylesheet">
<style>
.accordion-button {
font-size: 20pt !important;
font-weight: bold;
color: white !important;
background-color: green !important;
}
.accordion-month {
background-color: green;
color: white;
}
.beitraege_header {
font-size: 15pt;
font-weight: normal;
font-style: italic;
color: white;
}
.title-symbol {
color: white;
font-size: 30pt;
cursor: pointer;
}
.date {
font-style: italic;
font-size: smaller;
color: rgb(64,64,63);
}
.header {
background-color: rgb(0,64,0);
color: white;
}
</style>
<div class="container-sm">
<div class="row">
<div class="col header">
<h2 style="color:white">
<span id="but_darstellung" title="chronologisch/systematisch" data-bs-toggle="title" class="material-icons title-symbol">abc</span>
<span id="but_OpenClose" title="Alles öffnen/schließen" data-bs-toggle="title" class="material-icons title-symbol">open_in_full</span>
<a id="newwinLink"
href="https://buero.clubcomputer.at/beitraege-acc.htm"
rel="noopener" data-bs-toggle="title" title="In eigenem Fenster öffnen" target="_blank">
<span class="material-icons title-symbol">open_in_new</span></a> Beiträge
<span id="beitraege_total" data-bs-toggle="title" title="Anzahl der Beiträge/Kapitel" class="beitraege_header"></span>
</h2>
<div class="accordion" id="accordionContent">
</div>
<div> </div>
</div>
</div>
</div>
<script src="https://buero.clubcomputer.at/json-db.aspx" type="text/javascript"></script>
<script>
const template_accItem =
" <div class=\"accordion-item\">"
+ "<h2 class=\"accordion-header\" id=\"heading{acc_id}\">"
+ "<button class=\"accordion-button\" type=\"button\" data-bs-toggle=\"collapse\" "
+ "data-bs-target=\"#collapse{acc_id}\" aria-expanded=\"false\" aria-controls=\"collapse{acc_id}\">"
+ "{acc_title} <span class='beitraege_header'>{beitraege}</span>"
+ "</button></h2>"
+ "<div id=\"collapse{acc_id}\" "
+ "class=\"accordion-collapse collapse \" "
+ "aria-labelledby=\"heading{acc_id}\" data-bs-parent=\"#accordionContent\">"
+ "<div class=\"accordion-body\">{acc_content}</div>"
+ "</div></div>"
const MonthString = ["Jänner", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"]
const NavigateBase = "https://clubcomputer.at?p="
const NavigateBaseCat = "https://clubcomputer.at/category/"
const NewWin = "Neubeitrag"
function GetBeitrag(date, link, title) {
var s =
"<span class=\"date\">" + date + "</span>"
+ " <strong><a href='" + link + "' target='" + NewWin + "'>"
+ "<span class=\"material-icons\">open_in_new</span></a> "
+ title + "</strong><br>"
return s
}
function GetDate(date) {
var year = date.getFullYear()
var month = date.getMonth()
var day = date.getDate()
return year + "-" + String(month + 1).padStart(2, "0") + "-" + String(day).padStart(2, "0")
}
function ShowChron() {
$("#accordionContent").html("")
var year_old = ""
var month_old = ""
var ArticleCount = 0
var ArticleCountYear = 0
var beitraege = ""
for (var i = 0; i < beitraege_chr.length; i++) {
ArticleCount++
ArticleCountYear++
var title = beitraege_chr[i]["title"]
var link = NavigateBase + beitraege_chr[i]["id"]
var date = new Date(beitraege_chr[i]["date"])
var year = date.getFullYear(); if (i == 0) year_old = year
var month = date.getMonth()
date = GetDate(date)
if (year == year_old) {
if (month != month_old) {
month_old = month
beitraege += "<h5 class='accordion-month'>" + MonthString[month] + "</h5>"
}
beitraege += GetBeitrag(date, link, title)
if (i < beitraege_chr.length - 1)
continue
else
year--
}
$("#yc" + (year + 1)).html(--ArticleCountYear)
beitraege = "<pre>" + beitraege + "</pre>"
var temp_accItem = template_accItem
temp_accItem = temp_accItem.replace(/{acc_id}/g, (year + 1))
temp_accItem = temp_accItem.replace(/{acc_title}/g, (year + 1))
temp_accItem = temp_accItem.replace(/{acc_content}/g, beitraege)
temp_accItem = temp_accItem.replace(/{beitraege}/g, ArticleCountYear)
$('#accordionContent').append(temp_accItem)
beitraege = ""
ArticleCountYear = 0
year_old = year
}
$("#beitraege_total").html(ArticleCount)
}
function GetBeitraege(beitraege) {
var s = ""
var level = beitraege.level
for (var b = 0; b < beitraege.length; b++) {
var record_beitrag = beitraege[b]
var date = GetDate(new Date(record_beitrag.date))
s +=
"".padStart(level * 3, " ")
+ GetBeitrag(date, NavigateBase + record_beitrag.id, record_beitrag.title)
}
return s
}
function ShowSyst() {
$("#accordionContent").html("")
var ChapterCountTotal = 0
var ArticleCountTotal = 0
var ArticleCount = 0
var ChapterCount = 0
var UnterKapitel = ""
var Title1Old = null
for (var i = 0; i < beitraege_chap.length; i++) {
var record = beitraege_chap[i]
var level = record.level
var title = record.title
var slug = record.slug
var id = record.id
ChapterCountTotal++
if (i == 0) {
Title1Old = record
continue
}
if (level != 0) {
ChapterCount++
UnterKapitel += "<h5 class='accordion-month'>"
+ "".padStart((level - 1) * 3, " ")
+ "<a style='color:white' href='" + NavigateBaseCat + slug + "' target='" + NewWin + "'>"
+ "<span class=\"material-icons\">open_in_new</span></a> "
+ title
+ " <span class='beitraege_header'>" + eval("beitraege_chap_" + id).length + "</span>"
+ "</h5>"
var s = ""
var beitraege = eval("beitraege_chap_" + id)
UnterKapitel += GetBeitraege(beitraege)
ArticleCountTotal += beitraege.length
ArticleCount += beitraege.length
}
if ((level == 0) || (i == beitraege_chap.length - 1)) {
var Beitraege_Wurzel = ""
if (i < beitraege_chap.length - 1) {
var beitraege = eval("beitraege_chap_" + id)
Beitraege_Wurzel = GetBeitraege(beitraege)
ArticleCountTotal += beitraege.length
// ArticleCount += beitraege.length
}
var temp_accItem = template_accItem
temp_accItem = temp_accItem.replace(/{acc_id}/g, Title1Old.id)
temp_accItem = temp_accItem.replace(/{acc_title}/g, Title1Old.title)
temp_accItem = temp_accItem.replace(/{acc_content}/g, "<pre>" + Beitraege_Wurzel + UnterKapitel + "</pre>")
temp_accItem = temp_accItem.replace(/{beitraege}/g, ArticleCount + " (" + ChapterCount + ")")
$('#accordionContent').append(temp_accItem)
UnterKapitel = ""
ChapterCount = 0
ArticleCount = 0
Title1Old = record
}
}
$("#beitraege_total").html(ArticleCountTotal + " (" + ChapterCountTotal + ")")
}
if ($("#but_darstellung").html() == "abc") ShowChron(); else ShowSyst()
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="title"]')
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
$("#but_OpenClose").click(function () {
if ($("#but_OpenClose").html() == "open_in_full") {
$("#but_OpenClose").html("close_fullscreen")
$(".accordion-collapse").show()
} else {
$("#but_OpenClose").html("open_in_full")
$(".accordion-collapse").hide()
}
return false
})
$("#but_darstellung").click(function () {
if ($("#but_darstellung").html() == "123") {
$("#but_darstellung").html("abc")
ShowChron()
} else {
$("#but_darstellung").html("123")
ShowSyst()
}
})
</script>
Dieser Beitrag ist gleichzeitig eine Dokumentation der Seite „Beiträge“ für den Fall einer notwendigen Fehlersuche.
Franz war pensionierter HTL Lehrer (TGM), Präsident von ClubComputer, Herausgeber der Clubzeitung PCNEWS und betreute unser Clubtelefon und Internet Support. Er war leidenschaftlicher Rapid Wien Fan. Er ist leider Anfang Jänner 2024 nach langer schwerer Krankheit verstorben.
Neueste Kommentare