Archiv für die Kategorie ‘Prototype’

Universeller Feldupdater mit Rails

Dienstag, 24. August 2010

Ich erstelle momentan ein kleines, sehr spezielles CMS mit Ruby on Rails, mein erstes komplettes Rails Projekt. Der Umstieg von Java in die Rails-Welt war zwar gewöhnungsbedürftig und hatte einige Hürden, aber es hat sich gelohnt. Hier ein kleines, schönes Beispiel wie man mit wenigen Zeilen Ruby ziemlich universellen Code erzeugen kann.

Die Anforderung war, alle Eingabefelder im CMS, z.B. die Beschriftung der Bilder oder Überschriften und Artikel, einzeln editierbar zu machen. D.h. man klickt bzw. doppelklickt das Feld an, editiert es und die Änderung wird beim Verlassen des Feldes gespeichert.
Undenkbar hier für jedes Feld eigene Methoden im Controller oder JavaScript anzulegen.

Die Lösung besteht aus drei Teilen wobei ich hier nur den Controller näher erläutern möchte.
Teil 1 ist eine Helpermethode die ein entspr. Eingabefeld erzeugt.
Teil 2 ist eine kleine JavaScript Funktion die den Wert des Feldes per Ajax an den Controller sendet.
Teil 3 ist der Controller der den neuen Wert speichern soll.

Idee war es dem Controller folgende Werte zu liefern:
- Name der Model Klasse
- ID des Datensatzes
- Name des Attributes
- Wert des Attributes
Die ersten drei Werte werden durch einen Punkt getrennt als ID des HTML input gesetzt, z.B.: Image.34.caption

Jetzt aber zum Controller, der nur aus einer public Methode besteht:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class FieldsController < ApplicationController
 
  layout nil
 
  def update
    a = params[:key].split('.')
    update_field a[0], a[1], a[2], params[:value]
    head :status => 200
  end
 
  private
  def update_field classname, id, attr, value
    # must be a ActiveRecord::Base
    obj = ActiveRecord::Base.const_get(classname).find(id)
    obj.update_attribute(attr, value)
  end
end

Die als “key” übergebenen Werte werden erstmal zerlegt und der Methode update_field übergeben. Hier wird die Model-Klasse über die Kernel Funktion const_get geladen (Klassennamen sind in Ruby Konstanten). Das machen wir direkt auf ActiveRecord::Base, weil wir dort alle Model Klassen finden werden und somit nicht Kernel.const_get verwenden müssen. Auf der Modelklasse rufen wir direkt ein find mit der ID auf und bekommen die Instanz unseres Datenobjekts. Auf diesem setzen wir den Wert des Attributs mit update_attribute neu. Fertig.
Der Controller liefert dann noch den Status 200 zurück, damit unser AjaxRequest weiß, dass alles ok ist.

Auf ActiveRecord::Base gibt es noch weitere update Methoden, so könnte man z.B. auch mehrere Attribute updaten.


Christof Aenderl


JavaScript: Tastatureingaben mit prototype auswerten

Montag, 15. März 2010

Im folgenden Abschnitt möchte ich anhand des Beispiels einer Mehrfachauswahl von Eingabefeldern erläutern, wie einfach es ist, in JavaScript einen Tastendruck auszuwerten.
Ich verwende hier die prototype Bibliothek. Für alle, die prototype nicht kennen, ganz kurz:
$(’name’) entspricht der Methode getElementById(’name’)

var KeyEvent = {
   ctrl: false,
   keyCode: 0,
   element: null,
 
   /**
    * Am uebergebenen Element z.B. einem div Container oder document selbst
    * werden die Methoden fuer die Events keydown und keyup registriert.
    *
    * element: HTML-Element, an dem die Events registriert werden sollen
    * z.B. Tabelle oder Div.
    */
   init: function(element) {
      KeyEvent.element = element;
      // Taste gedrueckt
      Event.observe(KeyEvent.element, 'keydown', KeyEvent.registerDown);
      // Taste wieder losgelassen
      Event.observe(KeyEvent.element, 'keyup', KeyEvent.registerUp);
   },
 
   /**
    * Function, die auf den keydown event reagiert
    */	
   registerDown: function(event) {
      // falls die Taste gedrueckt bleibt, sollen keine weiteren 
      // keydown events verarbeitet werden
      KeyEvent.element.stopObserving('keydown');
      KeyEvent.keyCode = event.keyCode;
      if (event.ctrlKey) {
         KeyEvent.ctrl = true;
      }
   },
 
    /**
     * Function, die auf den keyup event reagiert
     */
   registerUp: function(event) {
      KeyEvent.ctrl = false;
      // wird die Taste losgelassen, starten wir den Observer fuer keydown wieder
      Event.observe(KeyEvent.element, 'keydown', KeyEvent.registerDown);
   }
};
 
/**
 * Wird beim onfocus des Eingabefeldes aufgerufen
 */
function selectField(element) {
   if (KeyEvent.ctrl) {
      // multiselect ... z.B. könnte man hier das Feld mit einer CSS class markieren.
   }
}

Im HTML könnte das dann wie folgt verwendet werden:

<body onload="KeyEvent.init($('content'))">
 
   <div id="content">
      <form ....>
         <input type="text" name="f1" onfocus="selectField(this)" />
         <input type="text" name="f2" onfocus="selectField(this)" />
         ...
      </form>
   </div>
</body>

Das obige Beispiel verwendet jetzt nur den speziellen event.ctrlKey. Eine denkbare Erweiterung wäre z.B. die Shift-Taste auzuwerten um die Felder von – bis zu markieren. Natürlich könnte man auch beliebige andere Tasten auswerten, dafür muss aber dann der numerische keyCode verwendet werden.

Mehr Infos zur Verwendung von Prototype finden sie unter http://prototypejs.org


Christof Aenderl


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