Űrlap mező helykitöltő jQuery kiegészítő

A weben, és ezáltal munkám során is gyakran találkozok olyan űrlapokkal, ahol valamelyik mezőben utalás szerepel arra, hogy mit várnak, mit írjunk be a mezőbe. Ekkor általában nem jelenítik meg a mezőhöz tartozó címkét sem. Ilyen űrlapok szoktak lenni a keresők, amikor a keresési mezőben csak annyi szerepel: Keresés…. Hasonló szokott lenni a belépési űrlap is, ahol az egyik mezőben az szerepel például hogy Felhasználói név míg a másikban pedig csillagok, így egyértelművé válik, hogy mi is a mezők, illetve az űrlap feladata.

Ekkor, ha belekattintunk a mezőbe ez a kitöltés eltűnik, esetleg a bevitt szöveg színe is kontrasztosabbá válik, ezáltal jobban elkülönül a még ki nem töltött mezőktől. HTML5 esetén erre már létezik mező tulajdonság placeholder néven, ugyanakkor még nem minden böngésző támogatja a HTML5-öt, illetve a placeholder attribútumot. Ennek ürügyén írtam egy nagyszerű kis jQuery kiegészítőt, ami ezt valósítja meg, sőt, ennél még valamivel többet is.

(function($, window, undefined){
  $.fn.extend({
    inputPlaceholder : function (settings) {
      // Csak TEXT és PASSWORD típusú INPUT mezők kellenek.
      var inputs = this.filter('input:text,input:password'),
          iph = '.inputPlaceholder',
          focus = 'focus' + iph,
          blur = 'blur' + iph,
          clearValues;

      if (settings === 'remove') {
        inputs.unbind(iph);
        return this;
      }

      // A beállításokat kiegészítjük az alapértelmezettekkel.
      settings = $.extend({
          'blurClass'    : '',
          'focusClass'   : '',
          'clearOnSubmit': true,
          'useAttribute' : false
        }, settings);

      if (inputs.length) {
        inputs.each(function () {
          var field = $(this),
              placeholder = settings.useAttribute ?
                field.attr(settings.useAttribute) :
                field.val();

          field.bind(focus, function(event) {
              // Eltároljuk az aktuális értéket későbbi felhasználásra.
              var fieldValue = field.val();

              // A mező értéke megegyezik az alapértelmezett értékkel, akkor ürítsük ki.
              if (placeholder === fieldValue) {
                field.val('').removeClass(settings.blurClass).addClass(settings.focusClass);
              }
            }).bind(blur, function (event) {
              // Eltároljuk az aktuális értéket későbbi felhasználásra.
              var fieldValue = field.val();

              // A mező értéke üres, rakjuk bele az alapértelmezettet.
              if (!fieldValue) {
                field.val(placeholder).removeClass(settings.focusClass).addClass(settings.blurClass);
              }
            }).triggerHandler(blur);
        });

        // Üríteni kell a mezőt a form elküldésekor?
        if (settings.clearOnSubmit) {
          clearValues = function () {
            inputs.each(function () {
              // Minden elemen végig kell mennünk `each`-csel
              // mivel a `triggerHandler` csak az első elemen hajtja végre az eseményt.
              $(this).triggerHandler(focus);
            });
          };
          inputs.closest('form').bind('submit' + iph, clearValues);
          $(window).bind('unload' + iph, clearValues);
        }
      }

      return this;
    }
  });
}(jQuery, this));

Elemezzük kicsit a kódot, kívülről indulva, hátha okoz valakinek valamilyen meglepetést a jelölésmód, illetve a használt technikák.

Önkioldó névtelen függvény

(function($, window, undefined){
  //…
}(jQuery, this));

Ez egy úgynevezett névtelen (anonymus) önkioldó (self executing) függvény. Ez már önmagában is több trükköt rejt. Miért is használna valaki ilyen függvényt? Erre több oka is lehet. A legfontosabb, hogy elrejti a függvényben levő változókat a globális névtérből, azaz nem szennyezi globális névteret feleslegesen a változóival, illetve függvényeivel. Másik indok, hogy mivel a lokális változók elérése gyorsabb, mint visszakeresni a globális változókat, így valamennyit tudunk gyorsítani a kódunkon. Harmadrészt a globális névtérben előforduló változókra más, illetve rövidebb néven tudunk hivatkozni, valamint a kód tömörítő algoritmusok is sokkal hatékonyabban tudnak működni így, mivel a lokális változókat szabadon át tudják nevezni. És van még itt egy utolsó kis dolog, amit ugyan nem használunk ki a kódunkban, de egyeseknek hasznos lehet, ez pedig az undefined körül bonyolódik. Ugyanis, amelyik függvény paramétert nem adtunk meg a függvény meghívásakor az undefined -nak inicializálódik. Viszont, ha a függvényen kívüli undefined -ra hivatkozunk, az nem feltétlen lesz hamiskás (falsy) értékű, ugyanis véletlenül megadható a következő:

undefined = true;
var x = true;
if (x != undefined) {
  // ide nem fogunk bejutni, ugyanis mindkettő értéke true.
}

Ezt mindenképpen el akarjuk kerülni, bár a mi kódunkban nem végzünk hasonló ellenőrzést, nem árt, ha fel vagyunk készülve. Az egyszerűbb megértés kedvéért sajnos az undefined szerepelhet mind változónévként, mind pedig értékként, így ezt akarjuk elfedni azzal, hogy létrehozunk egy lokális változót, melynek mind a neve, mind az értéke undefined . Ez egy tervezési hiba a nyelvben, de valamit kezdeni kell vele.

Inicializálás

$.fn.extend({
  inputPlaceholder : function (settings) {
    //…
  }
});

Ez egy eléggé standard jQuery kiegészítő írási forma, a lényege, hogy a jQuery prototípusát kiegészítjük egy új függvénnyel, aminek a neve inputPlaceholder . Ezt a $(selector).inputPlaceholder() módon érhetjük majd később el. Mint látható átadhatunk neki egy settings paramétert is, amit a későbbiekben majd még használunk. Amit fontos tudni a jQuery kiegészítő írásról, hogy a kódban a this alapjában az éppen kiválasztott elemekre, azaz a jQuery objektumra vonatkozik. Ezért lehet rajta például egyből filtert, vagy más hasonló függvényt alkalmazni.

// Csak TEXT és PASSWORD típusú INPUT mezők kellenek.
var inputs = this.filter('input:text,input:password'),
    iph = '.inputPlaceholder',
    focus = 'focus' + iph,
    blur = 'blur' + iph,
    clearValues;

Mivel a helykitöltő szövegnek igazán szerepe csak text és password mezőkre van (ameddig el nem terjednek a speciális HTML5-ös új mezők), ezért egyből szűrjük is a kiválasztást ezekre. Ezután létrehozunk pár változót, amik igazából a tömörítést segítik elő, és csökkentik az azonos szövegek előfordulását a kódban.

Itt következik egy érdekes lépés:

if (settings === 'remove') {
  inputs.unbind(iph);
  return this;
}

Hasonlóan a jQuery UI megvalósításhoz, lehetővé tesszük hogy a plugin által az oldalhoz adott eseménykezelőket egy lépésben el tudjuk távolítani, ezzel szinte visszaállítva az eredeti állapotot. Ez hasznos tud lenni, ha valamilyen okból a működés már nem kedvező számunkra. Ennek egyszerű a végrehajtása, egyszerűen a következőt kell meghívni:

$(selector).inputPlaceholder('remove');

Itt használunk egy igazán hasznos kis trükköt, hogy az összes eseménykezelőnket az .inputPlaceholder névtérbe helyezzük, így egyetlen lépéssel el tudjuk őket távolítani az elemekről. Persze ha nem voltak ilyen eseménykezelők hozzárendelve a kiválasztott elemekhez, akkor sincs semmi probléma ugyanis nem történik igazából semmi. Ami fontos még ebből a kódrészletből, az a return this; , ugyanis ez teszi lehetővé hogy jQuery-s szokásnak megfelelően láncba tudjuk kötni a végrehajtandó utasításokat (chaining).

Ezt követi a settings objektum kiegészítése az alapértelmezett értékekkel. Ez azért fontos, hogy a későbbi műveletek során a settings minden szükséges tulajdonsága meglegyen, ezzel elkerülve a további komolyabb teszteléseket a tulajdonságok felé, illetve használható alapértelmezéseket adunk azokra, amit nem adtunk meg paraméterben. Azaz, amennyiben megelégszünk az alapértelmezett értékekkel, úgy azokat nem kell átadni a paraméterben, csak azokat, amikben el szeretnénk térni.

Események hozzákapcsolása

Mint említettem az eseménykezelőket egy névtérbe helyezzük, így kényelmesebb velük dolgozni. Az eseménykezelőket egy closure-ban rakjuk az elemekhez, ez azért fontos, mert így egyszerűen el lehet tárolni, hogy mi a mező helykitöltő értéke, nem kell azt mondjuk data tulajdonságban tárolni. Az alapértelmezett értéknél lehetővé tesszük, hogy az DOM elem egy tulajdonságát használjuk, vagy a jelenlegi értékét. Például ha a placeholder vagy a title attribútum értékét szeretnénk ha mint helykitöltő szerepeljen, elég megadni a következőt:

$(selector).inputPlaceholder({useAttribute:'placeholder'});

vagy

$(selector).inputPlaceholder({useAttribute:'title'});

Az eseménykezelőket a bind paranccsal kapcsoljuk az elemhez, ezt azért tesszük, mert csak így lehet névtérben hozzárendelni, és megspórolunk a JavaScript motornak egy függvényhívást, ugyanis ha a focus , blur metódusokat használnánk, az ugyanúgy meghívja a bind utasítást a megfelelő paraméterekkel.

field.bind(focus, function(event) {
    // …
  }).bind(blur, function (event) {
    // …
  }).triggerHandler(blur);

A blur eseményt egyből végre is hajtjuk, de egy kisebb trükkel. A triggerHandler annyiban különbözik a trigger -től - amit a legtöbbször használunk -, hogy a böngésző alapértelmezett eseménye nem fut le, csak az kódból hozzákapcsoltak futnak, valamint csak az első kiválasztott elemre fut le, nem az összesre. Ezt még annyival kiegészítettük, hogy csak az általunk megadott névtérbe tartozó eseménykezelők fussanak le ugyanis:

blur == 'blur.inputPlaceholder'

Maguk az eseménykezelők igazából nem csinálnak sokat, csak hozzáadják illetve leveszik a blurClass és focusClass osztályokat az elemről, valamint az értékét ürítik vagy feltöltik az helykitöltővel, attól függően, hogy focus vagy blur esemény következett be, és hogy mi a DOM elem aktuális értéke.

field.bind(focus, function(event) {
    // Eltároljuk az aktuális értéket későbbi felhasználásra.
    var fieldValue = field.val();

    // A mező értéke megegyezik az alapértelmezett értékkel, akkor ürítsük ki.
    if (placeholder === fieldValue) {
      field.val('').removeClass(settings.blurClass).addClass(settings.focusClass);
    }
  }).bind(blur, function (event) {
    // Eltároljuk az aktuális értéket későbbi felhasználásra.
    var fieldValue = field.val();

    // A mező értéke üres, rakjuk bele az alapértelmezettet.
    if (!fieldValue) {
      field.val(placeholder).removeClass(settings.focusClass).addClass(settings.blurClass);
    }
  }).triggerHandler(blur);

Az osztály hozzáadásához, és elvételéhez a removeClass és addClass parancsokat használja, ami akkor is működik, ha a blurClass illetve focusClass értéke üres, nem szükséges még egy feltételt csinálni a kódban.

Mező ürítése elküldéskor

A végére maradt a DOM elem tisztítása. Ennek célja, hogy ne küldje el a helykitöltő értékét, amennyiben a felhasználó anélkül küldené el az űrlapot, hogy a helykitöltővel ellátott mezőt kitöltené. A bejegyzés elején említett űrlapok esetén is ez a kívánt eljárás. Amennyiben mégis azt szeretnénk, hogy a helykitöltő elköldésre kerüljön, ahhoz clearOnSubmit beállítást kell hamisra állítani.

clearValues = function () {
  inputs.each(function () {
    // Minden elemen végig kell mennünk `each`-csel
    // mivel a `triggerHandler` csak az első elemen hajtja végre az eseményt.
    $(this).triggerHandler(focus);
  });
};
inputs.closest('form').bind('submit' + iph, clearValues);
$(window).bind('unload' + iph, clearValues);

Itt is használunk pár trükköt. Mivel a triggerHandler csak a kiválasztás első elemére fut le, ezért egy each ciklusban hajtjuk végre a törlést, de azt se akárhogyan, egyszerűen meghívjuk a focus eseményt, ami mindezt megteszi a számunkra mindenféle ellenőrzéssel egyetemben. A mezőt tartalmazó form elemhez hozzákapcsolunk egy submit eseménykezelőt (természetesen ezt is megfelelően névterezve), ami az űrlap elküldésekor fogja a mezőket üríteni. Valamint, hogy az automata mezőkitöltő böngészők se akarják a helykitöltővel kitölteni a mezőt, az ablak unload eseménye esetén is lefuttatjuk a mezők tisztítását.

Lezárás

Ami a végére maradt, az a jQuery objektum visszaadása, így téve lehetővé, hogy az utasításokat tovább láncoljuk, ahogy láttuk az eltávolítás esetén is. Remélem sikerült kellően részletesen bemutatni a kis kiegészítőt, ami ugyan nem túl hosszú, de kellő mennyiségű jQuery és JavaScript trükköt használ fel, valamint bemutatja, hogyan is érdemes kinéznie egy jQuery pluginnek.

comments powered by Disqus