Thema: Usability

Fokussieren – nicht selektieren, bitte!

20. Juli 2010

Fokussieren - nicht selektieren, bitte!
Noch so ein Internet Explorer 6 Fehler, der bisher ganz an mir vorbei gegangen ist. Die Situation ist denkbar einfach: Ein Formular mit Auswahl-Feldern (SELECT) und dazugehörigen LABEL Elementen. Klickt der User auf das LABEL-Element wird das entsprechende Formular-Feld fokussiert. Funktioniert in allen mir bekannten Browsern fehlerfrei, nur der IE6 spielt verrückt. Hier wird das Formularfeld nicht nur fokussiert, nein, es wird auch gleich selektiert und zwar die erste Option des Auswahlfeldes.

Habe ein wenig herumgestöbert und siehe da, Microsoft ist das Problem bekannt und bietet eine Lösung. Interessant, dass gerade bei einem HTML-Element, das Usability und Accessibility erhöhen soll, auf eine JavaScript Lösung zurück gegriffen wird. Nicht so richtig sinnvoll, aber besser als gar nichts – hier der Beispielcode:

//Set a temp expando to store the current selectedIndex
function SelectOnFocusIn() {
  try {
    var eSrc = window.event.srcElement;
    if (eSrc)  eSrc.tmpIndex = eSrc.selectedIndex;
  } catch (e) {
    HandleError(e, false);
  }
}
 
//restore the selectedIndex
function SelectOnFocus() {
  try {
    var eSrc = window.event.srcElement;
    if (eSrc) eSrc.selectedIndex = eSrc.tmpIndex;
  } catch (e) {
    HandleError(e, false);
  }
}

Und der dazugehörige HTML-Code:

<select name="test" id="test"
  onfocusin="SelectOnFocusIn()" onfocus="SelectOnFocus()">
  <option value="3">Alien Perm </option>
  <option value="4">Alien Temp </option>
  <option value="1" selected="selected">Native </option>
  <option value="2">Naturalized </option>
  <option value="N">Not Indic. </option>
</select>

Eingebaut, getestet – und anscheinend hat der zuständige Entwickler noch nichts von multiple gehört, denn bei Auswahlfeldern mit Mehrfachauswahl greift der Code nicht. Es wird nur die erste selektierte Option übernommen, alle anderen Auswahlen gehen verloren – Bugfixing der besten Qualität. Welch hoher Maßstab hier angelegt wurde, sieht man schon an der Anweisung im catch Block der Funktionen, wo bitte ist die Funktion HandleError, oder gehört diese Funktion mittlerweile zu einem offiziellen JavaScript Standard? Nein – also weg damit, und dann auch gleich noch die fehlende Unterstützung von Mehrfachauswahlen implementiert:

function SelectOnFocusIn() {
  try {
    var eSrc = window.event.srcElement;
    if(eSrc)  {
      eSrc.firstSelected = eSrc.selectedIndex;
      var tmpIndex = [];
      for (var s = 0, l = eSrc.options.length; s<l; s++) {
        if (eSrc.options[s].selected == true) {
          tmpIndex.push(s);
        }
      }
      eSrc.tmpIndex = tmpIndex;
    }
  } catch (e) {}
}
 
function SelectOnFocus() {
  try {
    var eSrc = window.event.srcElement;
    if (eSrc) {
      /* muss gesetzt werden, anderenfalls wird die
          die erste Option immer mit aktiviert! */
      eSrc.selectedIndex = eSrc.firstSelected;
      for(var i=0, l = eSrc.tmpIndex.length; i<l; i++ ) {
        eSrc.options[eSrc.tmpIndex[i]].selected = true;
      }
    }
  } catch (e) {}
}

Mit diesen Korrekturen lässt sich der Fokus-Bug nun für alle Formen von SELECT-Feldern anwenden, und wenn dem schon so ist, dann würde eine allgemeine Regel für alle solche Felder doch deutlich weiter helfen, als in jedem einzelnen Feld zwei Event-Handler zu notieren. Das Ganze müsste dann nur noch zum frühst möglichen Zeitpunkt geladen werden, und idealerweise nur für den Internet Explorer 6. Meine finale Lösung sieht am Ende so aus:

(function () {
  var d   = document;
  selectFix = function() {
    var selFields = document.getElementsByTagName('select');
    if(!selFields || selFields.length==0) {return;}
    for(var i=0, l=selFields.length; i<l; i++) {
      var elm = selFields[i];
      elm.onfocusin = function() {
        try {
          var eSrc = window.event.srcElement;
          eSrc.firstSelected = eSrc.selectedIndex;
          var tmpIndex = [];
          for (var s = 0, l = eSrc.options.length; s<l; s++) {
            if (eSrc.options[s].selected == true) {
              tmpIndex.push(s);
            }
          }
         eSrc.tmpIndex = tmpIndex;
        } catch (e) {}
      };
      elm.onfocus = function() {
        try {
         var eSrc = window.event.srcElement;
         if (eSrc) {
           eSrc.selectedIndex = eSrc.firstSelected;
           for(var s=0, l = eSrc.tmpIndex.length; s<l; s++ ) {
             eSrc.options[eSrc.tmpIndex[s]].selected = true;
           }
         }
        } catch (e) {}
      }
    }
  };
  try { d.documentElement.doScroll('left'); selectFix(); }
  catch (err) { setTimeout(arguments.callee, 0); }
})();

Fehlt nur noch die Anforderung, das Script nur für den IE 6 laufen zu lassen. Dafür gibt es verschiedene Möglichkeiten. Eine JavaScript basierte “Browserweiche” nutzen (muss aber nicht sein), das ganze Script in eine eigene Datei auslagern und innerhalb eines Conditional Comment einbinden, oder – für den Fall, dass der Code innerhalb einer größeren JavaScript Datei steht über die jscript Variante:

/*@cc_on @*/
/*@if (@_jscript_version == 5.6)
  // dieser Bereich ist für jscript-Interpreter version 5.6 sichtbar
  // (das entspricht dem Internet Explorer 6)
@*/
/*@end @*/

Wie gesagt, es ist alles andere als ideal, die Funktionalität von LABEL mit JavaScript nachträglich zu implentieren, geht es bei diesem Element doch gerade um eine Erhöhung der “Verfügbarkeit” auch ohne Zusatztechnologien. Aber bei dieser Browserversion ist ohnehin schon alles verloren, sollte jemand eine bessere – eine scriptfreie Lösung haben, dann bitte!

CSS basiertes text-overflow in allen Browsern

17. Juni 2010

Pur CSS textoverflow in allen Browsern
Nochmal ein Beitrag zum Thema Textbegrenzung. Längere Texte abzuschneiden, weil sie anderenfalls bestimmte Rahmengrößen brechen würden, ist eine immer wiederkehrende Anforderung. Solche Kürzungen kann man zum Teil serverseitig erledigen – aber das ist nicht unter allen Umständen sinnvoll. Schwierig wird es immer dann, wenn diese Kürzung nicht auf einer definierten Zeichenlänge basiert, sondern auf einer definierten Größe (bspw. innerhalb einer Tabelle, oder einer Schaltfläche). Der Internet Explorer kennt die CSS-Eigenschaft text-overflow, eine entsprechende Implementierung im Firefox fehlt. Es geht trotzdem…

In meinem Artikel Text begrenzen, aber richtig hatte ich einige serverseitige Lösungen zum Begrenzen von Text vorgestellt. Solche Ansätze scheitern, wenn es bei der Begrenzung gar nicht um die Anzahl von Zeichen geht, sondern layoutabhängig sind. Die naheliegende Verwendung der CSS-Eigenschaft text-overflow wäre ideal, hier wird der Text nicht nur abgeschnitten, sondern in der Form Dieser Text geht noch weiter … dargestellt:

Beispielcode – die HTML Syntax

<div class="fixed-box">
  <p class="truncate">Ein viel zu langer Text für das Elternelement.</p>
</div>

Legt man folgenden CSS Code zugrunde, dann wird der Text zwar in allen Browsern “abgeschnitten” (durch overflow:hidden), aber nur im Internet Explorer und im Webkit (Safari, Chrome) greift text-overflow mit der Ausprägung ellipsis:

.fixed-box {
  width:100px;
}
 
.truncate {
  white-space: nowrap;
  text-overflow: ellipsis;
  overflow: hidden;
}

Aber wie hilft man Firefox auf die Sprünge? Sicher – man könnte sich über JavaScript helfen, bei Ajaxian wird auf bspw. ein entsprechendes JQuery Plugin verwiesen. Es gibt aber noch eine Alternative und die basiert auf XBL. XBL (XML Binding Language) ist eine XML-basierte Auszeichnungssprache, mit der man das Verhalten und Aussehen von XML- und HTML-Elementen beschreiben kann. Dies geschieht über sogenannte Bindings (Bindungen) in XBL, die an ein solches Element angehängt werden. Die Bindings werden in einer separaten XBL-Datei definiert. Über ein Binding kann auch Text in das XML- oder HTML-Element eingefügt werden. Ein Binding kann an mehrere unterschiedliche Elemente angehängt werden. Der Code für die Implentierung von text-overflow ellipsis:

<?xml version="1.0"?>
<bindings
  xmlns="http://www.mozilla.org/xbl"
  xmlns:xbl="http://www.mozilla.org/xbl"
  xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
>
  <binding id="ellipsis">
    <content>
      <xul:window>
        <xul:description crop="end" xbl:inherits="value=xbl:text">
          <children/>
        </xul:description>
     </xul:window>
    </content>
  </binding>
</bindings>

Cross Browser text-overflow CSS Syntax

Die XML Syntax wird als Datei abgespeichert und dann über CSS referenziert:

.truncate {
  white-space: nowrap;
  text-overflow: ellipsis;
  overflow: hidden;
  /* Firefox mit text-overflow */
  -moz-binding: url(ellipsis.xml#ellipsis);
  /* und nun auch noch Opera */
  -o-text-overflow:ellipsis;
}

Einen kleinen Schönheitsfehler hat die Sache im Firefox Version 3.6. Aufgrund eines Bugs bei -moz-binding werden einmal gesetzte Werte nicht wieder geändert. Heißt bei Fluid-Designs bleibt der “ellipsis” Effekt auch dann noch vorhanden, wenn er eigentlich nicht mehr benötigt wird.

Habe heute ein wenig mit der Syntax herumgespielt und dabei festgestellt, dass die genannte XML Fassung für Textflow im Firefox 3.6.3 einen Bug hat. Sobald der Text, der gekürzt werden soll, inline-Elemente wie bspw. STRONG oder EM enthält, “verschwinden” diese Fragmente. Eine modifizierte Fassung des XML-Codes schafft Abhilfe:

<?xml version="1.0"?>
<bindings
  xmlns="http://www.mozilla.org/xbl"
  xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
>
<binding id="none">
  <content><children/></content>
</binding>
<binding id="ellipsis">
  <content>
    <xul:label crop="end"><children/></xul:label>
  </content>
  <implementation>
    <field name="label"> document.getAnonymousNodes( this )[ 0 ] </field>
    <field name="style"> this.label.style </field>
    <property name="display">
       <getter>
         this.style.display
       </getter>
       <setter>
         if( this.style.display != val ) this.style.display= val
       </setter>
    </property>
    <property name="value">
      <getter>
         this.label.value
      </getter>
      <setter>
        if( this.label.value != val ) this.label.value= val
      </setter>
    </property>
    <method name="update">
      <body>
         var strings= this.textContent.split( /\s+/g )
         if( !strings[ 0 ] ) strings.shift()
         if( !strings[ strings.length - 1 ] ) strings.pop()
         this.value= strings.join( ' ' )
         this.display= strings.length ? '' : 'none'
      </body>
    </method>
    <constructor> this.update() </constructor>
  </implementation>
  <handlers>
    <handler event="DOMSubtreeModified"> this.update() </handler>
  </handlers>
</binding>
</bindings>

Dafür ist der erwähnte Bug bei dynamisch modifizierten Elementen (sei es über Javascript oder flexibel skalierbare Boxen) nicht mehr aktuell und kann gestrichen werden (wurde gestrichen). Hier sind noch einmal alle Tests zusammgefasst zu finden.

Seite 1 von 3123