13.10.2014
Author: Torsten Rademacher, T.Rademacher@comundus.com

Styling-Tricks für Formulare in Liferay

Styling von <input type=“file“ />

Dieser input-Tag funktioniert aus technischer Sicht einwandfrei. Er ist für den User sichtbar in das Formular eingefügt. In der Regel wird dabei das Style Guide zunächst nicht berücksichtigt.

Ein Grund warum es zu Konflikten kommen kann. Und zwar dann, wenn der Kunde dieses input-element an das individuelle Style Guide angepasst haben möchte, z. B. statt graue, blaue Felder. Denn dieses input-element lässt sich durch bloßes CSS nicht einfach so umstylen! Das Umstylen ist technisch machbar, aber für den unerfahrenen Entwickler nicht so einfach, wie gedacht. Die Folge ist, dass der Aufwand für diese Design-Anpassungen zeitlich und monetär in der Angebotsphase nicht einkalkuliert wurde.

 

Aus meiner Trickkiste – die Problemlösung

Im Folgenden werden wir einen Trick anwenden, mit dem wir ein einfaches file-input-element beliebig anpassen können. Der Trick ist einfach, aber wirkungsvoll. Wir fügen unser file-input-element wie gewohnt ein, stellen es aber durch andere Elemente dar, die wir dann wiederum optisch beliebig anpassen können.
Genial einfach, oder?

 

Unser Ausgangspunkt wird folgendes file-input element sein.
Nach unserem Hack soll es dann folgendermaßen aussehen:  

 

Liferay styling file-input

 

     1.   Änderungen im HTML

 

     a)    Was wir haben:

 

Liferay styling file-input

 

<label for=“foo“>Anhang</label>
<input type=“file“ name=“foo“ id=“foo“ class=“foo“ />

 

     b)    Wie wir vorgehen:

 

Nun sollten wir uns Gedanken machen, woraus so ein file-input optisch besteht. Es scheint, als bestünde es (ähnlich wie ein select) aus einem Textfeld zur Eingabe und einem Button zum Dialog öffnen. Diese einzelnen Felder fügen wir einfach in diesen input ein:

 

<label for=“foo“>Anhang</label>
<input type=“file“ name=“foo“ id=“foo“ class=“foo“>
<input type=“text“ name=“foo-txt“ id=“foo-txt“ class=“foo-txt“ />
<button class=“foo-btn“ id=“foo-btn“ />
</input>

 

Wir haben also unseren File-Tag und zusätzlich ein Textfeld und einen Button. Bisher nichts Weltbewegendes, da beides momentan auch noch nichts miteinander zu tun hat.

 

     c)    Das Ergebnis:

 

Liferay styling file-input

 

Wir haben unseren input type=“file“ – Tag und dahinter das Textfeld und den Button, den die Benutzer am Ende sehen wollen. Für die Funktionalität haben wir bereits alles, was wir brauchen, es sieht nur noch nicht so schön aus. Deshalb kümmern wir uns im nächsten Schritt um das Styling.

 

     2.   Änderungen im CSS:

 

Hinweis: Ab diesem Punkt gibt es Unterschiede in der allgemeingültigen und der Liferay-spezifischen Umsetzung. Daher werden von nun an im Code zwei Versionen vorgestellt.

 

     a)    Was wir haben:

 

Liferay styling file-input

 

Bisher haben wir noch nicht viel gestyled, aber wir gehen vom Beispiel aus, dass bereits Styleklassen existieren, sodass es nach der reinen Änderung im HTML ungefähr so aussehen könnte:

In diesem Beispiel ist input-file ungestyled, das Textfeld hellblau und der Button dunkelblau. Da der Button ein Icon und keine Beschriftung bekommen soll, ist auch keine Schrift zu erkennen.       

 

     b)    Wie wir vorgehen:

 

Wir wollen unsere beiden hinzugefügten Felder nach den Kundenvorgaben stylen, ohne, dass uns der input-file-Tag im Weg steht. Zunächst mal kümmern wir uns um unser Textfeld, das in diesem Beispiel eine Breite von 300px hat und sich linksbündig am Anhang ausrichtet:

 

Liferay styling file-input

 

 

Code für allgemeingültige Umsetzung:

 

.foo-txt {
       position: absolute; /* Richtet sich an nicht-statischen Elementen aus */
       left: 60px; /* Im Beispiel der Abstand zum Rand */
       background: #cce0ee;
       width: 300px;
}

 

Code für Liferay:

 

.foo-txt {
      position: absolute; /* Richtet sich an nicht-statischen Elementen aus */
      left: 258px; /* Im Beispiel der Abstand zum Rand */
      background: #cce0ee;
}

 

Auch der Button  wird nach Stylingvorgaben verschoben:

 

Code für allgemeingültige Umsetzung:

 

.foo-btn  {
       position: absolute;
       left: 365px; /* In diesem Beispiel die vorgesehene Position */
       width: 30px; /* In diesem Beispiel die vorgegebene Breite */
       height:20px; /* In diesem Beispiel die vorgegebene Höhe */
       border:none;
       background: #00469b url("search.png") top no-repeat; */ Icon
       background-size: 15px; /* Größe des Icons
}

 

Code für Liferay:

 

.foo-btn  {
       position: absolute;
       left: 540px; /* In diesem Beispiel die vorgesehene Position */
       width: 30px; /* In diesem Beispiel die vorgegebene Breite */
      height:20px; /* In diesem Beispiel die vorgegebene Höhe */
       border:none;
       background: #00469b url("search.png") top no-repeat; */ Icon
       background-size: 15px; /* Größe des Icons
 
}

 

Jetzt, da beide Element mit einer Hintergrundfarbe und der Button mit einem Hintergrundbild versehen sind, nähern wir uns schon stark unserem gewünschten Ergebnis:

Liferay styling file-input

 

 

Wer genau hinschau,t erkennt allerdings eine kleine Linie unterhalb des Textfeldes. Diese stammt noch von unserem input-file-Tag, an dem wir bisher noch nichts gemacht haben.

Doch darum kümmern wir uns jetzt:


Der Benutzer soll am Ende nur noch Textfeld und Button sehen. Damit der file-input trotzdem reagiert, muss er über den anderen Feldern liegen. Dies schaffen wir, indem wir seine Eigenschaft als statisches Element entfernen, seinen z-index erhöhen und ihn unsichtbar, aber trotzdem reaktionsfähig machen.

Die wichtigste Frage hierbei ist: Wie blende ich das Ding korrekt aus?

 

display:none?

Unbrauchbar! Durch diesen Style existiert unser file-input optisch und funktional gar nicht. Er wäre lediglich im DOM-Baum zu finden und vielleicht noch als Parameter für eine Request geeignet.

 

visibility:hidden?

Schon besser, da das Element da zu sein scheint, aber eben nicht sichtbar gemacht ist. Platz wäre reserviert,
ABER: Elemente, die das Attribut visibility:hidden habenreagieren nicht auf Klicks!

Was nun? Ich blende es nicht auf die klassische Art aus, sondern mache es voll transparent:

 

Code für allgemeingültige Umsetzung:

 

.foo {
      position: relative; /* Wird zum nicht-statischen Element */
      z-index: 2; /* steht über den neuen Elementen */
      width: 360px; /* In diesem Beispiel vorgesehene Breite */
      opacity: 0; /* volle Transparenz! */
}

 

Code für Liferay:

 

.foo {
      position: relative; /* Wird zum nicht-statischen Element */
      z-index: 2; /* steht über den neuen Elementen */
      width: 312px; /* In diesem Beispiel vorgesehene Breite */
      opacity: 0; /* volle Transparenz! */
}

 

     c)    Das Ergebnis:

Liferay styling file-input

Wir haben nun ein schön gestyltes file-input-Feld, das bereits auch schon anklickbar ist und auch Files und deren Pfade speichert.

Das einzige, was momentan noch fehlt, ist die Anzeige für den Benutzer (der Pfadname im Textfeld).

An dieser Stelle muss uns JavaScript behilflich sein:

 

     3.   Änderungen im JavaScript

 

An dieser Stelle werde ich zunächst eine Lösung in reinem JavaScript vorstellen. Im Anschluss wird auf die Umsetzung mit JQuery und AlloyUI eingegangen.

 

     a)    Was wir haben:

 

<label for=“foo“>Anhang</label>
<input type=“file“ name=“foo“ id=“foo“ class=“foo“>
<input type=“text“ name=“foo-txt“ id=“foo-txt“ class=“foo-txt“ />
<button class=“foo-btn“ id=“foo-btn“ />
</input>

Und dies gestyled. Es ist zwar funktionsfähig, aber uns fehlt noch die Anzeige des Pfads für den Benutzer.

 

     b)    Vorgehen in JavaScript:


Wir möchten, dass jedes Mal, wenn ein Benutzer eine Datei auswählt, der Dateipfad auf das Textfeld mit der id „foo-txt“ geschrieben wird.

Dazu  erzeugen wir zunächst im JavaScript-Bereich eine Variable (foo) und weisen ihr unser file-input-element zu, sodass wir auf unser  file-input-element zugreifen können.

         

<script type="text/javascript" >
     var foo = document.getElementById('foo');  
</script>

 

Jetzt können wir bestimmen, wann etwas passieren soll. In diesem Fall, wenn sich der Wert des file-input-elements geändert hat. Das machen wir in dem wir dem „onchange Event“ eine Funktion zuweisen:

 

 <script type="text/javascript" >
             var foo = document.getElementById('foo');
 
             foo.onchange = function (event) {
 
             }
</script>

Wenn ein Benutzer eine neue Datei, die er anhängen möchte, auswählt wird nun jedes Mal die anonyme, noch leere Funktion aufgerufen.

In dieser fehlen uns jetzt noch drei Zeilen:

 

     var fileInput = event.target;
     var footxt = document.getElementById('foo-txt'); 
     footxt.value = fileInput.value;

In der ersten Zeile weisen wir der Variable „fileInput“ das Element zu,
dessen onChange-Property unsere anonyme Funktion zugewiesen ist. In diesem Fall ist das unser file-input  namens „foo“.
In der zweiten Zeile weisen wir der Variable „footxt“ unser sichtbares Textfeld mit der id „foo-txt“ zu.
Nun muss nur noch der Wert unseres file-inputs (der eingetragene Dateipfad) in unser Textfeld übertragen werden.  

Unser JavaScript-Block sieht nun also folgenderweise aus:

 

       <script type="text/javascript">
             var foo = document.getElementById('foo');
foo.onchange = function (event) {
                  var fileInput = event.target;
                  var footxt = document.getElementById('foo-txt');
                  footxt.value = fileInput.value;
             }
       </script>

 

     c)    Vorgehen mit JQuery:

 

Der Hauptunterschied zwischen reinem JavaScript und JQuery ist in der Praxis, dass bei JQuery weniger Zeilen notwendig sind, da mehrere JavaScript-Funktionen bzw. Befehle zusammengefasst werden können.

Sehen wir uns einmal an, wie sich der Code mit JQuery verändert:

             $('#foo').change( function(event) {
                    $(‘#foo-txt‘).val(event.target.val());
             }

 

In diesen Zeilen haben wir nun die gesamte JavaScript-Logik komprimiert.

In der ersten Zeile wird unser file-input-Element mit der ID „foo“ ausgewählt und bekommt mithilfe der JQuery-Funktion .change(function) eine Funktion als Parameter zugewiesen, die bei einer Wertänderung aufgerufen wird.

In der zweiten Zeile steht nun unser komplettes Eventhandling, für das wir in JavaScript 3 Zeilen benutzt haben. Der JQuery-ID-Selektor ist nun schon aus der ersten Zeile, ebenso wie event.target aus dem JavaScript-Block. Einzige Besonderheit hier: die val()-Methode. Sie ist in Abhängigkeit ihrer Parametrisierung entweder Get- oder Set- Methode.

 

     d)    Vorgehen mit AlloyUI:

 

Da wir uns in der Liferay-Welt bewegen, werde ich noch kurz darstellen, wie unsere Lösung mit dem von Liferay genutzten JS-Framework (AlloyUI) aussieht:

             A.one('#foo').on(‚change‘, function(event) {
                    A.one(‘#foo-txt‘).val(event.target.val());
             }

 

Wir erkennen an dieser Stelle, dass AlloyUI vieles mit JQuery gemeinsam hat, daher gehe ich in diesem Beispiel auf die Unterschiede zwischen AlloyUI und JQuery ein:

Unsere Elemente werden nicht mit unserem JQuery-Universalelement „$“ geholt, sondern mit A.one(… . A. ist die Kurzfassung von AUI()., welches unser AlloyUI-Universalelement ist. Da AlloyUI ein bisschen mehr auf Semantik setzt als JQuery, in welchem es nur um Kürze geht, ist hier noch der Aufruf von „one“ nötig. Hiermit wird das Element aus unserem DOM geholt, welches unseren Kriterien entspricht, also die ID „foo“ besitzt.

Mithilfe der Funktion .on() wird unserem Element ein EventHandler zugewiesen, ‘change‘ bestimmt dabei, um welches Event es sich dabei handeln soll. Als zweiten Parameter übergeben wir unsere anonyme Funktion, die den Wert des file-inputs „foo“ auf unser sichtbares Textfeld „foo-txt“ überträgt.

 

     e)    Das Ergebnis:

 

Liferay styling file-input

 

Wir haben nun erfolgreich ein input type=“file“ gestyled!

Diese Methode funktioniert in den meisten gängigen Browsern.

 

     4.  Kompatibilität mit IE9

 

Wir haben es zwar geschafft, einen file-input zu stylen, aber an dieser Stelle muss gesagt werden, dass dies nicht überall gleich funktioniert!

Im Internet Explorer 9 wäre zum Dialog öffnen statt eines einfachen Klicks ein Doppelklick zur Dialogöffnung notwendig. Auch dieses Problem kann (sogar nur in HTML/CSS) beseitigt werden!

Wie wir vorgehen:

Um dieses IE9-spezifische Problem zu lösen, müssen wir dafür sorgen, dass im gesamten input-Bereich inklusive des Labels der Dialog mit einem Klick geöffnet werden kann. Dazu müssen wir die Funktionalität des input-Tags auf das Label auslagern und das Label über die gesamte Breite ziehen, ohne, dass sich die Schrift verändert. Die hinzukommenden Attribute sind hier markiert:

<label for=“foo“ data-role=“button“ data-inline=“true“ data-corners=“false“>

Anhang

</label>
<input type=“file“ name=“foo“ id=“foo“ class=“foo“ multiple
 data-role=“button“ data-inline=“true“ data-corners=“false“>
<input type=“text“ name=“foo-txt“ id=“foo-txt“ class=“foo-txt“ />
<button class=“foo-btn“ id=“foo-btn“ />
</input>

Ab diesem Punkt ist wieder CSS gefragt:

Da nun nicht das input-Tag, sondern das Label auf den Klick reagieren soll, benötigt dieses nun den höheren z-index sowie die absolute Positionierung:

label {
      padding: 4px 337px 4px 0; /* Nachträgliche Anpassung der Ausrichtung */
      position: absolute;
      z-index: 2;
}

Bereits jetzt ist die vollständige Funktionalität wieder gegeben. An dieser Stelle kann aber noch das input-Tag optimiert werden, da es nicht mehr in seiner ursprünglichen Form benötigt wird. Aus der vollen Transparenz wird ein Ausblenden und unser z-Index wird nun auch nicht mehr benötigt. Unser Styling sieht dann folgendermaßen aus:

.foo {
 
      position: relative;
      width: 312px;
      visibility: hidden;
}

Damit hätten wir auch für den IE9 das Styling und die Funktionalität für das input type=“file“ – Tag abgeschlossen. Diese Lösung deckt somit mehr Browser ab.


Viel Spaß beim Ausprobieren und bis zum nächsten Mal!

 

Torsten Rademacher