Mit ‘Javascript’ getaggte Artikel

Erstellen eines Plugins für den CKEditor 3.0

Dienstag, 27. Oktober 2009

Im Folgenden soll anhand eines kleinen Beispiels gezeigt werden, wie man ein Plugin für den CKEditor schreibt und dieses einbindet. Es soll ein Button in die Toolbar aufgenommen werden. Ein Klick auf diesen Button soll einen Dialog öffnen. In diesem Dialog wird man einen Text eingeben und eine Schriftfarbe für diesen Text auswählen können. Beim Klick auf den Ok-Button des Dialogs, wird dann der entsprechende Text in der gewählten Schriftfarbe in den Editierbereich eingefügt.

Zunächst benötigt man eine eigene Konfigurationsdatei “config.js”, um die Toolbar anzupassen.

CKEDITOR.editorConfig = function( config )
{	
	CKEDITOR.plugins.addExternal( 'insertText', CKEDITOR.basePath + '../ckeditor_customized/plugins/InsertText/' );	
 
	config.extraPlugins = "insertText";	
 
	CKEDITOR.config.toolbar = 'MyToolbar';
 
        config.toolbar_MyToolbar =
          [
            ['NewPage','insertText'],
          ];    
};

MyToolbar enthält lediglich 2 Buttons. Zum einen enthält sie den NewPage-Button, hinter dem sich ein bereits mit dem CKEditor-Source mitgeliefertes Plugin versteckt. Und zum anderen enthält sie den neuen Button. Für diesen legen wir das Verzeichnis “x/ckeditor_customized/plugins/InsertText/” an, wobei x für das Verzeichnis steht, in dem sich auch das CKEditor-Verzeichnis befindet. In das angelegte Verzeichnis fügen wir nun die Datei “plugin.js” ein, die folgenden Code enthält:

CKEDITOR.plugins.add('insertText', {
	init : function( editor )
	{
		editor.addCommand( 'insertTextDlg', new CKEDITOR.dialogCommand( 'insertTextDlg' ) );
 
		editor.ui.addButton( 'insertText',
			{
				label : 'Text einfügen',
				command : 'insertTextDlg',
				icon: <icon path>
			});
 
		CKEDITOR.dialog.add( 'insertTextDlg', CKEDITOR.basePath + '../ckeditor_customized/dialogs/insertTextDlg.js');
 
	}
});

Zunächst fügen wir in dem Plugin der Editor-Instanz ein neues Dialog-Kommando namens insertTextDlg hinzu, welches bei Klick auf den neuen Button ausgeführt werden soll. Anschließend registrieren wir den neuen Dialog, der sich beim Button-Klick öffnen soll. Der Dialog wird in der Datei “insertTextDlg.js” definiert.

CKEDITOR.dialog.add( 'insertTextDlg', function( editor ) {
	return { 
		title: 'Text einfügen',
 
		minWidth: 200,
		minHeight: 80,
 
 
		contents: [ 
			{
				id: 'tab1',
				label: 'Tab1',
				title: 'Tab1',
				elements : [ 
					{
						id: 'mytext',
						type: 'text',
						label: "Text",
						validate : function() {
							// potentielle Validierungen
							if (this.getValue() == "") {
								alert("Das Feld darf nicht leer sein!");
							}
							return this.getValue() != "";
						}
					},
					{
						id: 'mycolor',
						type: 'select',
						label: "Farbe",
						items: [
						        ['rot'],
						        ['grün'],
						        ['blau']
						       ],
						validate : function() {
							// Validierungen
							return true;
						}
					},
				 ]
			}
		 ],
 
 
		 onOk: function() { 
			var color = this.getContentElement('tab1', 'mycolor').getValue();
			var numColor;
			if (color == 'rot') numColor = '#F00';
			else if (color == 'grün') numColor = '#0F0';
			else if (color == 'blau') numColor = '#00F';
 
			var element = CKEDITOR.dom.element.createFromHtml(
					'<span style="color:' + numColor + ';">' + 
						this.getContentElement('tab1', 'mytext').getValue() + 
					'</span>');
			editor.insertElement(element);
		 }
 
	};
 
} );

Bei einem Klick auf Ok, werden die selektierten und eingegebenen Werte ermittelt. Nun kann der eingegebene Text in der gewünschten Farbe eingefügt werden.


Christian Borkowski


Single und Double-Clicks auf einem HTML-Element mit Prototype

Freitag, 24. Juli 2009

Möchte man auf einem HTML-Element (bzw. DOM-Element) einen einfachen und einen Doppel-Klick registrieren und
jeweils unterschiedliche Aktionen ausführen, steht man schnell vor einem Problem. Es gibt zwar ‘onclick’ und ‘ondblclick’ jedoch
löst ein Doppel-Klick immer auch einen einfachen Klick mit aus. Die Lösung besteht darin, nach einem einfachen Klick ein definierte Zeit zu warten und erst, wenn in dieser Zeit kein Doppel-Klick Event aufgetreten ist, die Aktion für den einfachen Klick wirklich auszuführen.
Tritt in dieser Zeit jedoch ein Doppel-Klick auf, wird die Aktion nicht durchgeführt und stattdessen die Aktion des Doppel-Klicks prozessiert. Mit Hilfe des beliebten Javascript Frameworks Prototype kann man dafür leicht eine generische Lösung entwickeln, die diese beiden Events für ein Element oder mehrere registriert:

 
var FunkyTools = {	
/* Registriert einen einfachen klick und einen Doppel-Klick auf den selben Elementen.
 * Stellt sicher, dass bei einem Doppel-Klick nicht auch der Event-Handler des einfachen
* Klicks getriggert wird (gilt nur für den Event-Handler der über diese Methode 
* für den einfachen Klick registriert wurde).  
* 
* Parameter:
* 
* element         = ein DOM-Element, ein String(id des Elementes) oder ein Array 
*                       mit DOM-Elementen bzw. Strings(id der Elemente). 
* singleClickFunc = der Even-Handler für einfache Klicks
* dblClickFunc    = der Even-Handler für doppelte Klicks         
* 
* */
registerClicks: function(element, singleClickFunc, dblClickFunc) {
       var elements = $(element);
 
	if (! Object.isArray(elements)) {
		elements = [element];
	}
 
	elements.each(function(element){
		var doubleClick = false;
 
		element.observe('click', function(event){
			doubleClick = false;
			singleClickFunc.wrap(function(proceed, event){
					if (doubleClick) {
						event.stop();
					} else {
						proceed(event);
					}
				}).delay(0.3, event);			
			});		
 
			element.observe('dblclick', dblClickFunc.wrap(function(proceed, event){
				doubleClick = true;
				proceed(event);
			}));				
		});			
	}
}

Christian Schätzlein


Javascript Pitfalls und mehr

Freitag, 15. Mai 2009

Es gibt beim schreiben von javascript so einiges zu beachten, wenn man zuerst für den Firefox entwickelt und anschließend sein javascript im IE bewundern möchte.

Zum Beispiel sollten Kommata vorsichtig gesetzt werden…

var Utils = {
 
	bindFocusSetter: function() {
		$$("input", "textarea", "select").each(
		    function(input) {
		    	this.bindFocusElement(input);
		    }.bind(this)
		);
	},
 
	bindFocusElement: function(input) {
		...
	},
 
	disableDefaultDrag: function(event) {
		...
	},
 }

Während der Firefox dies akzeptiert und die functions problemlos ausführt..
..schaut es im IE anders aus, das Komma hinter der letzten function läßt den IE stutzig werden.

Auch sollte man sich bei der Verwendung von Prototype einiges von Prototype selbst abschaun, z.B. das “prototypifizieren” von Elementen, wie im folgenden Code Block dargestellt.

/**
 * Fuer die Validierung von Inputfeldern.
 */
var Validate = {
	validate: function(regEx,field,message){
		var feld = $(field);
		if(feld){
			var feldlabel;
			if(feld.previous(0)){
...

So ist man mit “var feld = $(field);” auf der sicheren Seite ein Element an der Hand zu haben, auf dem gefahrlos Prototype Funktionen aufgerufen werden können.

Das hereinreichen von Strings per el in eine javascript Funktion kann auch daneben gehen, wenn z.B. Zeilenumbrüche vorhanden sind.

<tr class="active'"
    onclick="showZusatzInfo('${xyItem.inhalt}');"
    style="cursor: pointer;">

Ein Zeilenumbruch “\n” führt hier zu einem Fehler, beim auslösen des onclick Event-Handlers. Abhilfe schafft hier eine einfache JSTL-Function.
Der modifizierte onclick Event-Handler…

<tr class="active''}"
    onclick="showZusatzInfo('${xyItem.inhalt}');"
    style="cursor: pointer;">

Die simple Java-Klasse…

public class Functions {
 
    private Functions() {
    }
 
    public static String unwrap(final String input) {
        String result = null;
 
        if (input != null) {
            result = input.replaceAll("\\r\\n", "\\\\n");
            result = result.replaceAll("\\n\\r", "\\\\n");
            result = result.replaceAll("\\r", "\\\\n");
            result = result.replaceAll("\\n", "\\\\n");
        }
 
        return result;
    }
}

Und noch der TLD-Eintrag…

<function>
    <name>unwrap</name>
    <function-class>de.standard.Functions</function-class>
    <function-signature>
        java.lang.String unwrap(java.lang.String)
    </function-signature>
</function>

Bis zum nächsten Blog-Eintrag…
Sven Seiller


Sven Seiler


Scriptaculous Autocompleter Scroll Bugs

Dienstag, 09. Dezember 2008

Prototype und Scriptaculous sind in Kombination eine der beliebtesten Javascript-Bibliotheken, die bei Web-Anwendungen zum Einsatz kommen, um auf dem Client auf einfache Weise und trotzdem Cross-Browser fähig u.a. Effekte darzustellen, mit XHTML Requests zu arbeiten oder DOM-Manipulationen vorzunehemen.

Doch vor allem die Scriptacoulous-Bibliothek ist an einigen Stellen noch nicht ausgereift. Ein Beispiel dafür ist der Autocompleter, der Vorschläge für ein Textfeld anhand des bereits eingegeben Textes in einer Liste anzeigt. Eigentlich sollte das Fenster, wenn sich die Liste am unteren Rand befindet (Dokument ist länger als der Viewport) und das nächste Element nicht mehr im sichtbaren Bereich liegt, nach unten scrollen.

Da Browser im (Quirks-Mode) von Prototype aber grundsätzlich nicht unterstützt werden, funktioniert die Berechnung, wann gescrollt werden muss in diesem Fall nicht. Darüberhinaus exisitieren weitere bekannte Probleme mit dem Scrollen unabhängig vom verwendeten Browser-Mode (siehe Ticket für das Autocompleter Scroll Problem). Die in diesem Zusammenhang existierenden Patches sind jedoch auf den Strict-Mode zugeschnitten, funktionieren nicht zuverlässig für alle Browser und bieten zudem keine Lösung für einen weiteren Bug. Dieser ist dabei eigentlich mehr eine Verkettung von unglücklichen Umständen: Er tritt auf, wenn sich das Textfeld am unteren Rand des Fensters befindet und der Anwender mit der Maus hinein klickt, um – nach einer Texteingabe – aus der erscheinenden Liste eine Eintrag zu selektieren (über die Pfeiltasten). Läuft alles korrekt, wird das Fenster nach unten gescrollt und der nächste Eintrag selektiert. Da sich die Maus aber weiterhin an der gleichen Position (relativ zum Fenster) befindet, löst sie einen Mouseover-Event über dem zuvor selektierten Eintrag aus. Das Resultat dieses Verhaltens ist, dass die Liste beim Selektieren scheinbar hin und her springt bzw. das Selektieren teilweise gänzlich unmöglich ist.

Um für ein konkretes Projekt, dass mit Browsern im Quirks-Modus arbeitet, diese Probleme zu beseitigen wurde folgender Patch entwickelt. Er wurde getestet für Protototype Version 1.6 und Scriptaculous 1.8.1 und liegt als Monkey-Patch vor. Für Browser im Strict-Modus muss lediglich die Berechnung der Höhe des Viewport in der Funktion scrollIntoViewDown entsprechend des Kommentares geändert werden.

Der Patch berechnet die Höhe des Viewports korrekt und scrolled dementsprechend nur dann nach oben oder nach unten. Etwaige ungewollte Maus-Events nach einem nötigen – durch den Autocompleter hervorgerufenen – Scrollen werden einfach ignoriert.

Object.extend(Autocompleter.Base.prototype, {
	baseInitialize : function(element, update, options) {
	this.superBaseInitialize(element, update, options);
	this.ignoreHoverEvent = false;
},
superBaseInitialize :Autocompleter.Base.prototype.baseInitialize,
scrollIntoViewUp : function() {
	var entry = this.getEntry(this.index);
	if (entry.viewportOffset().top < 0) {
		// only scroll if entry out of bounds
		entry.scrollIntoView(true);
		// ignore next mouse event to avoid marking of entry
		// after scrolling if mouse is over list
		this.ignoreHoverEvent = true;
		// make sure following mouse events are not ignored if mouse is
		// not over list
		setTimeout(this.resetIgnoreHover, 500);
	}
},
scrollIntoViewDown : function() {
	var entry = this.getEntry(this.index);
//	get viewport height (most browsers, ie + mozilla in quirks mode)
	// (for strict mode use document.viewport.getHeight()
	var windowHeight = window.innerHeight || document.body.clientHeight;
	// get y value of selected entry and add entry height to get lower bounds
	// y value
	var currentHeight = entry.viewportOffset().top + entry.getHeight();
	if (windowHeight - currentHeight < 0) {
		// only scroll if entry out of bounds
		this.getEntry(this.index).scrollIntoView(false);
		// ignore next mouse event to avoid marking of entry
		// after scrolling if mouse is over list
		this.ignoreHoverEvent = true;
		// make sure following mouse events are not ignored if mouse is
		// not over list
		setTimeout(this.resetIgnoreHover, 500);
	}
},
markPrevious : function() {
	if (this.index > 0) {
		this.index--
		// scroll up if list is stepped up
		this.scrollIntoViewUp();
	} else {
		this.index = this.entryCount - 1;
		// scroll down on jump to last entry
		this.scrollIntoViewDown();
	}
},
markNext : function() {
	if (this.index < this.entryCount - 1) {
		this.index++
		// scroll down if list is stepped down
		this.scrollIntoViewDown();
	} else {
		this.index = 0;
		// scroll up on jump to first entry
		this.scrollIntoViewUp();
	}
},
resetIgnoreHover : function() {
	this.ignoreHoverEvent = false;
},
onHover : function(event) {
	if (!this.ignoreHoverEvent) {
		var element = Event.findElement(event, 'LI');
		if (this.index != element.autocompleteIndex) {
			this.index = element.autocompleteIndex;
			this.render();
		}
	} else {
		this.ignoreHoverEvent = false;
	}
	Event.stop(event);
}
});

Christian Schätzlein