Supponiamo di voler realizzare un widget per la ricerca che sia composto da un campo di testo con validazione e un bottone. L’utente dovrà avere la possibilità di inserire del testo ma questo non deve contenere alcun numero all’interno.

Gli elementi HTML di cui avremo bisogno saranno un input per inserire il testo, un button per eseguire il controllo sui numeri e uno spazio che dovrà essere di default invisibile da dedicare ai messaggi di avviso.

Cominciamo quindi a mettere i tre elementi dentro un div contenitore per tenerli raggruppati e poter piazzare il nostro widget ovunque all’interno della pagina senza perderne il controllo.

<div class="search-widget">
  <input type="text"/>
  <button>Check</button>
</div>

Per quanto riguarda il JavaScript definiamo una classe che SearchComponent che accetti in input il dom del nostro widget e definisca quindi i comportamenti agli elementi interni.

function SearchComponent (widgetDom) {
  var input = widgetDom.querySelector("input"),
      button = widgetDom.querySelector("button");
  button.onclick = function () {};
  input.onkeypress = function () {};
}

Al click del bottone usiamo una semplice regex per controllare se ci sono presenti numeri all’interno del testo inserito e nel caso aggiungere una classe CSS al widget per gestire l’errore.

function SearchComponent (widgetDom) {
  var errorClass = " error", // we have to use some space to concat CSS classes
      input = widgetDom.querySelector("input"),
      button = widgetDom.querySelector("button");
  button.onclick = function () {
    widgetDom.className = widgetDom.className.replace(errorClass); // remove previous validation
    if (input.value.match(/[0-9]/)) {
      widgetDom.className += errorClass;
    }
  };
  input.onkeypress = fucntion () {};
}

Aggiungiamo anche un controllo sul campo di testo nel caso in cui l’utente voglia premere invio anzichè usare il bottone. In questo caso possiamo sfruttare la definizione dell’evento sul bottone per invocarlo senza riscriverlo. Ciò ci permette facilmente di gestire la validazione in un unico punto soltanto.

function WidgetSearch (widgetDom) {
  var errorClass = "error", // setting up some default classes for validation
      successClass = "success",
      input = widgetDom.querySelector("input"), // find elements inside the current widget
      button = widgetDom.querySelector("button");
  button.onclick = function () {
    widgetDom.className = widgetDom.className.replace(errorClass, "").replace(successClass, ""); // remove previous validation classes
    if (input.value.length === 0) {
      return; // don't perform validation if there is no text to validate
    }
    if (input.value.match(/[0-9]/)) { // regex validation
      widgetDom.className += " " + errorClass; // add a css class to handle error
    } else {
      widgetDom.className += " " + successClass;
    }
  };
  input.onkeypress = function (event) {
    if (event.keyCode === 13) { // 13 is the key code for return button
      var click = new Event("click"); // define the click event programmatically
      button.dispatchEvent(click) // fire the event on the button simulating the user click
    }
  }
}

Le funzionalità del nostro widget sono ora pronte e rimane da gestire la messaggistica della validazione.

<div class="widget-search">
  <div class="input">
    <input type="text"/>
    <button>Check</button>
  </div>
  <div class="message">
    <div class="message-error">There are numbers in the text.</div>
    <div class="message-success">The text is clear.</div>
  </div>
</div>

Impacchettando gli elementi HTML possiamo meglio gestirne gli stili usando dei selettori CSS parlanti.

Definiamo un set di regole semplici per avere una base di grafica che possa rispecchiare le specifiche dell’esercizio.

Mettiamo il campo di testo e il bottone allineati uno vicino all’altro e i messaggi sotto.

.input * {margin-right: -5px;}  // stick the inputs together
.input input {border-radius: 10px 0 0 10px;}  // add some cool style
.input button {border-radius: 0 10px 10px 0;}
.message * {display: none;}  // the messages
.message-error {color: red;}
.message-success {color: green;}
.error .message-error {display: block;} // show relative message to validation result
.success .message-success {display: block;}

Il nostro widget è finalmente completato nella sua struttura. Per poterlo usare nella nostra applicazione ora dobbiamo solo invocarlo al caricamento della pagina istanziando un nuovo widget di tipo WidgetSearch.

window.onload = function () {
  var widget = document.querySelector(".widget-search");
  new WidgetSearch(widget)
}

Ci sono ora un paio di regole che devono essere tenute a mente usando un componente così definito con CSS e JavaScript.

Gli stili che abbiamo creato potrebbero essere troppo generici all’interno della nostra applicazione, quindi il consiglio è quello di usare, per tutto ciò che non vogliamo sia condiviso con altri elementi HTML in pagina, una root riferita al contenitore per limitare il più possibile overrides.

.widget-search .input * {margin-right: -5px;}
.widget-search .input input {border-radius: 10px 0 0 10px;}
.widget-search .input button {border-radius: 0 10px 10px 0;}
.widget-search .message * {display: none;}
.widget-search .message-error {color: red;}
.widget-search .message-success {color: green;}
.widget-search .error .message-error {display: block;}
.widget-search .success .message-success {display: block;}

Inoltre se volessimo usare più volte nella stessa pagina questo componente dobbiamo tenere presente che va istanziato un nuovo WidgetSarch per ogni elemento. Quindi dovremo aggiungere un ciclo per scorrere tutti i widget in pagina e inizializzarli.

window.onload = function () {
  var widgets = document.querySelectorAll(".widget-search");
  widgets.forEach(function(widget) {
    new WidgetSearch(widget)
  })
}

Infine potremmo creare un sistema automatizzato di inizializzazione di tutti i possibili widget in pagina passando per i data attribute.

Aggiungiamo un data-widget al nostro componente in modo da passargli il valore della classe JavaScript da istanziare e cambiamo il ciclo dentro window.onload per leggere tutti i data-widget in pagina.

<div class="widget-search" data-widget="WigetSearch">
  ...
</div>

window.onload = function () {
  var widgets = document.querySelectorAll("[data-widget]");  // get all elements with that data attribute
  widgets.forEach(function (widget) {
    var className = widget.dataset.widget;  // read the data attribute value
    new window[className](widget);  // get the class from the container of all of them, from window if it's defined at global level, otherwhise other variables
  })
}