JavaScript urlEncode()

Für eine Web-Applikation bei uns im Geschäft musste ich einen Link auf eine Seite mit PHP und Javascript erstellen. An sich kein Problem, nur musste ich in der URL einige Parameter mit Umlauten und Sonderzeichen übergeben, also musste ich den Parameter richtig umwandeln. Mit PHP geht das recht simpel mit der Funktion urlEncode() nur in Javascript gibt es da keine Funktion die einen String genau gleich maskiert.
Zwar gibt es die Funktionen encodeURI() und encodeURIComponent(), nur wandeln die die Zeichen etwas anders um:
Original:                 test: äöü +?&% -*'()
PHP urlEncode():          test%3A+%E4%F6%FC+%2B%3F%26%25+-%2A%27%28%29
JS encodeURI():           test:%20%C3%A4%C3%B6%C3%BC%20+?&%25%20-*'()
JS encodeURIComponent():  test%3A%20%C3%A4%C3%B6%C3%BC%20%2B%3F%26%25%20-*'()

Also habe ich mir selber eine Funktion gebastelt, die den String zuerst mit espace() maskiert und danach noch gewisse Sonderzeichen mit replace() richtig umwandelt.
function urlEncode(str)
{
    return escape(str).replace(/\+/g,'%2B').
        replace(/%20/g,'+').replace(/@/g,'%40').
        replace(/\*/g,'%2A').replace(/\//g,'%2F');
}

Das Resultat sieht dann folgendermassen aus:
Original:         test: äöü +?&% -*'()
PHP urlEncode():  test%3A+%E4%F6%FC+%2B%3F%26%25+-%2A%27%28%29
JS urlEncode():   test%3A+%E4%F6%FC+%2B%3F%26%25+-%2A%27%28%29

Datum mit JavaScript überprüfen

Bei gewissen Web-Formularen muss der Benutzer ein Datum eingeben und da möchte man natürlich auch sicherstellen, dass das eingegebene Datum korrekt ist. Mit JavaScript kann man dem Benutzer so recht schnell eine Rückmeldung geben, ob das eingegebene Datum auch korrekt ist oder nicht. Natürlich sollte man das Datum schlussendlich auch noch Serverseitig (PHP/ASP usw.) prüfen, da JavaScript ja schnell deaktiviert ist.
Die nachfolgende JavaScript Funktion überprüft ein Datum im Format DDMMYY auf seine Gültigkeit. Auch Schaltjahre werden so korrekt geprüft.
// die funktion erwartet das datum als string (DDMMYY)
function checkdate(value)
{
    // abbrechen wenn kein datum angegeben wurde
    if(value=="")
    {
        return false;
    }

    // regular expression fuer einen rudimentaeren check
    var reg=new RegExp("^([0-3]{1})([0-9]{1})([0-1]{1})([0-9]{1})([0-2]{1})([0-9]{1})$","g");

    // pruefen und abbrechen wenn es nicht passt
    if(!value.match(reg))
    {
        return false;
    }

    // string in tage, monate und jahre aufteilen
    var day=value.substr(0,2);
    var month=value.substr(2,2)-1;
    var year=value.substr(4,2);

     // datum erstellen
     date=new Date(year,month,day);

    // jahr ueberpruefen
    if(year!=date.getYear())
    {
        return false;
    }

    // monat ueberpruefen
    if(month!=date.getMonth())
    {
        return false;
    }

    // tag ueberpruefen
    if(day!=date.getDate())
    {
        return false;
    }

    // datum ist korrekt
    return true;
}

Callback-Funktion für die Ajax-Klasse

Nun gibts wieder ein kleines Update meiner Ajax Klasse. Das "Problem" an der alten Version war, dass die Ausgabe einfach in ein HTML-Element geschrieben wurde. Dies ist natürlich eine Einschränkung, auch wenn es bei meinen Scripten zu 95% korrekt ist. Bei den anderen 5% muss die Ausgabe der Daten aber anders aussehen. Bisher war die folgende Zeile für die Ausgabe verantwortlich:
document.getElementById(obj.dst).innerHTML=http.responseText;

Die Lösung ist nun die Folgende: Man kann der Klasse wenn nötig eine Funktion mit dem Namen "response" hinzufügen und wenn die existiert wird sie mit den Daten als Parameter aufgerufen, sonst werden die Daten wie bisher einfach ins HTML-Element geschrieben. Aus der Zeile oben wird nun der folgende Codeschnippsel:
// wenn obj.response eine funktion ist, diese aufrufen
if(typeof(obj.response)=="function")
{
    obj.response(http.responseText);
}
else
{
    // antwort ins html element schreiben
    document.getElementById(obj.dst).innerHTML=http.responseText;
}

Und die Benutzerdefinierte "Callback-Funktion" erstellt man so:
ajax=new XMLHttp;

// benutzerdefinierte funktion
ajax.response=function(response)
{
    // inhalt ins html element schreiben
    document.getElementById(ajax.dst).innerHTML=response;

    // element mit einem scriptaculous effekt anzeigen
    Effect.BlindDown(ajax.dst);
}

// ajax request starten
ajax.start("rpc.php?id="+id,"element");

Das wars auch schon. Recht simpel aber es Funktioniert tadellos und ist in meiner Meinung auch sehr einfach zu handhaben. Die komplette Klasse sieht nun so aus:
// Objekt XMLHttp
// Param url: Url der aufzurufenden Datei
// Param dst: ID des HTML-Elements
XMLHttp=function()
{
    // Den Browsertyp auslesen
    var browser=navigator.appName;
    var obj=this;

    if(browser=="Microsoft Internet Explorer")             // IE
    {
        var http=new ActiveXObject("Microsoft.XMLHTTP");
    }
    else                                                   // Mozilla, Safari, Opera, usw.
    {
        var http=new XMLHttpRequest();
    }
    
    obj.start=function(url,dst,load)
    {
        http.abort();
        obj.dst=dst;

        // HTTP-Request senden
        // Parameter url als Adresse
        http.open("GET",url,true);

        // Wenn Status geaendert wurde
        http.onreadystatechange=function()
        {
            // ueberpruefen, ob daten gesendet wurden
            // readystate 4 = complete
            if(http.readyState==4)
            {
                // ueberpruefen, ob status vom server richtig ist
                // status 200 = OK
                if(http.status==200)
                {
                    // wenn obj.response eine funktion ist, diese aufrufen
                    if(typeof(obj.response)=="function")
                    {
                        obj.response(http.responseText);
                    }
                    else
                    {
                        // antwort ins html element schreiben
                        document.getElementById(obj.dst).innerHTML=http.responseText;
                    }
                }
                else if(http.status!=0)
                {
                    // HTTP Fehlercode zurückgeben
                    document.getElementById(obj.dst).innerHTML="Fehler:\nHTTP-Status: "+http.status+"\nHTTP-Statustext: "+http.statusText;
                }
            }
        }
        
        http.send(null);
    }
}

Caching von AJAX-Requests verhindern

Bei meiner Ajax Klasse hatte ich ein kleines Problem, dass der Internet-Explorer gewisse Ajax-Requests zwischengespeichert wurden. Dies ist ja oft nicht was man will, den schliesslich soll immer das neuste Resultat zurückgeliefert werden.
Zum einen kann man bei der Serverseitigen Datei, die mit dem Request aufgerufen wird mit den Headers eine Cache-Control bewirken. Leider hat das bei mir nicht immer geholfen. Funktionieren würde es bei PHP so:
<?
header
("Cache-Control: no-cache, must-revalidate");
header("Expires: Sat, 26 Jul 1997 05:00:00 GMT");
?>


Die Lösung die bei mir funktioniert hatte war die folgende: Wenn bei der Url die angefragt wird zusätzlich noch ein Parameter mit einer zufälligen Zahl angehängt wird meint der Browser, dass er die Adresse zum ersten mal aufruft und nimmt so keine Daten aus dem Cache. Das sieht dann so aus:
ajax=new XMLHttp;
var url="rpc.php?param=test&rand="+Math.random();
ajax.start(url,"elem");

Update der Ajax-Klasse

Nun habe ich doch noch einen "Fehler" in meiner Ajax-Klasse gefunden. In meiner Web-Applikation kann der Benutzer in mehreren Inputfeldern Daten Filtern, die dann in einem Dix-Container ausgegeben werden. Mit einem listener auf den Felder wird überprüft, ob etwas eingegeben wurde und sobald dies der Fall ist, wird mittels Ajax die PHP-Datei aufgerufen und die Daten in den Container geschrieben. Soweit so gut, das hat mit der alte Klasse auch recht gut geklappt. Allerdings ist es ja so, dass je mehr Daten der Benutzer eingab, desto weniger Daten musste die PHP-Datei zurückgeben. Mehr Filterdaten=Weniger Daten.
Das bedeutet aber auch, dass die Requests die anfangs gestartet werden länger dauern als die die später gestartet werden. Nun kann es also sein (wenn der Benutzer schnell ist), dass zuerst ein Request gestartet wird, bei welchem eine grosse Datenmenge zurück kommt. In der Zwischenzeit wird ein nächster Request, vom selben Benutzer in derselben Session ein neuer Request mit mehr Filter-Daten gestartet, bei welchem weniger Daten zurückkommen. Dieser wird natürlich von PHP schneller abgearbeitet und wird auch schneller zum Browser übertragen. So werden diese Daten schön im Div-Container angezeigt. Soweit so gut, das Problem ist nun allerdings, dass noch der andere Request mit der grösseren Datenmenge läuft und sobald der fertig ist, werden die Daten angezeigt. Also die alten, die der Benutzer gar nicht sehen will.
Das bedeutet nun also, dass sobald ein neuer Request gestartet wird, der alte, noch laufende, abgebrochen werden soll. Allerdings sollen aber trotzdem mehre Requests paralell laufen können.

Das bedeutet, dass pro Objekt ein Request aufs mal laufen soll, aber man soll mehrere Objekte erstellen können, die dann paralell laufen können.

In meiner Lösung erstellt man nun als erstes eine Referenz zu einem Objekt in welchem ein XMLHttp Objekt erstellt wird. Wenn nun ein Request gestartet werden soll, ruft man die Methode start() mit URL, und ID des Ziels auf. Sobald ein neuer Request mit demselben Objekt gestartet wird, wird der alte abgebrochen.
ajax=new XMLHttp;
ajax.start("ajax.php?param=test","feld");
Weitere Requests können dann so gestartet werden:
ajax.start("ajax.php?param=blub","feld");
Und auch mehrere paralelle Requests sind kein Problem:
ajax1=new XMLHttp;
ajax2=XMLHttp;

ajax1.start("ajax.php?param=ajax1","feld1");
ajax2.start("ajax.php?param=ajax2","feld2");
Und die ganze Klasse sieht dann so aus:
// Objekt XMLHttp
// Param url: Url der aufzurufenden Datei
// Param dst: ID des HTML-Elements
XMLHttp=function()
{
    // Den Browsertyp auslesen
    var browser=navigator.appName;
    var obj=this;

    if(browser=="Microsoft Internet Explorer")             // IE
    {
        var http=new ActiveXObject("Microsoft.XMLHTTP");
    }
    else                                                   // Mozilla, Safari, Opera, usw.
    {
        var http=new XMLHttpRequest();
    }

    obj.start=function(url,dst)
    {
        // bereits laufende requests abbrechen
        http.abort();

        obj.dst=dst;

        // HTTP-Request senden
        // Parameter url als Adresse
        http.open("GET",url,true);

        // Wenn Status geaendert wurde
        http.onreadystatechange=function()
        {
            // ueberpruefen, ob daten gesendet wurden
            // readystate 4 = complete
            if(http.readyState==4)
            {
                // ueberpruefen, ob status vom server richtig ist
                // status 200 = OK
                if(http.status==200)
                {
                    // Antwort vom Server in HTML-Element schreiben
                    // dst als ID
                    document.getElementById(obj.dst).innerHTML=http.responseText;
                }
                else if(http.status!=0)
                {
                    // HTTP Fehlercode zurückgeben
                    document.getElementById(obj.dst).innerHTML="Fehler:\nHTTP-Status: "+http.status+"\nHTTP-Statustext: "+http.statusText;
                }
            }
        }

        http.send(null);
    }
}

Simple AJAX-Klasse

Ich habe vor ca zwei Jahren bereits einen kleinen Beitrag über Ajax geschrieben und muss das nun für ein Projekt im Geschäft neu aufrollen. Mit etwas OOP habe ich eine Ajax-Klasse geschrieben, die die Daten vom Server abholt und in ein beliebiges HTML-Element einfügt.
Mit dieser Klasse sind auch mehrere paralelle Anfragen möglich und das ganze funktioniert im Firefox, IE und Opera. Andere Browser habe ich (noch) nicht getestet. Die Klasse erwartet den Parameter Url (die Adresse die aufgerufen werden soll) und Dst (die ID des HTML-Elements für die Ausgabe). Mit einigen Veränderungen kann die Klasse die Daten vom Server auch anderst ausgeben und natürlich kann man das noch beliebig erweitert (Lade-Bilder usw.).
// Objekt XMLHttp
// Param url: Url der aufzurufenden Datei
// Param dst: ID des HTML-Elements
XMLHttp=function(url,dst)
{
    // Den Browsertyp auslesen
    var browser=navigator.appName;

    if(browser=="Microsoft Internet Explorer")             // IE
    {
        var http=new ActiveXObject("Microsoft.XMLHTTP");
    }
    else                                                   // Mozilla, Safari, Opera, usw.
    {
        var http=new XMLHttpRequest();
    }

    // HTTP-Request senden
    // Parameter url als Adresse
    http.open("GET",url,true);

    // Wenn Status geaendert wurde
    http.onreadystatechange=function()
    {
        // ueberpruefen, ob daten gesendet wurden
        // readystate 4 = complete
        if(http.readyState==4)
        {
            // ueberpruefen, ob status vom server richtig ist
            // status 200 = OK
            if(http.status==200)
            {
                // Antwort vom Server in HTML-Element schreiben
                // dst als ID
                document.getElementById(dst).innerHTML=http.responseText;
            }
            else
            {
                // HTTP Fehlercode zurückgeben
                alert("Fehler:\nHTTP-Status: "+http.status+"\nHTTP-Statustext: "+http.statusText);
            }
        }
    }
    http.send(null);
}


Das ganze wird nun folgendermassen aufgerufen. Hier wird die Adresse ajax.php?param=test angefragt und die Anwort vom Server wird in das HTML-Element (div/span/p/usw..) geschrieben.
ajaxObj=new XMLHttp("ajax.php?param=test","feld");

OOP mit JavaScript: Prototypen

Noch ein kleiner, zweiter Teil zu OOP mit JavaScript. Und zwar möchte ich kurz erklären, wie es mit Prototypen möglich ist nachträglich noch Variablen und Methoden zu einem Objekt hinzuzufügen.

Beispiel

Zuerst habe ich hier ein kleines Objekt, welches als Basis des Beispiels dient. Darin hat es eine Variable und eine Methode, die beide Public sind.
// Das Objekt (Klasse) erstellen
meinObjekt=function()
{
    // Eine Variable erstellen
    this.variable="Variable";
    
    // Eine Methode
    this.methode=function()
    {
        alert("Methode");
    }

    return true;
}

Variabeln / Methoden hinzufügen

Nun kann man dem bestehenden Objekt (oder einer Referenz) mit dem Stichwort Prototype neune Variabeln oder Mehthoden hinzufügen. Diese sind dann immer Public, also von überall verfügbar. Das sieht dann so aus:
// Eine neue Variable hinzufuegen
meinObjekt.prototype.neueVariable="Eine neue Variable";

// Eine neue Methode hinzufuegen
meinObjekt.prototype.neueMethode=function()
{
    alert(this.neueVariable);
}

Variabeln / Methoden aufrufen

Um auf die neuen Variabel oder Methoden zuzugreifen erstellt man wie übliche eine Referenz und hat sie nun ganz nortmal zur Verfügung.
// Referenz erstellen
var obj=new meinObjekt;

// Ausgabe der Variable
alert(obj.neueVariable);

// Aufruf der Methode
obj.neueMethode();

OOP mit JavaScript: Kurze Einführung

Ich bin gerade wieder an unserer kleinen Inhouse-Webaplikation am Arbeiten und versuche meine Ajax-Funktion zu verbessern, da das langsam aber sicher ein kleines Chaos wird. Bei Recherechen im Internet bin ich wieder auf die OOP-Möglichkeiten von JavaScript gestossen. Ob JavaScript nun wirklich Objekt-Orientiert oder nur -Basiert ist will ich hier aussen vor lassen, das tut nichts zu Sache. Ich möchte an dieser Stelle nur die wichtigsten Dinge notieren, in der Hoffnung, dass jemand anders, oder ich in einiger Zeit damit etwas weiter kommt. Möglicherweise gibts dann noch einen zweiten Teil dazu.

Ein Objekt erzeugen

Mit JavaScript hat man die Möglichkeit ein Objekt zu erstellen und diesem dann im Nachhinein Eigeschaften (Variabeln) und Methoden (Funktionen) hinzuzufügen. Dies hat aber den Nachteil, dass man die Eigenschaften/Methoden jedem Objekt hinzufügen muss. Von dem her ist das eine weniger praktikable Lösung.
// Ein Objekt erzeugen
var obj=new Object();

// Eine Eigenschaft definieren
obj.eigenschaft="Inhalt";

// Eine Methode definieren
obj.methode=function() 
{
    alert(obj.eigenschaft);
}

// Die Methode aufrufen
obj.methode();

Eine Referenz erstellen

Die bessere Lösung ist es eine Klasse zu erstellen und danach eine Referenz zu diesem Objekt zu machen. In JavaScript gibt es allerdings keine Klassen, so muss man eine Funktion nehmen. Mit new erstellt man dann die Refernz.
// Eine Funktion erstellen
function meinObjekt()
{
    ...
}

// Eine Referenz zum Objekt erstellen
var obj=new meinObjekt();

Variabeln: Privat und Public


Auch in JavaScript hat man die Möglichkeit zu definieren, ob eine Variable Public, oder Private ist. Auf Variabeln die Public sind, kann auch von ausserhalb des Objekts zugegriffen werden, auf private allerdings nicht. Um eine Variable zu erstellen die Public ist muss man this. davorstellen.
// Das Objekt (Klasse) erstellen
meinObjekt=function()
{
    // Eine "Public" Variable erstellen
    this.public="Public";
    
    // Eine Private Variable erstellen
    var privat="Privat";

    return true;
}

// Die Referenz erstellen
var klasse=new klasse();

alert(klasse.public);        // Gibt "Public" zurueck
alert(klasse.privat);        // Gibt "undefined" zurueck

Methoden: Privat und Public

Bei Methoden sieht das ganz ähnlich aus wie bei den Variabeln, auch hier kann man Methoden erstellen die Public oder eben Privat sind.
// Das Objekt (Klasse) erstellen
meinObjekt=function()
{
    // Eine "Public" Methode erstellen
    this.public=function()
    {
        alert("Public");
    }
    
    // Eine Private Methode erstellen
    privat=function()
    {
        alert("Privat");
    }

    return true;
}

// Die Referenz erstellen
var klasse=new klasse();

klasse.public();             // Gibt "Public" zurück
klasse.privat();             // Gibt nichts zurück


So, das war mal ein kleiner Exkurs in die OOP-Möglichkleiten von JavaScript. Es gibt da noch eine andere Funktionen, die ich wohl spätzer noch genauer erläutern werde.

Google Chart API: Diagramme im Handumdrehen erstellen

Google bietet ja jede menge APIs an, mit welchen man sich das Leben vereinfachen (?) kann. Unter anderem gibt es da die Google Chart API, mit welcher man im Handumdrehen Diagramme erstellen kann. Zur auswahl stehen die üblichen Balken-, Kuchen-, Linien-, usw. Diagramme.
Das ganze funktioniert in dem man eine Url von Google aufruft und dort einige Parameter angibt; zurück kommt dann ein Bild. So kann man die Url gleich beim img Tag als src angeben.
Eine Anleitung und übersicht mit allen möglichen Diagrammen und Parametern gibts bei Google.

Ich habe einen kurzen Test gemacht und die die Verteilung der Artikel in den Themen dargestellt, recht einfach und das Resultat kann sich sehen lassen:
http://images.t-error.ch/blog/929/chart.png

Datum mit Javascript überprüfen

Da ich mich gerade etwas mit JavaScript am herumschlagen bin, gibts mal wieder etwas aus dieser Ecke. Für eine kleine Applikation im Geschäft brauchte ich eine Funktion, welche überprüft, ob das Datum in einem Textfeld korrekt ist. Mit Regular Expressions geht das bis zu einem gewissen Grad, überprüfen ob das Datum aber wirklich gültig ist geht damit allerdings nicht. Stichwort Schaltjahr.

Mit dem JavaScript Objekt date kann man das Datum aber sehr genau überprüfen. Die folgende Funktion überprüft, ob das Datum im Textfeld das Format ddmmyy hat und ob es gültig ist.
Aufrufen kann man die Funktion zum Beispiel so:
<input type="text" name="datum" onBlur="checkdate(this)" />

Und hier die Funktion.
function checkdate(object)
{
    // wert auslesen
    value=object.value;

    // regexp zur grobpruefung des datums
    var reg=new RegExp("^([0-3]{1})([0-9]{1})([0-1]{1})([0-9]{1})([0-2]{1})([0-9]{1})$","g");

    // wenn das regexp nicht matcht 
    if(!value.match(reg))
    {
        return false;
    }
    
    // datum in tag, monat und jahr zerlegen
    var day=value.substr(0,2);
    var month=value.substr(2,2)-1;
    var year=value.substr(4,2);

    // datums objekt erstellen
    date=new Date(year,month,day);

    // jahr ueberprufen
    if(year!=date.getYear())
    {
        return false;
    }

    // monat ueberpruefen
    if(month!=date.getMonth())
    {
        return false;
    }

    // tag ueberpruefen
    if(day!=date.getDate())
    {
        return false;
    }

    return true;
}