blogeum
 

Archiv für den Tag ‘code’

jQuery-Eventhandler für Änderung der Sichtbarkeit

Kürzlich stand ich wieder einmal vor einem interessanten Problem, welches es zu lösen galt. Eine bestimmte Aktion sollte ausgeführt werden, wenn ein Element (bspw. durch Klicken) sichtbar gemacht wird. In diesem Fall sollte eine asynchrone Anforderung (AJAX) ausgeführt werden. Weil ich aber ein jQuery-Accordion eingesetzt habe, hatte ich keinen direkten Zugriff auf die show-Methode (mal abgesehen vom Umschreiben selbiger). Daher musste ein Weg gefunden werden, eine Aktion auszulösen, sobald sich die Sichtbarkeit des Elements änderte.

Nach ein wenig rumsuchen habe ich schließlich die passenden Events gefunden: DOMAttrModified für Firefox und Opera und onpropertychange für den Internet Explorer (für Chrome und Safari gibt es leider keine einfache Lösung). Dazu die beiden Definitionen:

Occurs after Attr.value has been modified and after an Attr node has been added to or removed from an Element. Document Object Model (DOM) Level 3 Events Specification

Fires when a property changes on the object. onpropertychange Event

Beide Events können in jQuery für ein Element zugleich an einen Eventhandler gebunden werden (man beachte, dass das Event für den IE hier nur propertychange heißt):

$('#elem').bind('DOMAttrModified propertychange', myfunc)

Beim Accordion ändern sich aber ständig die Attribute, wenn es auf- oder zuklappt aufgrund der Animation. Daher wollte ich es nur einmal ausführen lassen. Dies geschieht über die jQuery-Methode one().

$('#elem').one('DOMAttrModified propertychange', myfunc)

In der Funktion myfunc(e) hat man dann, über den standardmäßig übergebenen ersten Parameter e, Zugriff auf das Event-Objekt. Im Internet Explorer geschieht dies über das globale window.event-Objekt.

Im Event-Objekt kann man schließlich leicht überprüfen, ob das Objekt gerade eingeblendet wurde. Dazu stellt das Event-Objekt die Eigenschaft newValue zur Verfügung, welche man nur auf den Wert display: block; überprüfen muss (wird von show() standardmäßig gesetzt). prevValue gibt die vorherigen Attribute (evtl. mehrere!) aus.
Im Internet Explorer sollte man das globale window.event.propertyName überprüfen und gleichzeitig checken, wie die neuen Attribute sind. Dazu prüft man die Eigenschaft window.event.srcElement.style.display auf den Wert block.

Zusammenfassend sieht der Code für die (einmalige) Überwachung der Zustandsänderung und die auszuführende Aktion so aus:

$('#elem').one('DOMAttrModified propertychange', function(e) {
    if((e.attrName == 'style' && e.newValue == 'display: block;') || (window.event.propertyName == 'style.display' && window.event.srcElement.style.display == 'block')) {
        alert('Ich bin sichtbar geworden');
        // weitere Aktionen hier einfügen
    }
});

10 Tipps zu jQuery

Durch Peter Kröner bin ich auf eine interessante Präsentation von Paul Irish gestoßen. In der Präsentation weist Paul auf 10 Dinge hin, die ihm beim Durchlesen der jQuery-Source aufgefallen sind.

Ich kann jedem Interessierten nur empfehlen sich das Video anzuschauen.

Im Folgenden möchte ich auf ein paar Dinge hinweisen, die ich auch noch nicht kannte und die mir speziell aufgefallen sind:

Selbstaufrufende Funktionsdefinition

(function myfunc(param) {

    // Funktionsrumpf hier einfügen

})(myvar);

In obigem Code wird eine (anonyme) Funktion namens myfunc(param) erstellt, die direkt im Anschluss mit dem Parameter myvar aufgerufen wird.

Zeitgesteuerte Wiederholungen

Wenn man ein Ereignis oder eine Funktion nach einer bestimmten Zeitspanne immer wieder wiederholen will, gibt es in JavaScript die Methode setInterval(func, delay). Nachteilig ist aber, dass die Funktion funcimmer nach der Ablauf der Zeitspanne delay aufgerufen wird, egal ob func längere Zeit zur Ausführung benötigt als delay. So kann es zum „Stau“ kommen.

setInterval(function() {
    myfunc();
}, 100);

Wenn myfunc() im Beispiel länger als 100 Millisekunden zur Ausführung benötigt, wird trotzdem nach 100 Millisekunden myfunc() erneut aufgerufen, usw. Doch mit der eben definierten selbstaufrufenden Funktionsdefinition kann man dem entgegenwirken:

(function myloop() {

    myfunc();
    setTimeout(myloop, 100);

})();

myfunc() wird nun erst vollständig abgearbeitet und erst dann nach einer Wartezeit von 100 Millisekunden wird myloop() und damit myfunc() erneut aufgerufen.
Einen Nachteil hat die Lösung aber trotzdem in meinen Augen: myloop() wird rekursiv aufgerufen und erzeugt dadurch natürlich einen Overhead der bei häufiger periodischer Ausführung nicht zu unterschätzen ist.

Elementfilter

In jQuery lassen sich relativ einfach alle Elemente eines bestimmten Typs finden. $(':password') findet beispielsweise alle Passwortfelder. Doch diese Methode ist sehr langsam, weil per Definition alle Elemente innerhalb des DOM durchgegangen werden und geprüft wird, ob die Elemente vom Typ password sind (also quasi $('*:password')).
Beschleunigen lässt sich das ganze, indem man ein wenig spezifischer wird. Den Typ password können nur Input-Felder haben. Also ist es sinnvoll, die Auswahl ein wenig einzuschränken. $('input:password') sucht nur nach Input-Felder vom Typ password, was wesentlich schneller ist.

Das Gleiche gilt auch für die Filter text, radio, checkbox, file, password, submit, image, reset und button.

JSON-Parsing

JavaScipt Object Notation ist ein einfaches Datenaustauschformat auf der Basis von anonymen JavaScript-Objekten. In JavaScript kann man so ein JSON-Objekt theoretisch einfach über eval() auswerten. Aber wie die sicherheitsbewussten unter euch wissen, ist eval() evil!
jQuery nutzt deshalb nativ die browsereigenen JSON-Parsing-Methoden. Für den Fall, dass diese nicht existieren (bspw. in älteren Browsern), haben sich die jQuery-Entwickler eine andere trickreiche Implementierung überlegt, ohne auf eval() zurückgreifen zu müssen:

var x = (new Function('return ' + jsonobj))()

Es wird schlicht ein neues Funktionsobjekt erzeugt, welches dann das JSON-Objekt zurückgibt. Auf diese Weise haben wir auch JSON geparsed, aber ohne Sicherheitsbedenken. Denn im Gegensatz zu eval() darf das Funktionsobjekt nur auf seinen eigenen Namensraum und Gültigkeitsbereich zugreifen und nicht auf den globalen. Daher kann es nicht zum Überschreiben von Variablen, Objekten, usw. kommen. (siehe Kommentare) Trickreich, stimmt’s?

jQuery-Performance

Abschließend möchte ich auch noch auf einen Vortrag von Paul Irish zum Thema „jQuery Anti-Patterns for Performance & Compression“ hinweisen, der einiges noch einmal aufgreift und vertieft. Für jeden jQuery-Entwickler ein Muss!

CSS und JavaScript-Minifier

Heute werde ich wieder einmal eine nützliche PHP-Funktion vorstellen. Genauer gesagt handelt es sich um eine Klasse, die CSS und JavaScript komprimieren kann. Bei diesem Minification (oder minify) genannten Prozess, werden unnötige Kommentare, Leerzeichen, Tabulatoren und Zeilenumbrüche entfernt, wodurch die Datei kleiner wird und so schneller an den Benutzer übertragen werden kann. Zusätzlich kann in der Klasse der „bereinigte“ Quellcode auch noch vor der Übertragung mit gzip oder Deflate komprimiert werden, damit er noch schneller übertragen werden kann.

<?php
/* minifier.class.php */

class minifier {

    public function minify_CSS($str, $zip = FALSE) {
        # Zeilenumbrüche und Tabulatoren entfernen
        $str = str_replace(array("\r", "\n", "\t"), '', $str);

        # Kommentare entfernen
        $str = preg_replace('#/\*(.*)\*/#U', '', $str);

        # Header setzen
        header('Content-Type: text/css; charset=UTF-8');
        header('Cache-Control: public, max-age=1209600');

        if($zip === TRUE)
            $str = $this->zip($str);

        echo $str;
    }


    public function minify_JS($str, $zip = FALSE) {
        # Einzeilige Kommentare entfernen
        $str = preg_replace('#//.*#', '', $str);

        # Zeilenumbrüche und Tabulatoren entfernen
        $str = str_replace(array("\r", "\n", "\t"), '', $str);

        # Mehrzeilige Kommentare entfernen
        $str = preg_replace('#/\*(.*)\*/#U', '', $str);

        # Header setzen
        header('Content-Type: text/javascript; charset=UTF-8');
        header('Cache-Control: public, max-age=1209600');

        if($zip === TRUE)
            $str = $this->zip($str);

        echo $str;
    }


    protected function zip($str) {
        if(isset($_SERVER['HTTP_ACCEPT_ENCODING']) === TRUE) {

            # Welche Verfahren werden unterstützt?
            if(stripos($_SERVER['HTTP_ACCEPT_ENCODING'], 'deflate') !== FALSE) {

                # Komprimieren mit Deflate-Verfahren
                header('Content-Encoding: deflate');
                $str = gzdeflate($str, 9);
            } elseif(stripos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE) {

                # Komprimieren mit gzip-Verfahren
                header('Content-Encoding: gzip');
                $str = gzencode($str, 9);
            }
        }

        return $str;
    }
}
?>

Die Klasse kann dann in einer beliebigen Datei so aufgerufen werden:

<?php
require_once('minifier.class.php');

$min = new minifier();

# JavaScript komprimieren
$min->minify_JS('[JavaScript-Quellcode]', TRUE);

# CSS komprimieren
$min->minify_CSS('[CSS-Quellcode]', TRUE);
?>

Im Gegensatz zu vielen anderen Minifiern habe ich versucht die Optimierungen „leichtgewichtig“ zu gestalten, also einen Kompromiss aus Bandbreiteneinsparung und Ressourcenschonung zu finden. Viele andere Minifier nehmen nämlich noch mehr Optimierungen vor (Variablenumbenennung, komplizierte RegEx-Konstrukte), die viel Rechenzeit benötigen und daher nur bedingt für die On-the-fly-Erstellung von komprimiertem Code geeignet sind. Sicherlich lassen sich damit die Dateigrößen teils erheblich weiter reduzieren aber auf Kosten der serverseitigen Performance.

Auf blogeum wird die Klasse in einer erweiterten Form auch eingesetzt, um CSS und JS vor der Übertragung zu verkleinern und die Übertragungszeiten zu vermindern.

Wie immer steht der Code unter einer Creative Commons-Lizenz.

PHP-Funktion für Wake-on-LAN

Mal wieder eine kleine aber feine PHP-Funktion. Diesmal geht es darum, einen Rechner per Wake-on-LAN-Signal (WOL) über das Netzwerk aufzuwecken. Prinzipiell muss dazu nur eine Verbindung zu einem anderen Rechner im Netzwerk oder einem Router aufgebaut werden, der dann das entsprechende Signal im Netzwerk herumschickt und so den gewünschten Computer aufweckt, sobald dieser das Signal empfängt.

Das Signal ist relativ einfach aufgebaut: 6 Mal der Hexwert FF und anschließend 16 Mal die MAC-Adresse der Netzwerkkarte des zu weckenden Rechners. Dieses sogenannte MagicPacket wird via UDP an Port 0, 7 oder 9 gesendet.

Wenn man die IP-Adresse des Routers nicht kennt, kann auch ein Broadcast (Signal an alle Geräte im Netzwerk) gesendet werden, sofern der Router dies zulässt.

<?php
# MAC-Adresse des zu weckenden Rechners
$mac = '00:00:00:00:00:00';
# IP des Routers (255.255.255.255 für Broadcast)
$ip = 'udp://255.255.255.255';

function wakeup($mac, $router) {
    if(strlen($mac) !== 17)
      return FALSE;

    if(filter_var($router, FILTER_VALIDATE_URL) === FALSE)
      return FALSE;

    $raw = sscanf($mac, '%2x:%2x:%2x:%2x:%2x:%2x');
    $func = create_function('$v', 'return isset($v);');
    $raw = array_filter($raw, $func);

    if(sizeof($raw) !== 6)
      return FALSE;


    # Router-Verbindung aufbauen auf Port 9 mit 4 Sekunden Timeout
    $fp = fsockopen($router, 9, $errnum, $errstr, 4);

    if(is_resource($fp) === FALSE) {
        trigger_error('['.$errnum.'] '.$errstr, E_USER_ERROR);
        return FALSE;
    }


    # Beginn des MagicPacket
    $magicp = "\xFF\xFF\xFF\xFF\xFF\xFF";

    # MAC in ASCII-Zeichen umwandeln
    $hexmac = '';
    foreach($raw AS $c) {
        $hexmac .= chr($c);
    }

    # 16 Mal MAC an MagicPacket anhängen
    $magicp .= $hexmac.$hexmac.$hexmac.$hexmac.$hexmac.$hexmac.$hexmac.$hexmac.$hexmac.$hexmac.$hexmac.$hexmac.$hexmac.$hexmac.$hexmac.$hexmac;

    fwrite($fp, $magicp);
    fclose($fp);

    printf('Wake-Up-Signal an %02X:%02X:%02X:%02X:%02X:%02X übertragen!', $raw[0], $raw[1], $raw[2], $raw[3], $raw[4], $raw[5]);
    return TRUE;
}

wakeup($mac, $ip);
?>

Ich benutze diese Funktion um den Server aus der Ferne aus dem Ruhezustand zu holen, um mit ihm arbeiten zu können.

Haskell ganz einfach

Ein Leidensgenosse im Fach Informatik hat mich auf ein sehr witziges und einfaches Handbuch zur funktionalen ProgrammierspracheHaskell aufmerksam gemacht.
Learn You a Haskell for Great Good! beschreibt sehr einfach und anschaulich die Programmiersprache Haskell und zeigt, was man alles damit machen kann. Bereichert wird das Ganze durch witzige Illustrationen, die vom Autor, Miran Lipovača aus Slowenien, selbst gezeichnet wurden.

Learn You a Haskell for Great Good!

Für alle Haskell-Geplagten ein wirklich sehr schönes und einfaches Tutorial.

Danke an Ulli für den Tipp.

Was’n das?

blogeum ist das persönliche Weblog von Christian Gürtler – seines Zeichens begeisterter Webworker und Programmierer. Er studiert gegenwärtig Angewandte Informatik an der TU Chemnitz.

 

Abonnieren

RSS-IconImmer auf dem Laufenden mit den Einträgen aus dem Weblog als Atom-Feed. Nunmehr 72 Abonnenten!

Folge mir auf Twitter

 

Neue Kommentare

Christian: Hallo, interessante Funktion. Ich denke ich werde das Beispiel für JavaScript…

Tobias Neumann: Hallo, interessante Funktion. Ich denke ich werde das Beispiel für JavaScript…

Christian: OK, den Zugriff auf "superglobale" Objekte wie window habe ich nicht bedacht.…

ChrisB: Auf diese Weise haben wir auch JSON geparsed, aber ohne Sicherheitsbedenken.…

Martha: Das wundert mich auch. Rot ist doch eine Warnfarbe. Ich kann mir das auch nur…

 

Neue Einträge

 

Lesenswert

Werbung

Wikio - Top Blog - High-tech

 

© 2009–2010 Christian Gürtler

Die Blog-Inhalte stehen unter einer Creative Commons-Lizenz.

XHTML · CSS

BlogPingR.de - Blog Ping-Dienst, Blogmonitor Blogverzeichnis - Blog Verzeichnis bloggerei.de