Anzahl der verbleibenden Zeichen anzeigen

Problem

Ihre Firma hat auf ihrer Website eine Kontaktform. In dieser Form gibt es ein <textarea>-Element , damit die Anwender Anfragen stellen können. Aber Sie wissen ja – Zeit ist Geld und Sie wollen nicht, dass Ihre Mitarbeiter Kurzgeschichten lesen müssen, daher wollen Sie die Länge der Nachrichten beschränken. Dabei wollen Sie dem Anwender auch zeigen, wie viele Zeichen er noch eingeben kann:

<textarea></textarea>
<div class="remaining">Verbleibende Zeichen: <span class="count">300</span></div>

Lösung

Sprechen Sie alle .remaining-Elemente an und suchen sie das zugehörige <textarea>-Element und die maximale Zeichenzahl aus dem Kind-Element .count heraus. Binden Sie an das <textarea> eine update-Funktion, um benachrichtigt zu werden, wenn der Anwender Text eingibt:

// für jedes Element der Art "Verbleibende Zeichen: ###"
$('.remaining').each(function(){

    // Maximalzahl und Eingabefeld finden und sichern
    var $count = $('.count',this);
    var $input = $(this).prev();

    // .text() gibt einen Text zurück. Multipliziert mit 1 wird es eine Zahl (für math)
    var maximumCount = $count.text()*1;

    // update-Funktion wird für die Events keyup, paste und input aufgerufen
    var update = function(){

        var before = $count.text()*1;
        var now = maximumCount - $input.val().length;

        // Prüfen, ob der Anwender die Grenze schon überschritten hat
        if ( now < 0 ){
            var str = $input.val();
            $input.val( str.substr(0,maximumCount) );
            now = 0;
        }

        // DOM nur bei Bedarf aktualisieren
        if ( before != now ){
            $count.text( now );
        }
    };

    // Auf Änderungen lauschen (siehe Diskussion)
    $input.bind('input keyup paste', function(){setTimeout(update,0)} );

    // update direkt aufrufen, falls Eingabefeld schon befüllt ist
    update();

}); // Ende von .each()

Diskussion

Der vorgestellte Code ist generisch genug, um eine beliebige Zahl von »Verbleibende Zeichen«-Ausgaben und <textarea>-Elementen auf einer Seite betreuen zu können. Das kann nützlich sein, wenn Sie ein CMS oder eine Dateneingabemaske haben.

Um auch das Kopieren oder Einfügen von Daten in das <textarea> per Maus zu berücksichtigen, müssen wir die Events input und paste berücksichtigen. Das Event mouseup kann nicht verwendet werden, weil es nicht ausgelöst wird, wenn man einen Eintrag aus dem Kontextmenü des Browsers wählt. Das Event input ist Teil von HTML5 (Working Draft) und schon durch Firefox, Opera und Safari implementiert. Es wird bei einer Benutzereingabe ausgelöst – unabhängig vom verwendeten Eingabegeräte (Maus oder Tastatur). Safari hat zum Zeitpunkt der Entstehung dieses Buches einen Bug, durch den das input-Event bei <textarea>-Elementen nicht ausgelöst wird. Sowohl Safari als auch der Internet Explorer kennen das Event paste für <textarea>-Elemente und keyup, um Tastenaktionen abzufangen. Bindet man keyup, input und paste, ist das zwar redundant, aber in diesem Fall hilfreich. Die Funktion update ist so einfach, dass sie keine Performance-Probleme hervorruft, und sie passt das DOM nur dann an, wenn es notwendig ist. Es gibt also keinen Ärger durch Redundante Aufrufe von update.

Eine Alternative zu redundanten Events wäre das Verwenden von setInterval , solange das <textarea>-Element den Fokus besitzt. Die gleiche Funktion update könnte in regelmäßigen Zeitabständen aufgerufen werden. Kombiniert mit dem Event keyup würden Sie beim Tippen eine direkte Reaktion erhalten und mit einem passenden Aktualisierungs-Intervall – vielleicht so 300 Millisekunden – würde auch nach einem Einfügen eine Reaktion erfolgen. Ist die update-Funktion komplexer oder teurer, dann kann das der bessere Weg sein.

Bindet man Events an Form-Elemente, ist es manchmal wichtig, einen Timeout zu nutzen, um einen Funktionsaufruf ein bisschen zu verzögern. Im vorherigen Beispiel löst der Internet Explorer das paste-Event aus, bevor der Text aus der Zwischenablage tatsächlich im <textarea>-Element eingefügt wird. Dadurch würde die Berechnung der verbleibenden Zeichen falsch sein, bis der Anwender klickt oder eine Taste drückt. Durch setTimeout(update,0) wird die Funktion update an das Ende des Call Stacks verschoben und erst ausgelöst, nachdem der Browser den Text hinzugefügt hat:

$input.bind('input keyup paste', function(){setTimeout(update,0)} );

Was geschieht, wenn JavaScript abgeschaltet ist? Sie sollten den Bereich »Verbleibende Zeichen« standardmäßig per CSS verbergen. Mit JavaScript fügen Sie dann einem Eltern-Element einen Klassennamen hinzu, der die CSS-Regel überschreibt. Es ist zudem wichtig, die Länge der Nachricht auf dem Server zusätzlich zu kontrollieren:

<style type="text/css" title="text/css">
 .remaining { display:none; }
 .jsEnabled .remaining { display:block; }
</style>

...

// wenn das HTML-DOM bereit ist
$(document).ready(function(){
    $('form').addClass('jsEnabled');
});
JQuery Kochbuch
titlepage.xhtml
part0000.html
part0001.html
part0002_split_000.html
part0002_split_001.html
part0002_split_002.html
part0003_split_000.html
part0003_split_001.html
part0003_split_002.html
part0003_split_003.html
part0003_split_004.html
part0003_split_005.html
part0003_split_006.html
part0003_split_007.html
part0004_split_000.html
part0004_split_001.html
part0004_split_002.html
part0004_split_003.html
part0004_split_004.html
part0004_split_005.html
part0004_split_006.html
part0004_split_007.html
part0004_split_008.html
part0004_split_009.html
part0004_split_010.html
part0004_split_011.html
part0004_split_012.html
part0004_split_013.html
part0004_split_014.html
part0004_split_015.html
part0004_split_016.html
part0004_split_017.html
part0004_split_018.html
part0005_split_000.html
part0005_split_001.html
part0005_split_002.html
part0005_split_003.html
part0005_split_004.html
part0005_split_005.html
part0005_split_006.html
part0005_split_007.html
part0005_split_008.html
part0005_split_009.html
part0005_split_010.html
part0005_split_011.html
part0005_split_012.html
part0005_split_013.html
part0006_split_000.html
part0006_split_001.html
part0006_split_002.html
part0006_split_003.html
part0006_split_004.html
part0006_split_005.html
part0006_split_006.html
part0006_split_007.html
part0006_split_008.html
part0006_split_009.html
part0006_split_010.html
part0007_split_000.html
part0007_split_001.html
part0007_split_002.html
part0007_split_003.html
part0007_split_004.html
part0007_split_005.html
part0007_split_006.html
part0007_split_007.html
part0007_split_008.html
part0007_split_009.html
part0007_split_010.html
part0007_split_011.html
part0008_split_000.html
part0008_split_001.html
part0008_split_002.html
part0008_split_003.html
part0008_split_004.html
part0008_split_005.html
part0008_split_006.html
part0008_split_007.html
part0008_split_008.html
part0008_split_009.html
part0008_split_010.html
part0008_split_011.html
part0008_split_012.html
part0008_split_013.html
part0008_split_014.html
part0008_split_015.html
part0008_split_016.html
part0008_split_017.html
part0008_split_018.html
part0008_split_019.html
part0008_split_020.html
part0008_split_021.html
part0008_split_022.html
part0009_split_000.html
part0009_split_001.html
part0009_split_002.html
part0009_split_003.html
part0009_split_004.html
part0009_split_005.html
part0009_split_006.html
part0009_split_007.html
part0009_split_008.html
part0009_split_009.html
part0009_split_010.html
part0010_split_000.html
part0010_split_001.html
part0010_split_002.html
part0010_split_003.html
part0010_split_004.html
part0010_split_005.html
part0010_split_006.html
part0010_split_007.html
part0010_split_008.html
part0010_split_009.html
part0010_split_010.html
part0010_split_011.html
part0011_split_000.html
part0011_split_001.html
part0011_split_002.html
part0011_split_003.html
part0011_split_004.html
part0011_split_005.html
part0011_split_006.html
part0011_split_007.html
part0011_split_008.html
part0011_split_009.html
part0011_split_010.html
part0011_split_011.html
part0012_split_000.html
part0012_split_001.html
part0012_split_002.html
part0012_split_003.html
part0012_split_004.html
part0012_split_005.html
part0012_split_006.html
part0012_split_007.html
part0012_split_008.html
part0013_split_000.html
part0013_split_001.html
part0013_split_002.html
part0013_split_003.html
part0013_split_004.html
part0013_split_005.html
part0013_split_006.html
part0013_split_007.html
part0013_split_008.html
part0013_split_009.html
part0013_split_010.html
part0013_split_011.html
part0013_split_012.html
part0014_split_000.html
part0014_split_001.html
part0014_split_002.html
part0014_split_003.html
part0014_split_004.html
part0014_split_005.html
part0014_split_006.html
part0014_split_007.html
part0014_split_008.html
part0014_split_009.html
part0014_split_010.html
part0014_split_011.html
part0015_split_000.html
part0015_split_001.html
part0015_split_002.html
part0015_split_003.html
part0015_split_004.html
part0015_split_005.html
part0015_split_006.html
part0015_split_007.html
part0015_split_008.html
part0015_split_009.html
part0015_split_010.html
part0016_split_000.html
part0016_split_001.html
part0016_split_002.html
part0016_split_003.html
part0016_split_004.html
part0016_split_005.html
part0016_split_006.html
part0016_split_007.html
part0016_split_008.html
part0016_split_009.html
part0017_split_000.html
part0017_split_001.html
part0017_split_002.html
part0017_split_003.html
part0017_split_004.html
part0017_split_005.html
part0017_split_006.html
part0017_split_007.html
part0017_split_008.html
part0017_split_009.html
part0017_split_010.html
part0017_split_011.html
part0018_split_000.html
part0018_split_001.html
part0018_split_002.html
part0018_split_003.html
part0018_split_004.html
part0018_split_005.html
part0018_split_006.html
part0019_split_000.html
part0019_split_001.html
part0019_split_002.html
part0019_split_003.html
part0019_split_004.html
part0019_split_005.html
part0019_split_006.html
part0019_split_007.html
part0019_split_008.html
part0019_split_009.html
part0019_split_010.html
part0020_split_000.html
part0020_split_001.html
part0020_split_002.html
part0020_split_003.html
part0020_split_004.html
part0020_split_005.html
part0020_split_006.html
part0020_split_007.html
part0020_split_008.html
part0021_split_000.html
part0021_split_001.html
part0021_split_002.html
part0021_split_003.html
part0021_split_004.html
part0021_split_005.html
part0021_split_006.html
part0021_split_007.html
part0021_split_008.html
part0021_split_009.html
part0022.html
part0023.html
part0024.html
part0025.html