Javascriptin koodausohjeita

Näitä ohjeita voi käyttää portlettien kanssa tai aivan yhtä hyvin soveltaen kaikkialla muualla, missä Javascriptia käytetään.

Javascript on dynaaminen, heikosti tyypitetty skriptikieli, jonka ongelmat ilmenevät käännösvaiheen puuttuessa vasta ajovaiheessa (runtime errors). Heikko tyypitys on yksi syy näihin hankalasti löytyviin virheisiin. Javascriptin muuttujan tyyppi määräytyy sijoitettavan arvon perusteella ja vaihtelee, jos sijoitettavan arvon tyyppi vaihtelee.

var x = ”Kissa”; // nyt arvo on merkkijono ”Kissa”
x = 3; // nyt arvo on luku 3

Älä koodaa näin, vaan käytä selkeyden vuoksi tyyppikohtaisia muuttujia. Jos missään, niin nimenomaan Javascriptissa unkarilaisen notaation käyttö on perusteltua, vaikkakin tämän notaation käyttö ei ole enää suosiossa. Unkarilaista notaatiota voidaan käyttää myös rajoitetusti, esimerkiksi globaaleja muuttujia voi nimetä g-alkuisiksi (esim. gName), vaikka muuten ei notaatiota käyttäisikään.

Javascriptissa kannattaa käyttää systemaattisesti virhekäsittelijää, vaikka senkin käyttö on varsin vähäistä, nettisivujen esimerkeissä sitä ei juuri näy, ja suurissakin projekteissa se voi puuttua. Virhekäsittely ottaa hyvin kiinni käytönaikaiset virheet. Hyvä funktion perusrakenne on esimerkiksi seuraava:

/**
* Funktion kuvaus
*/
function metodinNimi(par1, par2)
{
  var f = ”metodinNimi”;
  try {
    // metodin koodi tähän
  }
  catch (e) { workErr(e, f); }
}

Edellä workErr on yleiskäyttöinen funktio (huom. tämä funktio siis pitää koodata), jossa käsitellään virhe. Virhe voidaan tulostaa käyttäjälle (alert) tai konsolin lokiin (console.log). Virheilmoitus saadaan: e.message. Voit myös heittää omia loogisia virheitä koodista Javan tyyliin: throw new Error(”virhekuvaus”).

Angularia käytettäessä on mietittävä palvelurajapinnan toteutus. Nimitämme tätä MVC-mallissa ”pikku-modeliksi”, koska se on kontrollerin osa, joka kutsuu itse modelia, eli portletin Ajax-toimintoja.

Tämä kontrollerin osa koostuu alla olevan kaltaisista metodeista, jotka ovat täysin sovelluskohtaisia, palvelukutsun parametrit voidaan tuoda funktion parametreina tai lukea suoraan js-muuttujista (kuten alla). Samoin tulos voidaan kirjoitaa suoraan js-muuttujiin. Metodin siirrettävyydestä ei siis tarvitse huolehtia.

function insertCompany()
{
  var f = ”insertCompany”;
  try {
    var req = { item : $scope.company }; // input
    var addr = gsInsertRowUrl; // portletin Ajax-kutsun osoite

    angService.callPost(addr, req, function(resp, status) // palvelukutsu
    {
      if (resp.state != gsSuccess) { throw new Error(resp.result); }
      setInfo(resp.result); // output, asynkroninen
    });
  }
  catch (e) { workError(e, f); }
}

Funktio, joka on sovelluskohtainen (luetaan ja kirjoitetaan sovelluskohtaisiin elementteihin), sisältää siis:
– toimintaa kuvaavan nimen
– input parametrien asetus (tämä on siis sovelluskohtainen, ei yleinen)
– itse palvelukutsu, joka on tässä yleinen (callPost)
– output arvojen asetus (tapahtuu asynkronisesti ja on jälleen täysin sovelluskohtainen)

Itse servicen toteutus on sensijaan yleinen edellä olevassa esimerkissä. On myös mahdollista tehdä oma Angular-service-tiedosto, tässä vaikkapa CompanyService, jolle olisi määritellyt metodit (tyypillisesti insert, delete, update ja select, esim. insertCompany). Tämä on kuitenkin turha taso, koska sovelluskohtaisesta metodista (edellä) selviää hyvin mistä palvelukutsusta on kyse.

Alla olevan täysin yleiskäyttöisen Post-metodin toteutus käy puolestaan mille tahansa RESTful-kutsulle:

angular.module(”angServiceMod”, [])
.service(”angService”, [ ”$http”, function($http)
{
  this.callPost = function (address, jsonData, callback)
  {
    var f = ”angService/callPost”;
    try
    {
      $http({
        method : ”post”,
        url : address,
        data : jsonData,
        headers : { ”content-type” : ”application/json” }
      })
      .success(callback)
      .error(function(resp) // common error-handler
      {
        log(”Error ” + f + ”: response: ” + resp.toString());
      });
    }
    catch (e)
    {
      workError(e, f + ”, inputs: address = [” + address + ”], jsonData = [” +
        jsonData.toString() + ”], callback = [” + callback + ”]”);
    }
  };
}]);

Kutsujen asynkronisyys on aina otettava huomioon. Yksi hyvä ohje on sijoittaa asynkroninen kutsu viimeiseksi tapahtumankäsittelyssä, jolloin ei tarvitse miettiä ratkaisuja tietyn käskyjen suoritusjärjestyksen varmistukseen. Mikäli asynkronisia metodeja joutuu ketjuttamaan, on helpointa tehdä seuraava kutsu edellisen paluuarvon käsittelyn jälkeen, edellä olevassa esimerkissä:


angService.callPost(addr, req, function(resp, status)
{
  if (resp.state != gsSuccess) { throw new Error(resp.result); }
  setInfo(resp.result);

  insertOrder(); // toinen kutsu lähtee, kun ensimmäinen on suoritettu
});

jQuerya käytettäessä voidaan RESTful-palvelujen kutsut hoitaa samoilla periaatteilla, yksinkertaisilla toistettavilla metodeilla. Alla oleva metodi hoitaa kaikki RESTful-kutsut, jossa parametrit ja paluuarvo ovat JSON-olioita. Virhekäsittely on tässä yleinen ja tähän koodattu, eikä sitä tarvitse erikseen koodata käyttökohteissa:

/**
* Common JQuery post call.
* @param address
* @param jsonReq
* @param callback
*/
function callPost(address, jsonReq, callback)
{
  var f = ”callPost”;
  try {
    jQuery.ajax(
    {
      type : ”post”,
      url : address,
      dataType : ”json”,
      data : jsonReq,
      success : callback,
      error : function(xhr, status, excep) // common error handler
      {
        console.log(”error ” + f + ”: [” + excep + ”]”);
      }
    });
  }
  catch (e)
  {
    workError(e, f + ”, input: address = [” + address + ”], jsonReq = [” +
      jsonReq.toString() + ”], callback = [” + callback + ”]”);
  }
}

Edellä olevaa jQuerya käyttävää metodia kutsutaan kuten Angularin vastaavaa, metodissa joka kapseloi parametrien asettamisen, RESTful-palvelun kutsun sekä asynkronisen paluuarvon käsittelyn. MVVM-frameworkin puuttuessa tiedot pitää kirjoittaa tarvittaessa suoraan HTML-elementteihin (lukeminen vastaavasti). Tästä esimerkki:

function getMessage(sWho, sMessage)
{
  var method = ”controllerOne/getMessage”;
  try {
    var req = { // input arvojen asetus
      who : sWho,
      message : sMessage
    };
    callPost(gsGetMessage, req, function(resp, status)
    {
      if (resp.state != gsSuccess) { throw new Error(resp.result); }
      elem(gsNamespace + ”txtHello”).value = resp.result;
    });
  }
  catch (e) { workError(e, method); }
}

Tähän loppuu sarjamme, joka käsitteli Liferayta, jQuerya, AngularJS:aa sekä SPA-arkkitehtuuria portleteissa. Edelleen Liferay 7 odotuttaa, samoin AngularJS, joka on – ikävä kyllä – laitettu täysin uusiksi. Meille ei ole avautunut ollenkaan, miksi erittäin toimiva ja hyvin suuren suosion saavuttanut AngularJS 1.X piti laittaa uusiksi. Tämä tulee aiheuttamaan suuria ylläpitokustannuksia niille Angularia laajasti käyttäneille yrityksille, jotka luottivat Googleen ja sen Angularin jatkuvuuteen. Onko mitään syytä luottaa jatkossa?

Tavoitteemme on ollut luoda jämäkkä, yksinkertainen, ja helposti ymmärrettävä ratkaisu SPA-portletille. Ratkaisu on täysin yleiskäyttöinen ja toistettava, ja sen arkkitehtuuri on mahdollisimman vähän sidoksissa Liferayn teknisiin ratkaisuihin, varsinkin serveripäässä. Kannattaa muistaa, että Liferay-alustan tekijöillä on puolestaan tavoitteena sitoa ratkaisu nimenomaan Liferayhyn mahdollisimman tiukasti.

Uusia blogeja on suunnitteilla, mutta tarkkaa aihetta tai aikataulua niiden suhteen ei vielä ole.