Mit ‘Javascript’ getaggte Artikel

Integration von Ext JS (AJAX) in eine bestehende Webanwendung

Montag, 02. November 2009

Das man mit JavaScript und AJAX nicht nur tolle, sondern auch wirklich nützliche Funktionalität in einer Webanwendung unterbringen kann, ist nicht von der Hand zu weisen. Eingabefelder, die schon beim Eintippen mögliche Treffer anzeigen, sind nur ein Beispiel und auf fast allen bekannten Webseiten zu finden.
Solche Funktionalität lässt sich auch in bestehenden Anwendungen nachrüsten, ohne das komplette Projekt umzukrempeln. Die folgenden Beispiele stammen aus der Erweiterung einer Struts 1 Anwendung, in die wir nachträglich Ext JS (Version 2.2) integriert hatten, um einige der Widgets von Ext JS zu nutzen.

1. Ext JS zum Projekt hinzufügen
2. Einbinden von Ext JS in die JSP’s
3. AJAX Request mit Ext JS (Beispiel)
4. Response (HTML oder JSON)

Anmerkung: Expression Language (EL) muss vom Server unterstützt werden.

1. Ext JS zum Projekt hinzufügen

Das Archiv, das man von der Ext JS Seite herunterladen kann, enthält neben den eigentlichen Bibliotheken auch zahlreiche Beispiele und Sourcen, die von der Anwendung nicht benötigt werden. Folgende Dateien* und Verzeichnisse* kopiert man in sein Projekt z.B. in das Unterverzeichnis WebContent/js/ext:
- ext-all.js
- ext-core.js
- /adapter
- /build
- /resources

* Ext JS Version 2.2 oder 2.3 ohne Debug

Man kann auch die oben erwähnten Dateien und Verzeichnisse in ein ZIP packen, z.B. ext-2.2.zip und dieses beim Build der Anwendung durch Ant enstspr. auspacken und zum WAR hinzufügen lassen. Damit ist einigermaßen sichergestellt, dass die Ext-Dateien nicht editiert werden und zudem hat man nur eine Datei auf dem SVN bzw. CVS liegen (erleichtert auch den Wechsel auf eine andere Ext-Version). Das ist aber absolut optional.

2. Einbinden von Ext JS in die JSP’s

Um Ext verfügbar zu machen und den Standardadapter zu nutzen sind folgende Zeilen im HTML-Head nötig:

<script type="text/javascript" 
src="${pageContext.request.contextPath}/js/ext/ext-all.js"></script>
<script type="text/javascript" 
src="${pageContext.request.contextPath}/js/ext/adapter/ext/ext-base.js"></script>

Will man einige der Widgets von Ext nutzen, ist zusätzlich das CSS von Ext einzubinden:

<link rel="stylesheet" type="text/css" 
href="${pageContext.request.contextPath}/js/ext/resources/css/ext-all.css" />

und ggf. ein Theme auszuwählen, wenn man nicht das Standard-Blau verwenden möchte:

<link rel="stylesheet" type="text/css" id="theme" 
href="${pageContext.request.contextPath}/js/ext/resources/css/xtheme-gray.css" />

Eine Besonderheit: Ich weiß nicht, ob die Ext Entwickler das mit Absicht gemacht haben und welchen Zweck es haben soll, aber Ext “funkt” nach Hause. An einigen Stellen wird ein 1×1 Pixel Image von der Ext-Seite abgerufen. Will man das umgehen und stattdessen das Image lokal laden, sollte man diese Zeilen im Header unterbringen:

<script type="text/javascript">
// url for empty image otherwise http://extjs.com will be called
Ext.BLANK_IMAGE_URL = '${pageContext.request.contextPath}/js/ext/resources/images/default/s.gif';
</script>

3. AJAX Request mit Ext JS (Beispiel)

Nun werden wir einen Ajax Request ausführen um z.B. den Wert eines Formularfelds an den Server zu übermitteln, ohne die komplette Seite neu laden zu müssen (also kein Form.submit()).
Die Funktion würde wie folgt aussehen:

function sendValue(value) {
  Ext.Ajax.request({
    url: '${pageContext.request.contextPath}/sendValue.do',
    params: { newValue: value },
    success: function(result, response) {
      // in diesem Fall soll nichts gemacht werden	
    },
    failure: function(result, request) {
      Ext.Msg.alert('Fehler', 'Request konnte nicht ausgeführt werden');
    }
  });
}

Serverseitig rufen wir hiermit eine entspr. gemappte StrutsAction auf.
Auszug aus der struts-config.xml:

<action path="/sendValue"
  type="de.baderundjene.example.ValueChangeAction">
</action>

Ein Form kann man sich sogar sparen und den Parameter direkt auslesen:

public final class ValueChangeAction extends Action {
  @Override
  public final ActionForward execute(....) {
    final String newValue = request.getParameter("newValue");
    // irgendwas mit dem newValue machen
 
    return null;
  }
}

Am Ende geben wir null zurück, es soll ja kein ActionForward erfolgen und der Response soll leer sein.

4. Response (HTML oder JSON)

Möchten wir aber einen Wert, Werte oder ein HTML-Fragment zurückgeben, muss dieses als String in den Response geschrieben werden.
Um Daten an den Client (Browser) zu schicken, die dort per JavaScript verarbeitet werden sollen um z.B. die Werte einer Select-Box zu verändern, eignet sich JSON (JavaScript Object Notation) besonders gut. Details zu JSON findet man ganz leicht im Internet. Bevor wir also in unserer Action null zurückgeben, schreiben wir unseren JSON-String in den Response:

// ein Attribut data mit dem Wert Test
final String json = "({data:'Test'})";
// HttpServletResponse response;
// hier können wir auch das Encoding auf z.B. UTF-8 stellen
response.setContentType("text/json; charset=UTF-8");
final PrintWriter out = response.getWriter();
out.println(json);
out.flush();

Im JavaScript erweitern wir unsere success Methode:

function sendValue(value) {
  Ext.Ajax.request({
    url: '${pageContext.request.contextPath}/sendValue.do',
    params: { newValue: value },
    success: function(result, response) {
      var responseObj = Ext.decode(result.responseText);
      Ext.Msg.alert('Erfolg', responseObj.data);
    },
    failure: function(result, request) {
      Ext.Msg.alert('Fehler', 'Request konnte nicht ausgeführt werden');
    }
  });
}

Möchte man ein HTML-Fragment zurückgeben, um z.B. einen Teil der Seite neu zu laden, kann man in der Action einen ActionForward auf eine entspr. JSP durchführen. Diese JSP enthält dann eben keine ganze HTML-Seite sondern nur den Teil, den wir neu laden möchten, z.B. eine Tabelle mit den Kundendaten.
D.h. struts-config.xml anpassen:

<action path="/sendValue"
  type="de.baderundjene.example.ValueChangeAction">
  <forward name="success" path="inc/customerTable.jsp"/>
</action>

und statt null den ActionForward mapping.findForward(”success”); zurückgeben.
Unsere angepasste JavaScript Methode:

function sendValue(value) {
  Ext.Ajax.request({
    url: '${pageContext.request.contextPath}/sendValue.do',
    params: { newValue: value },
    success: function(result, response) {
      // <div id="customer-data">&nbsp;</div>
      var customerDiv = Ext.get('customer-data');
      customerDiv.update(response.responseText, true);
    },
    failure: function(result, request) {
      Ext.Msg.alert('Fehler', 'Request konnte nicht ausgeführt werden');
    }
  });
}

Fazit

Ein Projekt kann sehr leicht mit Ajax erweitert werden ohne bestehende Funktionalität anzufassen. Welches MVC-Framework hier zum Einsatz kommt, spielt eigentlich keine Rolle. Modernere Frameworks bieten allerdings teilweise out-of-the-box Unterstützung für Ajax und JSON an.
Bei unserem Projekt haben wir neue Erweiterungen direkt mit Ext JS implementiert, möglichst alle eigenen oder sonstiges JavaScript durch Ext Funktionen ersetzt und bauen jetzt bei Bedarf und wenn es sinnvoll ist, ältere Teile der Anwendung auf Ext um. Z.B. verwenden wir das Tree Widget inkl. Drag and Drop und Kontextmenü, welches sehr mächtig ist und einen sehr stabilen Eindruck macht.
Zu wünschen wäre jetzt nur noch eine bessere Unterstützung durch eclipse, aber das kommt vielleicht auch noch.
Falls es sich ergibt, werde ich auch noch näher auf interessante Widgets von Ext JS eingehen.

Viel Spaß und Erfolg beim Ausprobieren und Anwenden.


Christof Aenderl


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