<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Filzblog &#187; signavio</title>
	<atom:link href="http://the-pixelpla.net/blog/tag/signavio/feed/" rel="self" type="application/rss+xml" />
	<link>http://the-pixelpla.net/blog</link>
	<description>So long and thanks for all the fish!</description>
	<lastBuildDate>Fri, 26 Nov 2010 15:43:02 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>Richtext Editing</title>
		<link>http://the-pixelpla.net/blog/2010/10/richtext-editing/</link>
		<comments>http://the-pixelpla.net/blog/2010/10/richtext-editing/#comments</comments>
		<pubDate>Tue, 19 Oct 2010 17:33:21 +0000</pubDate>
		<dc:creator>Phil</dc:creator>
				<category><![CDATA[Nerd Stuff]]></category>
		<category><![CDATA[cross-browser]]></category>
		<category><![CDATA[hell]]></category>
		<category><![CDATA[lernen]]></category>
		<category><![CDATA[signavio]]></category>
		<category><![CDATA[web 2.0]]></category>

		<guid isPermaLink="false">http://the-pixelpla.net/blog/?p=420</guid>
		<description><![CDATA[Diesen Sommer habe ich den August noch einmal bei Signavio verbringen dürfen. Meine Aufgabe war es einen Richtext Editor zu implementieren. Das besondere dabei war, dass der Editor natürlich nicht nur im Firefox, sondern in allen großen Browsern, also auch Chrome, Opera (, IE6) laufen muss. Dass das nicht leicht werden würde, war mir schon [...]]]></description>
			<content:encoded><![CDATA[<p>Diesen Sommer habe ich den August noch einmal bei <a href="http://www.signavio.com">Signavio</a> verbringen dürfen. Meine Aufgabe war es einen Richtext Editor zu implementieren. Das besondere dabei war, dass der Editor natürlich nicht nur im Firefox, sondern in allen großen Browsern, also auch Chrome, Opera (, IE6) laufen muss. Dass das nicht leicht werden würde, war mir schon klar aber mit manchen Tücken habe ich wirklich nicht gerechnet. Da mich schon mindestens zwei Leute darauf angesprochen haben, meine Erfahrungen doch mal mit ihnen zu teilen, und um meinen Frust los zu werden, hier also ein Artikel zum Thema Richtext Editing. Zu beachten ist, dass hier einige nicht-standard JavaScript Funktionen genutzt werden. Dies sind Funktionen des <a href="http://www.prototypejs.org">PrototypeJS</a>-Frameworks.</p>
<h2>Der Anfang&#8230;</h2>
<p>&#8230; schien gar nicht so schlimm! Denn als ich schon überlegte, wie ich die einzelnen Textbausteine am besten mit Tags umschließe, fand ich <a href="https://developer.mozilla.org/en/midas">das hier</a>. Kommandos, die man direkt auf dem <code>document</code> Objekt ausführen kann. Diese Art Kommandos wurden einst von Microsoft in <code>DHTML</code> eingebunden, dann von der Mozilla Gruppe adaptiert und stehen nun in eigentlich allen großen Browsern zur Verfügung. Man braucht nur einen <code>iframe</code>, der den Editor beherbergen soll und in diesem setzt man den sogenannten <code>designMode</code> auf <code>On</code>. Um die ganze Sache auch im IE laufen lassen zu können muss das <code>contentEditable</code> Attribute des <code>body</code> Tags auf <code>true</code> gesetzt werden. Beispiel gefällig?</p>
<pre class="brush: js; html-script: true">
<scirpt language="text/javascript">
    // no IE!
    document.observe("dom:loaded", function() {
        document.designMode = "On";
    });
</script>
</pre>
<p>Soweit, so gut. Allerdings wurden nicht alle Befehle von allen Browserherstellen übernommen oder implementiert. Am schlimmsten erweist sich hierbei übrigens Opera. Bemerken tut man dies, wenn man versucht, mit dem <code>selection</code> Objekt zu arbeiten. Opera schmückt sich ja gern damit, mit den neuesten Standards konform zu sein. Wie sie das erreichen ist allerdings eine andere Sache. Im Falle vom Selektionsobjekt wird dies oft durch einfaches <b>stubben</b> von Methoden erreicht. Da diese Methoden dann keine Fehler werfen, sondern einfach <strong>nichts</strong> tun, erweist sich das debuggen als große Qual. Außerdem sind die Methoden, die tatsächlich Auswirkungen zeigen teils fehlerhaft implementiert. So funktionieren im Opera viele Funktionen nur, wenn man von rechts nach links selektiert, und nicht in der anderen Richtung. Aber zurück zum Thema.</p>
<p>Ein wohl sehr wichtiger Befehl ist <code>heading</code> für Überschriften. Der Befehl erhält als zweiten Parameter lediglich &laquo;<code>h[1,2,3,4,5]</code>&raquo; um die gewünschte Headline zu erzeugen. Leider zeigt er zur Zeit nur im Firefox seine Wirkung. Opera ignoriert ihn einfach und Chrome zeigt unerwünschtes Verhalten. Kurz nachdem man mit der Implementierung eines Befehls, wie <code>heading</code> begonnen hat, wird einem klar, dass die eigentliche Funktionalität leicht zu erreichen ist, wenn da nicht die Benutzerfreundlichkeit wäre. Ein Benutzer erwartet im Allgemeinen, dass der komplette Paragraph in eine Überschrift gewandelt wird, wenn auch nur ein Bruchteil davon selektiert wurde. Die generelle Idee ist es also, von den Selektionsgrenzen aus nach links und rechts nach begrenzenden Elementen zu suchen. Allgemein würde man hier von einem <code>br</code> tag ausgehen, das einen Zeilenumbruch, und damit einen Abschnitt begrenzt. Leider kommt uns hier wiederum die unterschiedliche Implementierung der Browser in die Quere. Drückt man währen der Eingabe <code>Enter</code> so fügt Firefox <code>br</code> ein, wie man es erwarten würde. Chrome hingegen nutzt <code>div</code> tags, um Abschnitte abzugrenzen und Opera fügt <code>p</code> Elemente ein. </p>
<h2>Die richtigen Elemente finden</h2>
<p>Mit der Idee im Kopf, dass wir einfach nur nach links und nach rechts suchen müssen, bis wir entweder ein begrenzendes Tag finden oder der nächste Kindknoten <code>undefined</code> ist, fangen wir also an zu programmieren. Aber schon nach kurzer Zeit wird uns klar, dass das nicht so einfach ist. Das Selektionsobjekt gibt nämlich nur Auskunft über den Knoten, in dem es anfängt und aufhört. Da wir Text selektieren wird das meist ein Textknoten sein. Und der kann in <code>b</code>, <code>i</code> und weitere Tags geschachtelt sein. Wir müssen also sehen, dass wir zwei Eltern von den beiden finden, die auf einer Ebene im DOM-Baum liegen und von diesen dann nach links und rechts suchen. </p>
<p>Die Tiefe eines Knotens im DOM-Baum lässt sich rekursiv recht einfach durch folgende Funktion ermitteln.</p>
<pre class="brush: js">
function getDepth(node, count) {
    if(node.parentNode) {
        return getDepth(node.parentNode, (count || 0) + 1);
    }

    return count || 0;
}
</pre>
<p>Von hier an denken wir weiter. Wie muss unsere Suche aussehen? Wenn der Startknoten tiefer im Baum liegt, als der Endknoten, müssen wir unsere Suche mit dem Elternknoten von Start und dem gleichen Endknoten fortsetzen. Genau andersrum ist es, wenn der Endknoten tiefer im Baum liegt. Was aber passiert, wenn beide Knoten die gleiche Tiefe haben? Die Annahme, dass dann alles in Ordnung ist und wir einfach mit der Links-Rechts-Suche fortfahren könnten, ist leider falsch. Es könnte folgende Situation aufgetreten sein:</p>
<pre class="brush: js; html-script: true">
Lorem ipsum <b><i>Start</i> Text</b> dolor sit <b><i>End</i> Text</b> amet.
</pre>
<p>Oder, um die Baumstruktur ein bisschen besser hervorzuheben:</p>
<pre class="brush: js; html-script: true; highlight: [4,11]">
Lorem ipsum
    <b>
        <i>
            Start
        </i>
        Text
    </b>
dolor sit
    <b>
        <i>
            End
        </i>
        Text
    </b>
amet.
</pre>
<p>Wie man sieht, liegen der Start- und Endtext in einer Ebene, aber die Suche nach links und rechts wäre sinnlos. Hier müssen wir den Algorithmus also auf die Elternknoten, der beiden anwenden. </p>
<p>Die folgende Funktion sucht diejenigen Knoten, die auf einer Ebene im Baum liegen und auch Geschwister sind. Dabei wird ein Objekt zurückgegeben, dass unter den Schlüsseln <code>left</code> und <code>right</code> den linken und rechten Knoten bereithält. Dies ist lediglich eine Hilfe für die spätere Suche nach den Grenzen des Abschnitts. Bitte beachtet auch, dass hier <a href="http://prototypejs.org">Prototype</a> spezifische Funktionen, wie <code>$A(...)</code> verwendet werden.</p>
<pre class="brush: js; highlight: [15, 19, 31]">
function findHighestSiblings(start, end, depthStart, depthEnd) {           

    // first we need to check which node is deeper in the tree
    if(!depthStart) {
        depthStart = getDepth(start);
    }

    if(!depthEnd) {
        depthEnd = getDepth(end);
    }

    if(depthStart > depthEnd &#038;&#038; start.parentNode) {
        // start is deeper in the tree
        // -> we check whether the parent of start has the same depth as end
        return findHighestSiblings(start.parentNode, end, undefined, depthEnd);
    } else if (depthStart < depthEnd &#038;&#038; end.parentNode) {
        // end is deeper in the tree
        // -> we check whether the parent of end has the same depth as start
        return findHighestSiblings(start, end.parentNode, depthStart);
    } else {
        // both nodes have the same depth but are wrapped in containers
        if(start.parentNode &#038;&#038; end.parentNode &#038;&#038;
           start.parentNode !== end.parentNode) {               

            var gpStart = start.parentNode.parentNode;
            var children = $A(gpStart.childNodes);

            if(gpStart) {
                if(children.indexOf(end.parentNode) === -1) {
                    // the wrapping can be multilevel
                    return findHighestSiblings(start.parentNode, end.parentNode, depthStart, depthEnd);
                }                   

                // now we have the same parent and determine which of the elements
                // is the left and which is the right one
                // we need this for the previousSibling-/ nextSibling-search
                if(children.indexOf(start.parentNode) < children.indexOf(end.parentNode)) {
                    return {
                        left: start.parentNode,
                        right: end.parentNode
                    };
                } else {
                    return {
                        left: end.parentNode,
                        right: start.parentNode
                    };
                }
            }
        }

        var sParent = start.parentNode;
        var children = $A(sParent.childNodes);

        if(children.indexOf(start) < children.indexOf(end)) {
            return {
                left: start,
                right: end
            };
        } else {
            return {
                left: end,
                right: start
            };
        }
    }
}
</pre>
<p>Nachdem die begrenzenden Elemente gefunden wurden, kann diese Selektion mit einem <code>h</code> Tag umschlossen werden. Die Überschrift wurde also erzeugt. </p>
<h2>Das Selektionsobjekt</h2>
<p>Ist der Dreh- und Angelpunkt beim arbeiten mit Text. Generell läuft man an jeder Stelle in das Objekt, an dem man Text extrahieren, hinzufügen oder bearbeiten möchte. Abfragen kann man das Objekt wie folgt:</p>
<pre class="brush: js">
function getSelection() {
    // Mozilla
    if (window.getSelection) {
        return window.getSelection();
    }

    // Safari + Chrome
    if (document.getSelection) {
        return document.getSelection();
    } 

    // IE
    if (document.selection) {
        return document.selection.createRange().text;
    }
}
</pre>
<p>Die Safari/ Chrome-Abzweigung dient allerdings nur für ältere Versionen dieser Browser. Die neusten Versionen folgen alle dem Mozilla Beispiel. </p>
<p>Falsch ist die Annahme, dass die sichtbare Selektion gleich dem Selektionsobjekt ist. Was man sieht sind die sogenannten <code>Ranges</code> des Objektes. Ein Selektionsobjekt kann im allgemeinen Fall beliebig viele <code>Ranges</code> beherrbergen. Dies entspricht dann einer Mehrfachselektion mittels gedrückter <code>STRG</code>-Taste. Allerdings will man sich in den meisten Fällen nur mit der ersten <code>Range</code> beschäftigen. Das Objekt gibt auch Auskunft über die Elemente in denen es beginnt und endet. Wenn man zum Beispiel aus einer Selektion einen Link erstellen möchte, kann man dies wie folgt tun:</p>
<pre class="brush: js">
function createLink() {
    var selection = getSelection();
    var range = selection.getRangeAt(0);
    var link = undefined;

    link = findLink(range.startContainer, range.endContainer);

    if(link) {
        var href = Element.readAttribute(link, 'href');
        var url = prompt("Edit the following link:", href);                

        if(url &#038;&#038; url !== 'http:/'+'/') {
            link.setAttribute('href', url);
        }
    } else {
        var url = prompt("Set link to:", "http://");

        if(url &#038;&#038; url !== 'http:/'+'/') {
            document.execCmd('createlink', url);
        }
    }
}
</pre>
<p>Diese Funktion sucht als erstes in der Selektion nach bereits bestehenden Links. Existiert bereits ein Link, so wird dessen Wert übernommen und zum Editieren angeboten. Wie man sieht ist das eigentliche Erstellen des Links nicht durch mühsehlige, selbstimplementierte Funktionen realisiert, sondern durch das einfache Ausführen des <code>createlink</code> Kommandos auf dem <code>document</code> Objekt.</p>
<h3>Unterschiede beim Erstellen von Selektionen</h3>
<p>Wenn man per Hand Selektionen erstellen möchte, um dann beispielsweise Kommandos auf ihnen auszuführen, so muss man dabei eine gewisse Vorsicht mit an den Tag bringen. </p>
<p>Das <code>Range</code> Objekt hat von Haus aus die sehr nützlichen Funktionen <code>setStart</code>, <code>setStartBefore</code>, <code>setEnd</code> und <code>setEndAfter</code>. Leider verhalten sich diese Funktionen nicht in allen Browsern gleich. Nehmen wir folgendes Beispiel:</p>
<pre class="brush: js; html-script: true">
Lorem <i>ipsum</i> <b>dolor</b> <i>sit</i> amet.
</pre>
<p>Wenn wir nun eine Selektion um das <code>dolor</code> setzen möchten, würde man (oder ich) intuitiv annehmen, dass wir den Start der Selektion <strong>vor</strong> das <code>b</code> Element setzen und das Ende <strong>hinter</strong> das Element. Anscheinend waren die Entwickler von Mozilla und Chroma auch meiner Meinung. Allerdings sehen die Jungs bei Opera die Sache etwas anders. Wenn wir im Opera die Selektion vor das <code>b</code> Element setzen, so umschließen wir auch das komplette <code>i</code> Element damit. Gleich verhält es sich für das Ende der Selektion. Was nun also tun? Zum Glück ist das Verhalten der Browser für die <code>setStart</code> und <code>setEnd</code> Methoden gleich. Diese nehmen zwei Parameter. Der erste gibt das Element an, in dem die Selektion liegen soll und einen <code>offset</code> wert.</p>
<pre class="brush: js; highlight: [11,13]">
function selectBetween(l, r) {
    var selection = getSelection();
    var range = selection.getRangeAt(0);

    if(l === r) {
        range.selectNodeContents(l);
    } else {
        range.setStart(l, 0);

        if(this.isTextNode(r)) {
            range.setEnd(r, r.textContent.length);
        } else {
            range.setEnd(r, $A(r.childNodes).length);
        }
    }
}
</pre>
<p>So, und wem fällt beim Ansehen des Codes die nächste Tücke auf? Nagut, ist nicht so schwer, da ich die Zeilen schon markiert habe. Man hätte annehmen können, beim <code>offset</code> handelt es sich um ein Zeichen-Offset. Das stimmt auch, so lange es sich bei dem gewählten Knoten um einen Textknoten handelt. Ist dies aber nicht mehr der Fall, so beschreibt das Offset die Position des Knotens in der <code>childNodes</code> Liste des Elternelements. Und dann bedeuted es, dass auch das komplette Element mit eingeschlossen wird.</p>
<p>Das soll es für's Erste gewesen sein. Ich habe zwar nicht alle Schwachstellen und Lücken beleuchtet aber auf die gröbsten und fiesesten Stellen aufmerksam gemacht. Hoffentlich ist das hier jedem, der es liest eine Hilfe. An einer Übersetzung ins Englische arbeite ich übrigens noch.</p>
]]></content:encoded>
			<wfw:commentRss>http://the-pixelpla.net/blog/2010/10/richtext-editing/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Oktoberfest die Zweite</title>
		<link>http://the-pixelpla.net/blog/2009/09/oktoberfest-die-zweite/</link>
		<comments>http://the-pixelpla.net/blog/2009/09/oktoberfest-die-zweite/#comments</comments>
		<pubDate>Thu, 17 Sep 2009 19:28:20 +0000</pubDate>
		<dc:creator>Phil</dc:creator>
				<category><![CDATA[Ausflüge]]></category>
		<category><![CDATA[bier]]></category>
		<category><![CDATA[feiern]]></category>
		<category><![CDATA[signavio]]></category>

		<guid isPermaLink="false">http://the-pixelpla.net/blog/?p=212</guid>
		<description><![CDATA[Und wieder einmal gings aufs Oktoberfest. Allerdings diesmal mit glücklichem Ausgang des Abends. Ich bin zwar wieder im Zug eingeschlafen aber ich habe aus meinen Fehlern gelernt und mir vorher mein Handy gestellt. Taxi fahren konnte ich aber trotzdem noch. Nämlich vom Potsdamer Hauptbahnhof, bis zu mir nach Hause. Mit 9€ war das allerdings deutlich [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://the-pixelpla.net/blog/wp-content/uploads/2009/09/OktoberfestAmRotenRathausPlakat-725x1024.gif" rel="lightbox[212]"><img src="http://the-pixelpla.net/blog/wp-content/uploads/2009/09/OktoberfestAmRotenRathausPlakat-212x300.gif" alt="Oktoberfest am Roten Rathaus" title="Oktoberfest am Roten Rathaus" width="212" height="300" class="alignleft size-medium wp-image-213" /></a>Und wieder einmal gings aufs Oktoberfest. Allerdings diesmal mit glücklichem Ausgang des Abends. Ich bin zwar wieder im Zug eingeschlafen aber ich habe aus meinen Fehlern gelernt und mir vorher mein Handy gestellt. Taxi fahren konnte ich aber trotzdem noch. Nämlich vom Potsdamer Hauptbahnhof, bis zu mir nach Hause. Mit 9€ war das allerdings deutlich günstiger, als die letzte Tour! </p>
<p>Der Abend hat aber noch eine weitere Erkenntnis gebracht. Wenn man betrunken Versucht Matheaufgaben zu lösen und der Meinung ist, man wäre Mental noch fit, um das ganze zwischen die Artikel einer Zeitung zu bringen, irrt man sich gewaltig. Ich weiß ja nicht, was ich da Gestern in die Zeitung gemalt hab, aber mit Mathematik hatte das nicht mehr viel zu tun.</p>
]]></content:encoded>
			<wfw:commentRss>http://the-pixelpla.net/blog/2009/09/oktoberfest-die-zweite/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

