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

“Beiträge”.

Ansicht der Beiträge, jahgangsweise, zugeklappt

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

abc Zur systematischen Darstellung

123 Zur jahrgangsweisen Darstellung

open_in_full Alle Jahrgänge/Kapitel expandieren

close_fullscreen Alle Jahrgänge/Kapitel zuklappen

open_in_new 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 open_in_new 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>&nbsp;Beiträge&nbsp;
                <span id="beitraege_total" data-bs-toggle="title" title="Anzahl der Beiträge/Kapitel" class="beitraege_header"></span>&nbsp;
            </h2>
            <div class="accordion" id="accordionContent">
            </div>
            <div>&nbsp;</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}&nbsp;<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>"
            + "&nbsp;<strong><a href='" + link + "' target='" + NewWin + "'>"
            + "<span class=\"material-icons\">open_in_new</span></a>&nbsp;"
            + 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>&nbsp;"
                    + title
                    + "&nbsp;<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 Fiala

Ehemaliger Präsident Clubcomputer / Herausgeber PCNEWS bei ClubComputer.at
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.

Letzte Artikel von Franz Fiala (Alle anzeigen)

Zur Werkzeugleiste springen