Submit your widget

jQuery UI: Radiobutton- und Checkbox-Replacement

Created 14 years ago   Views 15705   downloads 2158    Author protofunc
jQuery UI: Radiobutton- und Checkbox-Replacement
View DemoDownload
143
Share |

Eine gerne durchgeführte Aufgabe ist es, die in manchen Browsern recht hässlichen Checkboxen und Radiobuttons durch schönere, dem Design angepasste Eingabeelemente zu ersetzen.

UI-Widget-Factory

Die UI-Widget-Factory ist Teil von jQuery UI (in der ui.core.js). Um die Funktionsweise der Factory zu verstehen, empfehle ich folgendes UI Widget Tutorial.
Der Grundaufbau unseres unseres Scripts, wird wie folgt aussehen:

$.widget('ui.checkBox', {

});
$.ui.checkBox.defaults = {
    focusClass: 'focus',
    checkedClass: 'checked',
    disabledClass: 'disabled',
    hideInput: true,
    radioElements: false
};

 

In das noch leere Object-Literal werden wir die im folgenden beschriebenen Methoden integrieren:

Die Init-Methode:

Als 1. sammeln wir alle label-Elemente, welche mit dem jeweiligen input-Element durch das for-Attribut in Beziehung stehen. Diese(s) label-Element(e) werden durch entsprechende Hintergundbilder später die Rolle und den Zustand des input-Elements deutlich machen. Solltet ihr hierfür aufgrund von Designgründen nicht, die bereits vorhanden label-Elemente nutzen, sondern ein eigenes Element für die Optik des input-Elements nutzen wollen, könnt ihr vor dem instanziieren des Checkbox-Objects einfach ein weiteres label-Element einfügen. Beispiel:

$('input:radio').each(function(){
 var jElm = $(this);
  jElm
   .after('<label class="visualRadio" for="'+jElm.attr(id)+'">')
   .checkBox();
});

 

In der Regel wird dies jedoch nicht nötig sein.

Sofern es sich um ein Radiobutton handelt, holen wir uns alle Radiobuttons der Gruppe über das name-Attribut. Hintergrund ist, dass Radiobuttons sofern sie ungecheckt werden kein Cross-Browser-taugliches Event werfen und wir dies manuell nachholen müssen.

Im Anschluss verschieben wir das Original-input-Element aus dem Viewport. Dies machen wir allerdings davon abhängig, ob der User sich im Usermode (auch Kontrastmodus genannt) befindet, wofür wir ein weiteres kleines Script einsetzen. Sollte das Script nicht verwendet werden, gehen wird davon aus, dass sich der User nicht im Usermodus befindet.

Hiernach rufen wir unsere – noch zu definierende – reflectUI-Methode auf welche die Zusätnde des input-Elements durch CSS-Klassen auf unser(e) label-Element(e) überträgt.

Im Anschluss fügen wir noch die entsprechenden Event-Handler hinzu, um die Zustandsänderungen dynamisch ändern zu können. Damit das this weiterhin auf unser instanziiertes Object zeigt, setzen wir die bind-Function-Methode ein.

Hiermit ist unsere init-Methode fertig:

init: function(){
 var that = this,
  opts = this.options;
 this.labels = $('label[for='+this.element.attr('id')+']');
 this.checkedStatus = false;

        this.radio = opts.radioElements ||
  (this.element.is(':radio')) ?
  $(document.getElementsByName(this.element.attr('name'))) :
  false;

 var usermode = ($.userMode && $.userMode.get) ?
  $.userMode.get() :
  false;

 if(!usermode && opts.hideInput){
  var css = ($('html').attr('dir') == 'rtl') ?
   {position: 'absolute', right: '-999em', width: '1px'} :
   {position: 'absolute', left: '-999em', width: '1px'};
  this.element.css(css)
   .bind('usermode', function(e, o){
    if(o.usermode){
     that.destroy.call(that, true);
    }
   });
 }
 this.reflectUI({type: 'initialReflect'});
 this.element
  .bind('click.checkBox', $.bind(this, this.reflectUI))
  .bind('focus.checkBox blur.checkBox', function(){
   that.labels.toggleClass(opts.focusClass);
  });
}

 

Die reflectUI-Methode

Diese Methode ist sozusagen der Kern unserers Scripts. Sie stellt lediglich sicher, dass die Zustände (disabled und vor allem checked) auf unser User-Interface übertragen werden.

Wird eine Veränderung zum vorherigen Zustand erkannt, ruft die Methode die propagate-Methode auf, welche in vielen jQuery-UI-Widgets vorkommt und sehr interessant ist:

reflectUI: function(elm, e){
 var checked = this.element.is(':checked'),
  oldChecked = this.checkedStatus,
  o = this.options;
 e = e ||
  elm;
 this.labels[(this.element.is(':disabled'))?
  'addClass' :
  'removeClass'](o.disabledClass);

        this.checkedStatus = checked;

        if (checked !== oldChecked) {
            this.labels.toggleClass(o.checkedClass);
            this.propagate('change', e);
        }
}

 

Die propagate-Methode

Die propagate-Methode triggert an dem jeweiligen input-Element ein custom-Event, welche durch entsprechende Event-Objekte sowie Übergabe der eigenen Objektreferenz seine Zustände nach aussen trägt. Dies ist eine sehr gute Möglichkeit, um andere Widgets mit dem checkBox-Widget zu verbinden, ohne lästige starke Abhängigkeiten zu schaffen oder in den Original-Code einzugreifen. Es lässt sich aber auch für profaneres Gebrauchen. So kann man beispielsweise die Veränderung des checked-Status dynamisch durch eine Animation kenntlich machen.
Sofern es sich bei dem input-Element, um einen Radiobutton handelt, wird für alle Radiobuttons zusätzlich die reflectUI-Methode aufgerufen.

propagate: function(n, e){
 if(this.radio){
  this.radio.checkBox('reflectUI', e);
 }
    this.element.triggerHandler("checkbox" + n, [e, {
        instance: this,
        options: this.options,
        checked: this.checkedStatus,
  labels: this.labels
    }]);
}

 

Die destroy-Methode

Viele jQuery-UI-Widgets enthalten eine eigene destroy-Methode, welche dazu dient, das Widget mehr oder weniger auszuschalten und den Original-Zustand herzustellen. Bei unserer destroy-Methode kann gewählt werden, ob nur das Original-input-Element wieder sichtbar sein soll. Die Events und Zustandsanzeigen am label-Element bleiben erhalten oder ob zusätzlich alle Event-Handler entfernt werden sollen.

destroy: function(onlyCss){
 this.element.css({position: '', left: '', right: '', width: ''});
  if(!onlyCss){
   var o = this.options;
   this.element
    .unbind('.checkBox');
   this.labels
    .removeClass(o.disabledClass+' '+o.checkedClass+' '+o.focusClass);
  }
}

 

Kleine Verfeinerungen

Hiermit ist das Script bereits fertig und einsatzbereit. Als kleine Verfeinerung unseres Scripts fügen wir noch ein paar weitere öffentliche Methoden hinzu, die uns in anderen Situation noch hilfreich sein könnten:

disable: function(){
 this.element[0].disabled = true;
 this.reflectUI({type: 'manuallyDisabled'});
},
enable: function(){
 this.element[0].disabled = false;
 this.reflectUI({type: 'manuallyenabled'});
},
toggle: function(){
 this.changeCheckStatus((this.element.is(':checked'))?false:true);
},
changeCheckStatus: function(status){
 this.element.attr({'checked': status});
 this.reflectUI({type: 'changeCheckStatus'});
}

 

Fazit:

Wie sich gezeigt hat, ist der Scripting-Aufwand im Vergleich zum Ergebnis (komplette Tastaturnutzung wie Original-Elemente, richtige Rollen und Zustände bei Screenreadern etc.) relativ gering gewesen. Der Grund: Wir haben an keiner Stelle eine Tastaturnutzung oder ähnliches Scripten müssen. All das besorgt der Browser für uns.