JavaScript in Stud.IP

< Asset-Bundling (≥4.2) | Entwicklungs-HOWTO | Sicherheitsaspekte >

In Stud.IP wird mit der Zeit mehr und mehr Javascript für erweiterte und vereinfachte Bedienung oder auch einfach für besonders schöne Effekte benutzt.

1.  Code-Konventionen für JavaScript

Als Programmierer hat man sich im Grunde nur daran zu richten, dass der globale Namespace einigermaßen sauber gehalten wird (also globale Variablen müssen vermieden werden) und dass die Code-Konventionen eingehalten werden, die im Lifters005 zusammen gefasst sind. Die Code-Konventionen sind von Douglas Crockfords "Code Conventions for the JavaScript Programming Language" komplett übernommen.

Was den Namespace angeht, so sollen alle speziellen Stud.IP Funktionen unterhalb des STUDIP-Objektes angehängt werden. Programmiere ich also einige Funktionen für die News, fange ich an mit:

  1. STUDIP.News = {
  2.     openclose: function (id) {},
  3.     open: function (id) {},
  4.     close: function (id) {}
  5. }

und fülle diese Methoden dann mit Leben. An diese Konvention sollten sich auch Plugin-Programmierer halten, wobei die noch speziell darauf zu achten haben, dass ihre Methodennamen auch tatsächlich eindeutig sind. Implementieren zwei unterschiedliche Plugins eine Methode STUDIP.go , so wird mindestens eines der beiden Plugins weinen. Sinnvoll ist es da, den eindeutigen Klassennamen des Plugins zwischen zu schieben, entweder über STUDIP.pluginclassname.go oder eventuell auch einfach nur pluginclassname.go.

2.  Eigene Stud.IP Bibliothek

Für alle Javascript-Programmierer wird von Interesse sein, dass einige Funktionen schon in Stud.IP implementiert sind, die auch an anderer Stelle nützlich sein könnten. Der Vorteil liegt auf der Hand: der Code bleibt klein und kann später besser um Funktionalitäten erweitert werden.

2.1  Vorhandene Methoden

MethodeAnwendbar aufVerhaltenZu beachten
$.showAjaxNotification(position=left);Alle ElementeDem Element wird ein AJAX-Indikator vorangestellt.Der Indikator kann mittels des Parameters position="right" auch hinter dem Element positioniert werden. Der Indikator wird absolut positioniert, was zu Problemen bei Veränderungen von Elementen in der Umgebung des eigentlichen Elements führen kann.
$.hideAjaxNotification();Alle ElementeDer dem Element zugehörende AJAX-Indikator wird entfernt, sofern vorhanden.-

2.2  Vorhandene Verhaltsmuster über CSS-Klassen

KlasseAnwendbar aufVerhaltenZu beachten
.add_toolbar<textarea />Dem Element wird eine Menüleiste mit vereinfachten Formatiermöglichkeiten vorangestellt.Auf diese Art und Weise kann nur das Standard-Buttonset von Stud.IP verwendet werden.
.load_via_ajax<a />Eine angegebene URL (entweder metadata.url oder die URL des gegebenen Links) wird via AJAX in ein Element (entweder metadata.target oder das auf das gegebene Elemente folgende Element) geladen.Über metadata.indicator kann via CSS-Regel das Element angegeben werden, welches den AJAX-Indikator erhält. Über metadata können weitere Parameter für den Aufruf der URL angegeben werden.
.load_via_ajax.internal_message<a />Spezialfall für interne Nachrichten.Die Parameter werden an die Gegebenheiten in lib/sms_func.inc.php angepasst.
.resizable<textarea />Das Element kann durch einen Schieber am unteren Rand in der Höhe verändert werden.-

2.3  AJAX-Anfragen

AJAX-Anfragen sollten über jQuery mit den Methoden .load .get oder .ajax durchgeführt werden. Die meisten AJAX-Aufrufe haben einen AJAX-Indicator, der dem Nutzer mitteilt, dass gerade etwas geladen wird. Falls dieser Indicator ungebeten sein sollte, kann man die AJAX-Methode einbetten wie folgt:

  1. STUDIP.ajax_indicator = false;
  2. $('#dynamischer_bereich').load(url);
  3. STUDIP.ajax_indicator = true;

2.4  URLHelper in Javascript

Das Objekt STUDIP.URLHelper bietet für Javascript ähnliche Funktionalität wie der URLHelper in PHP. Dennoch darf man nicht vergessen, dass beide URLHelper gänzlich unabhängig voneinander sind und nicht miteinander kommunizieren können. Wozu braucht man dann einen URLHelper in Javascript? Zum Beispiel, um:

  • Einen Link zu generieren, wo eine Javascript-Datei sonst nicht wüsste, wie die Adresse des Servers ist. Schreiben Sie also STUDIP.URLHelper.getURL("about.php"), um einen URI-kodierten Pfad zu http://www.studip......de/about.php zu bekommen, oder STUDIP.URLHelper.getURL("about.php"), um das selbe als nicht URI-kodiert zu erhalten.
  • Variablen zu einer beliebigen URL hinzuzufügen, ohne sich Gedanken darüber zu machen, welche Variablen schon in der URL sind und welche nicht. Also wird aus STUDIP.URLHelper.getURL(alte_url, {hallo: "welt"}) ein http:.../about.php?hallo=welt, egal ob alte_url schon einen Wert für hallo angegeben hatte oder auch nicht. Auch muss man sich dann keine Gedanken darüber machen, ob man den Parameter mit "?" oder einem "&" anhängt. Beachten Sie zudem, dass Parameter in der alte_url weniger Priorität haben als Parameter im zweiten Argument.
  • Variablen dauerhaft (also solange die HTML-Seite in Benutzung ist) zu generierten URLs hinzuzufügen. Das geht mit der Methode STUDIP.URLHelper.addLinkParam("hallo", "welt"). Nach diesem Aufruf wird jedes STUDIP.URLHelper.getURL("about.php") zum Beispiel http://..../about.php?hallo=welt zurück geben. Mit dieser Methode werden auch Parameter aus der abgegebenen URL überschrieben, also STUDIP.URLHelper.getURL("about.php?hallo=ich") würde trotzdem Welt als Inhalt des Parameters hallo wider geben. Nicht so jedoch mit STUDIP.URLHelper.getURL("about.php", {hallo: "ich"}), wobei der zweite Parameter wieder einmal Priorität hat.
  • Variablen dauerhaft von generierten URLs abzuziehen, wenn sie denn vorher drin gewesen sein sollten. Dazu gibt man analog zu oben addLinkParam("hallo", "") an und der Parameter hallo wird stets als zwingend leer angesehen und auch aus bestehenden URLs gestrichen, wenn vorher vorhanden.
  • Alle Hyperlinks eines Abschnittes des Dokumentes mit aktuellem Parameter zu versehen. Nach einem oder mehreren addLinkParam-Aufrufen könnte man STUDIP.URLHelper.updateAllLinks("#container"); aufrufen, wodurch alle Links innerhalb des CSS-Selektors "#container" einmal durch die STUDIP.URLHelper.getURL(...) Methode ersetzt werden und dadurch aktuelle Parameter bekommen. Gibt man keinen Selektor an, werden die Links des ganzen Dokuments ersetzt.

2.5  Caching in Javascript

Stud.IP bietet seit Version 3.2 eine Abstraktion des Caching in JavaScript über STUDIP.Cache an. Man erhält eine Instanz mittels STUDIP.Cache.getInstance() mit dem optionalen Parameter prefix. Dieser Präfix sollte nach Möglichkeit immer genutzt und sinnvoll gewählt werden, da er Konflikte beim Zugriff auf Cachedaten verhindern kann. Ist der Präfix gesetzt, so ist gewährleistet, dass der Cache nur auf Daten "unterhalb" dieses Präfixes zugreifen kann, ohne einen entsprechenden Mechanismus bei jeder einzelnen Cacheoperation angeben zu müssen:

  1. let cache0 = STUDIP.Cache.getInstance('foo.');
  2. let cache1 = STUDIP.Cache.getInstance();
  3.  
  4. cache0.set('test', 42);
  5.  
  6. console.log(cache0.get('test'), cache1.get('foo.test'));
  7.  
  8. cache1.set('foo.test', 23);
  9.  
  10. console.log(cache0.get('test'), cache1.get('foo.test'));

Der Cache unterstützt folgende Operationen:

has(key)
Fragt ab, ob der Cache einen Wert für den Schlüssel hat
get(key, setter, expires)
Holt einen Wert für den Schlüssel ab. Ist kein Wert gesetzt und setter ist definiert, so wird der Wert erzeugt und mit der angegebenen Laufzeit gespeichert.
set(key, value, expires)
Speichert einen Wert für den angegebenen Schlüssel mit der angegebenen Laufeit (expires = false bedeutet, dass der Wert gelöscht wird sobald das Browserfenster geschlossen wird).
remove(key)
Löscht den gespeicherten Wert für den angegebenen Schlüssel.
prune()
Löscht alle gespeicherten Daten.

Zu beachten: Sollen Daten nur für einen Nutzer oder eine gewisse Session gespeichert werden, so sollte zwingend ein geeigneter Präfix genutzt werden, der die entsprechenden Daten (gehasht) enthält. Der Cache in Javascript weiß nichts von den Gegebenheiten auf PHP-Seite.

3.  Das jQuery-Framework in Stud.IP

Zur Zeit (Stud.IP 4.1) wird jQuery 3.2.1 und jQuery-UI 1.12.1 verwendet. Von jQuery-UI sind alle Funktionen in Stud.IP geladen.

Kleine Änderungen am Aussehen von jQuery-UI sind auf einer Parallelseite? dokumentiert.

4.  Weitere verwendete JS-Bibliotheken

Eine Übersicht der aktuell verwendeten JS-Bibiliotheken findet sich in package.json.

4.1  jQuery-Plugins

TableSorter (Link)

Dieses Plugin stellt ähnliche Möglichkeiten wie das vormalige TableKit-Plugin (s.o.) zur Verfügung und ermöglicht das flexible client-seitige Sortieren von Tabellen.

4.2  jQuery UI Multiselect (Link)

Das jQuery-UI-Multiselect-Plugin wandelt "multiple select inputs" in sexier aussehende Äquivalente um. Das Plugin wurde in den folgenden Changesets gepatchet:

4.3  JS-L10n (Link)

Diese Bibliothek wird verwendet, um lokalisierte Strings in JS verwenden zu können. Weiteres ist bereits im Wiki dokumentiert.

5.  FAQ

5.1  Wie modularisiere ich meinen JavaScript-Code?

In Stud.IP darf Code nach ECMAScript2015 und besser geschrieben werden, der dann zu ES5 kompiliert wird. Wenn ich meinen Code also auf mehrere Dateien verteilen möchte, verwende ich einfach das "import"-Statement, ein Sprachfeature von JavaScript, das gut auf MDN beschrieben wird: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import

Dazu lege ich also eine zweite Datei an und trage dann in meiner ersten Datei ein "import"-Statement mit dem relativen Pfad zu dieser Datei ein.

5.2  Wie binde ich npm-Bibliotheken ein?

Das kann man gut am Beispiel von "lodash" zeigen: Die lodash-Bibliothek wird via npm installiert: "npm i --save-dev lodash". Dann trage ich in meine Datei ein:

import lodash from "lodash"

5.3  Was muss ich als Modulname bei import hinschreiben?

Die exakten Details finden sich hier: https://webpack.js.org/concepts/module-resolution/#resolving-rules-in-webpack

Hier eine kurze Zusammenfassung:

Verweise ich auf eigenen Code, schreibe ich einen relativen Pfad auf:

import '../src/file1';
import './file2';

Möchte ich eine Bibliothek importieren, verwende ich den Modulnamen der Bibliothek:

import lodash from 'lodash';
import 'module/lib/file';

5.4  Ich möchte Code/Assets nur bei Bedarf laden. Wo muss ich die eintragen und wie lade ich die?

In ECMAScript wird das dynamische Nachladen gerade standardisiert (Anfang 2019 ist diese Feature gerade in Stage 3) Der gegenwärtige Stand ist in https://github.com/tc39/proposal-dynamic-import dokumentiert.

Dennoch kann man dank webpack jetzt schon damit arbeiten. Dazu lade ich mittels des funktionsartigen "import()"-Ausdrucks einfach eine Datei.

Hier ein Beispiel:

  1. import('/modules/my-module.js')
  2.   .then((module) => {
  3.     // Do something with the module.
  4.   }).catch(error => 'An error occurred while loading the module');

Ausführliche Doku dazu gibt es hier:

5.5  Ich habe ein Plugin, das seine Komponenten auch gerne packen würde. Wie sage ich es dem Stud.IP-Kernsystem bzw. dessen Webpack?

Plugins kümmern sich um ihre eigenen Angelegenheiten. Wenn ein Plugin-Entwickler webpack verwenden möchte, tut er das für sein Plugin selbst.

5.6  Wie kann ich die gleiche Funktion zum Document Ready und beim Dialog Update eine Funktion ausführen?

Ab Version Stud.IP 4.4 gibt es dafür den Event studip-ready, der die Events ready und dialog-update zusammenfasst und in beiden Fällen getriggert wird. Vor Version Stud.IP 4.4 muss die gleiche Funktion von Hand an die beiden Events gebunden werden.

5.7  Welches ist der Worttrenner für Javascript-Dateien?

Javascript-Dateien sollen im Kebab-Case-Stil abgelegt werden (also eins-zwei.js und nicht eins_zwei.js).

Letzte Änderung am December 08, 2020, at 11:07 AM von tleilax.