jQuery – Sebastians Blog https://sgaul.de Neues aus den Softwareminen Thu, 13 Mar 2014 20:34:55 +0000 de-DE hourly 1 https://wordpress.org/?v=6.1.1 https://sgaul.de/wp-content/uploads/2019/02/cropped-sgaul-2-1-32x32.jpg jQuery – Sebastians Blog https://sgaul.de 32 32 Javascript: Klick außerhalb eines Bereiches https://sgaul.de/2013/10/15/javascript-klick-ausserhalb-eines-bereiches/ Tue, 15 Oct 2013 15:46:43 +0000 https://sgaul.de/?p=2368 if $('.container').has(ev.target).length == 0 # ... Dies ist beispielsweise hilfreich um ein Popup oder Dropdown-Menü schließen möchte, wenn der Nutzer „irgendwo“ außer auf das Widget selbst klickt.]]> Folgendes Beispiel überwacht ein Dokument auf einen beliebigen Klick, außer wenn dieser in einem bestimmten Element (.container) liegt:

$(document).on 'click', (ev) ->
    if $('.container').has(ev.target).length == 0
      # ...

Dies ist beispielsweise hilfreich um ein Popup oder Dropdown-Menü schließen möchte, wenn der Nutzer „irgendwo“ außer auf das Widget selbst klickt.

]]>
jQuery-Selektor-Erweiterung Labeled https://sgaul.de/2012/09/07/jquery-selektor-erweiterung-labeled/ Fri, 07 Sep 2012 20:43:44 +0000 https://sgaul.de/?p=1378 Für eine TYPO3-Website soll ein E-Mail-Formular eingerichtet werden. Ich nutze die Erweiterung Powermail, die mir alle wesentlichen Schritte abnimmt: Nur die einzelnen Felder müssen angegeben werden, um Formulargenerierung und Mail-Versand muss man sich nicht kümmern. Für ein paar zusätzliche Validierungen nutze ich Javascript, das die entsprechenden Felder des Formulars anhand ihrer IDs identifiziert und validiert. Nachdem auf dem Testsystem alles funktioniert, exportiere ich die Seite in die Live-Website. Dort der Schock: Alle Feld-IDs haben sich geändert, das Javascript funktioniert nicht mehr.

Powermail und das UID-Problem

Die Ursache ist Powermails UID-Nutzung: Formularfelder werden in einer Datenbanktabelle gespeichert, die eine Auto-Increment-ID enthält. Diese UID wird im HTML auch für ID- und Name-Attribut des Feldes verwendet. Beim Export stimmt dieser Identifikator oft nicht mehr überein. Nachdem mir dies nun gefühlte 20 mal (also vermutlich zwei mal) passiert ist, muss eine Lösung her.

Der menschliche Weg: Beschriftungen folgen

Die Idee: Felder nicht anhand eines unberechenbaren Attributs identifizieren, sondern den menschlichen Ansatz wählen: Man gibt an, wie ein Formularelement beschriftet ist.

Mein kleines Projekt Labeled bringt dem jQuery-Selektor diesen Ansatz bei. Die Zusatzfunktion :labeled(suchstring) sucht nach interaktiven DOM-Elementen, die in irgend einer Form durch den entsprechenden String beschrieben werden.

  • Beliebige Elemente, die durch das For-Attribut eines <label for="refid">suchstring</label> referenziert werden
  • Schaltflächen (<button>, <input type="submit|reset">, <a>), die mit suchstring beschriftet sind
  • Fieldsets, die ein direktes Kindelement <legend>suchstring</legend> enthalten

Die Funktion lässt sich in der üblichen jQuery-Selektorsyntax verwenden, z.B. $('button:labeled(Abschicken)'), $('fieldset:labeled(Persönliche Daten) input:labeled(Vorname)') oder einfach $(':labeled(E-Mail-Adresse)').

Labeled auf GitHub

Die jQuery-Selektor-Erweiterung Labeled kann auf der GitHub-Seite des Projekts heruntergeladen werden: https://github.com/SebastianG86/jquery-labeled

Weiterführende Gedanken

Schon während der Entwicklung einer Browser-Automatisierung im Rahmen meiner Masterarbeit bin ich auf viele Webdienste gestoßen, die den Quelltext ihrer Seiten derart generieren, dass eine Selektion schwer fällt. Es werden oft nur Divs oder Spans verwendet, die anhand von zufälligen IDs und Klassen Funktion und Aussehen erhalten. Semantik ist in einem solchen Markup kaum zu finden.

Zu Demonstrationszwecken wollte ich aus einem sozialen Netzwerk automatisiert die Wohnorte der Kontakte auslesen. Diese Orte standen jeweils in einer Tabelle, deren Länge je nach Anzahl der angegebenen Informationen variierte. Das gesuchte Datum war daher mal in der dritten, mal in der fünften Zeile. Da sich auch Klassen und ID der Zelle stets änderten, war eine Adressierung kaum möglich.

Auch in diesen Fällen ist ein beschriftungsorientierter Ansatz sehr hilfreich. Dieser müsste jedoch viel weiter gehen, als es Labeled derzeit erlaubt. Formulierungen wie „die Zelle rechts neben ‚Wohnort’“ erfordern eine weit umfangreichere Syntax und Logik.

Realisierung

Die Erweiterung besteht fast ausschließlich aus einer großen Bedingung. Lediglich die Tests für referenzierende Labels und Legends wurden in Funktionen verschoben – und selbst das hat eigentlich nur optische Gründe.

(function($) {
  $.extend($.expr[':'], {
    labeled: function(element, b, args) {
      var labelText = args[3];
      element = $(element);
      return element.is('a:contains("' + labelText + '")')
        || element.is('button:contains("' + labelText + '")')
        || element.is('input[value="' + labelText + '"]')
        || element.is('*[placeholder="' + labelText + '"]')
        || elementHasReferencingLabel(element, labelText)
        || elementHasLegend(element, labelText);
    }
  });

  function elementHasReferencingLabel(element, labelText) {
    return element.is('[id]') 
        && $('label[for=' + element.attr('id') + ']:contains(' + labelText + ')').length;
  }

  function elementHasLegend(element, labelText) {
    return element.is('fieldset') 
      && element.find('> legend:contains(' + labelText + ')').length;
  }

})(jQuery);
]]>
FIWA: Eine fast native Menüleiste https://sgaul.de/2011/11/27/fiwa-eine-fast-native-menuleiste/ Sun, 27 Nov 2011 10:31:11 +0000 https://sgaul.de/?p=785 Schon seit längerem bin ich auf der Suche nach einem HTML-CSS-JS-Framework, welches sich möglichst genau an das eigene Betriebssystem anpasst und im Anwendungsmodus des Browsers gar nicht als Webdienst auffällt. Leider konnte ich dazu nie etwas finden, daher habe ich mit FIWA („Framework for Integrated Web Applications“) einfach mal einen eigenen Ansatz gestartet. Als erstes ist nun eine recht brauchbare Menüleiste fertig geworden.

FIWA Menubar

Die Menüleiste verhält sich nun ziemlich genau wie die in GTK (und somit hoffentlich auch den meisten anderen Toolkits, viele Tests konnte ich noch nicht machen):

  • immer normaler Mauszeiger
  • Hover-Hervorhebung nur, wenn irgend ein Teil des Menüs offen
  • Untermenüs bleiben offen bis Klick irgendwo im Dokument
  • ist Untermenü offen, werden andere Untermenüs schon durch darüberfahren geöffnet und das vorige geschlossen

Leider wird die Funktionsweise durch eine Screenshot kaum deutlich.

FIWA Menubar

 Das Design

Für die perfekte Systemintegration benötigt man vor allem ein passendes Theme. Ich habe mich vorerst dazu entschieden, keiner speziellen Vorgabe zu folgen, um eine möglichst allgemeine Lösung zu finden, für die man später dann verschiedene Designs erstellen kann.

Einschränkungen

In Chrome und Firefox funktioniert die Menüleiste tadellos. Ich möchte mich bewusst auf moderne Browser beschränken. Im Zweifel kann lieber jemand zehn Minuten in sein IE-6-Update stecken als ich jetzt drei Stunden um es da zum Laufen zu kriegen.

Eine etwas größere Einschränkung ist da die Tatsache, dass das Menü derzeit nur zwei tief verschachtelt werden darf. Jede weitere Ebene ist etwas problematischer, da diese ja neben dem aktuellen Unterpunkt positioniert werden müssen. Aber auch das sollte in absehbarer Zeit machbar sein.

Download

Die Sache ist noch nicht im Ansatz fertig. Wer aber schon einmal hineinsehen möchte, kann das über das entsprechende Github-Projekt tun:

]]>
J-Query-Probleme mit $.event.trigger(), live() und delegate() https://sgaul.de/2011/09/19/event-trigger-live-delegate-problem/ Mon, 19 Sep 2011 17:18:13 +0000 https://sgaul.de/?p=529 In meinem Artikel „J-Query-Projekte mit Events strukturieren“ habe ich die Möglichkeit vorgestellt, Javascript-Events mittels J-Querys $.event.trigger(eventName) auszulösen. Man sollte sich aber bewusst sein, dass diese Methode nicht mit Elementen zusammenarbeitet, die mittels live() oder delegate() an einen Event gebunden wurden.

Bind(), live() und delegate() – der Test

Ein kleiner Test macht deutlich, dass $.event.trigger(eventName) wenig kooperativ ist:

$(function() {

    $('.class0').bind('myevent', function() {
        $(this).css('backgroundColor', '#f99');
    });

    $('.class1').live('myevent', function() {
        $(this).css('backgroundColor', '#f99');
    });

    $('body').delegate('.class2', 'myevent', function() {
        $(this).css('backgroundColor', '#f99');
    });

    $.each([0,1,2,3,4,5], function(i) {
        $('
') .appendTo('body') .text('class' + i) .addClass('class' + i); }); $('.class3').bind('myevent', function() { $(this).css('backgroundColor', '#f99'); }); $('.class4').live('myevent', function() { $(this).css('backgroundColor', '#f99'); }); $('body').delegate('.class5', 'myevent', function() { $(this).css('backgroundColor', '#f99'); }); $.event.trigger('myevent'); });

Lediglich das Element, welches nach seiner Erzeugung mittels bind() verbunden wurde, reagiert auf den Trigger:

Hintergrund

Dass lediglich Bind funktioniert, hat einen einfachen, aber sehr technischen Hintergrund.

Beim Aufruf von $(selektor).bind() sucht J-Query alle Elemente, auf die der Selektor passt und verknüpft sie mit dem entsprechenden Event-Handler. Zusätzlich wird ein Zeiger in $.event hinterlegt. Dies macht man sich bei $.event.trigger() zunutze.

Bei live() und delegate() wird hingegen gar nicht geprüft, welche Elemente auf den Selektor passen. Stattdessen wird das Event auf der fast höchsten Ebene des DOM-Baums registriert, dem Document. Klickt man nun auf ein Div im Body, wird der erzeugte Click-Event nach oben durchgereicht: Div > Body > HTML > Document. So kann jede Schicht für sich selbst auf das Event reagieren, da ein Klick auf das Div auch ein Klick auf den Body der Seite ist.

Dieses Verhalten bezeichnet man als Event-Propagation und wird von J-Query genutzt: Der Handler $(‚div‘).live(‚click‘, callback) registriert einen Klick-Eventhandler im Document. Kommt nun ein solcher Event im Document an, guckt der Handler nach, ob der Event über ein Element ging, das auf den Selektor (im Beispiel ‚div‘) passt. Falls ja, führt er auf all diesen passenden Elementen die Funktion callback aus.

Die Variante delegate() verhält sich, von ihren Kleinigkeiten abgesehen, genauso.

Diese Funktionsweise erklärt, warum Live und Delegate auch auf Elemente wirken, die zur Zeit der Festlegung noch nicht vorhanden waren: Der Selektor wird nach der Event-Auslösung, nicht zur Event-Registrierung ausgewertet.

Mögliche Lösungen

Eine wirklich gute Lösung konnte ich bisher nicht finden.

Man könnte jedem Event eine Klasse zuweisen, und statt auf $.event auf $(‚.eventklasse‘) triggern. Diese Lösung wird sehr oft vorgeschlagen. Ich hingegen finde sie nicht schön: Klassen gehören zum HTML der Seite. Sie sollten nicht für Zwecke missbraucht werden, die nur Javascript tangieren.

Derzeit versuche ich auf Live und Delegate zu verzichten. In den meisten Fällen ist dies problemlos möglich: Entweder das Element existiert bereits im DOM-Baum (steht also direkt in der HTML-Datei) oder ich registriere den Event-Handler direkt nach der Erzeugung des Elements. Für beides kann man dann einfach Bind nehmen.

]]>
J-Query-Projekte mit Events strukturieren https://sgaul.de/2011/09/04/j-query-projekte-mit-events-strukturieren/ Sun, 04 Sep 2011 09:23:13 +0000 https://sgaul.de/?p=511 Sobald es sich um mehr als ein kleines Aufklappmenü handelt, können J-Query-Projekte schnell groß und somit unübersichtlich werden. Schon bevor es soweit ist, sollte man sich eine klare Struktur überlegen. Es gibt unzählige Projekte im Web und somit auch viele verschiedene Vorlagen. Viele nutzen einfache Funktionsstrukturen, einzelne oder auch mehrere Klassen. Was mir bisher kaum über den Weg lief: Mit eigenen Events bietet J-Query eine Möglichkeit, sauber und vor allem unabhängig zu modularisieren. Ganz im Stile des Observer-Patterns muss eine Datenquelle so nicht mehr wissen, wer genau an ihr interessiert ist.

Verschiedene Funktionen auf verschiedene Listener aufteilen

Als Grundlage benötigt man zunächst HTML. Dieses dient als Template, auf welches man dann mittels Javascript zugreifen kann. Hierbei sollte man beachten, überlegt mit IDs und Klassen zu arbeiten. Ist ein Event-Listener wirklich nur für ein Element notwendig, ist die ID das beste Zugriffsmittel. Klassen sind aber wichtiger: Oft ist nicht klar, wie viele Elemente eine spezielle Funktionalität haben müssen, daher sollte man für jede Funktion eine Klasse vergeben:

$('.send-value').click(function() {
    // Button-Value mittels Ajax senden
});

$('.exclusive').click(function() {
    // Andere Buttons mit Klasse exclusive deaktivieren
});

Man erlangt so einen recht gut strukturierten Code, den man einfach wiederverwenden kann. Zudem vermeidet man riesige Funktionskörper in einem einzelnen Listener, in dem man später kaum etwas wiederfinden kann.

Code nach dem Laden der Seite ausführen

Viele J-Query-Plugins bieten bereits eine recht breite Palette an Events, mit denen man arbeiten kann. Will man eigene Event-Ketten aufbauen, muss man in aller Regel nach dem Laden der Seite beginnen.

$(function() {
    // Dieser Code wird nach dem Laden der Seite ausgeführt
});

J-Query-Mobile hat hierfür noch einige andere Events, die man stattdessen nehmen sollte:

$('#page').bind('pageshow', function() {
    // Dieser Code wird nach dem Laden der Seite ausgeführt
});

Eigene Events auslösen: $.event.trigger

Oft kommt man mit den vorgegebenen Events nicht aus. Vor allem wenn man Code wiederverwenden will, muss man diesen oft in verschiedenen Situationen triggern. Ein Beispiel: Ein Listenupdate wird einmal nach dem Laden der Seite und dann immer wieder periodisch ausgeführt. Daher ist es schlecht, die Update-Funktion in die Document-Ready-Event zu packen:

$(function() {
    $.event.trigger('updatelist');
    window.setInterval(
        function() {
            $.event.trigger('updatelist');
        }, 10000);
});

$('#list').bind('updatelist', function() {
    // Das Listenupdate...
});

Die Funktion $.event.trigger ist hierbei enorm wichtig, in der J-Query-Dokumentation aber kaum zu finden. Diese spricht immer nur von $(selector).trigger(event), welche den Event auf einer konkrete Liste von gematchten Elementen auslöst. Dies ist in aller Regel aber eine denkbar schlechte Lösung, da wir eben bei dem Triggern des Events nicht wissen wollen (und sollten), wer an diesem Event interessiert ist. $.event.trigger hingegen triggert den Event auf allen Elementen, die sich dafür registriert haben.

Auf verschiedene Dateien verteilen

Da die Verteilung auf viele kleine Events zwar übersichtlicher, aber keineswegs kürzer ist, wird die entsprechende Javascript-Datei schnell sehr lang. Hier ist es daher empfehlenswert, die Funktionen irgendwie zu gruppieren und auf verschiedene Dateien aufzuteilen. Bei J-Query-Mobile hat man oft mehrere Einzel-Pages in einer Seite. Hier kann man für jede Einzelseite eine eigene Javascript-Datei erzeugen.

Anschließend kann man den JS-Code dann serverseitig wieder zusammenfassen und komprimieren, um die Netzwerklast zu minimieren.

]]>
J-Query Mobile: Nicht nur fürs Smartphone https://sgaul.de/2011/08/04/j-query-mobile-nicht-nur-furs-smartphone/ https://sgaul.de/2011/08/04/j-query-mobile-nicht-nur-furs-smartphone/#comments Thu, 04 Aug 2011 18:59:25 +0000 https://sgaul.de/?p=397 Ich möchte hier kurz über meine ersten Erfahrungen mit J-Query Mobile berichten. Zunächst war ich skeptisch: Für das Tweedback-Projekt benötigten wir eine Webgrundlage, die sowohl auf mobilen Geräten wie auch auf großen Rechnern brauchbar ist. Auf dem Smartphone sehen die Ergebnisse klasse aus – das war schon vorher klar. Für Geräte mit größeren Auflösungen kann man dann einfach modifizieren und so gute Ergebnisse erzielen.

Auf kleinen Bildschirmen…

J-Query Mobile auf dem Smartphone

Wie man sieht, ist das Ergebnis auf dem Telefon sofort sehr hübsch. Hier für reicht es, der noch etwas knappen Dokumentation zu folgen. Was dort allerdings fehlt, ist ein Hinweis, dass die Emulierung von großen Bildschirmen auf Telefonen deaktiviert werden muss. Hierzu einfach folgendes im Head notieren:

<meta name="HandheldFriendly" content="true" />
<meta name="viewport" 
	content="width=device-width, height=device-height, user-scalable=no" />

… und auf großen Monitoren

Diese einspaltige Aufteilung ist für große Bildschirme denkbar schlecht geeignet. Mit ein paar Tricks kann man hier aber schnell in ein Zweispaltenlayout umwandeln. Hierfür werden die beiden Blöcke „Create a new Event“ und „More…“ nicht in ein Div-Element mit der Content gepackt, sondern wie folgt aufgeteilt:

<div data-role="page" id="create-event">
	<div data-role="header" data-theme="b">
		<h1>Tweedback</h1>
	</div>
	<div data-role="content">
		<div class="content-primary">
			<h2>Create a new Event</h2>
		</div>
		<!-- /div.content-primary -->

		<div class="content-secondary">
			<h2>More...</h2>
		</div>
		<!-- /div.content-secondary -->

	</div>
	<!-- /content -->
</div>

Auf kleinen Geräten macht dies zunächst keinen Unterschied. Nun nutzen wir moderne CSS-3-Weichen, um die beiden Blöcke Primary- und Secondary-Content ab einer bestimmten Auflösung nebeneinander darzustellen.

/* === Device size switches */

@media only screen and (min-width: 768px) {
	.content-secondary {
		float: right;
		padding: 0;
		width: 30%;
	}
	.content-primary {
		float: left;
		padding: 0;
		width: 65%;
	}
}

Natürlich sind noch diverse andere Anpassungen für Laptop und Schreibtischrechner denkbar. So ist die Schrift- und Formulargröße nicht jedermanns Sache und wirkt auf den Screenshots etwas erschlagend. Mit dem angesprochenen Ansatz kann man aber vergleichsweise einfach all sowas erreichen – mit vergleichsweise geringem Aufwand.

J-Query Mobile auf großen Bildschirmen

J-Query Mobile: Nur der Name ist schlecht

Der Namenszusatz „Mobile“ ist etwas unglücklich: Auch auf großen Rechnern kann man das Framework wunderbar nutzen und einfach moderne Webapplikationen erzeugen, die auf nahezu jeder Plattform laufen.

]]>
https://sgaul.de/2011/08/04/j-query-mobile-nicht-nur-furs-smartphone/feed/ 3