Při zobrazování tabulkových údajů je pro uživatele pohodlnější, pokud si může zvolit řazení řádků podle vybraného sloupce. V článku si ukážeme univerzální klientský skript, který můžeme aplikovat na běžně používané typy tabulek.

Abychom zbytečně nevynalézali vynalezené, upravíme pro české prostředí jeden z oblíbených skriptů, dostupných na internetu. Použijeme skript sorttable ze serveru kryogenix.org, který je sám založen na starším skriptu Sortable Table ze serveru WebFX.

Původní skript je velmi kvalitní, má však několik menších problémů. Skript například umí rozpoznat typ údajů – textové, číselné, datové nebo měnu – poslední dva zmíněné ovšem neznají český formát, takže bylo nutno upravit detekci typu údaje a také způsob třídění (u datového formátu). Druhým problémem je, že původní skript prováděl třídění mimo prvního nad všemi řádky tabulky, takže nebylo lze mít v zápatí nějaký souhrnný řádek. Do původního skriptu jsem proto doplnil mechanismus, kterým můžeme libovolný řádek i sloupec z třídění vyjmout. Dalším problémem byla nemožnost třídění textu s českými diakritickými znaky, bylo tedy potřeba doplnit vlastní způsob pro třídění – JavaScript sám o sobě řadí diakritické znaky až za nediakritické znaky. Prohlédněte si ukázku (zdrojový kód).

Aplikování skriptu do tabulky sestává z několika kroků. Prvním je prosté zavedení skriptu do stránky:

 <script type=“text/javascript“ src=“ClientScripts/SortTable.js“></script>

Dále je potřeba do tabulky doplnit potřebné atributy – tabulka musí mít nastavenu třídu sortable a k řádkům a sloupcům, které nemají být tříděny, je nutno přidat třídu dontsort. Vlastnosti těchto tříd pochopitelně můžeme libovolně měnit podle požadovaného stylu, jejich název je však nutné s ohledem na skript dodržet.

Zde je ukázka tabulky, kde poslední dva sloupce nemají umožňovat třídění a poslední dva řádky tvoří zápatí tabulky, které také nemá být tříděno:

<table class=“sortable“ id=“Datagrid1″>
  <tr>
    <td>Název souboru</td>
    <td>Datum vytvoření</td>
    <td>Velikost</td>
    <td class=“dontsort“>Editace</td>
    <td class=“dontsort“>Výmaz</td>
  </tr>
 <tr>
    <td>20030829tip_s.jpg</td>
    <td>9.8.2003 13:24:11</td>
    <td>1538</td>
    <td><img src=“Images/Edit.gif“ alt=“Editovat“ /></td>
    <td><img src=“Images/Delete.gif“ /></td>
  </tr>
  <tr>
   <td>asdfi.png</td>
    <td>15.5.2003 20:38:14</td>
    <td>3470</td>
    <td><img src=“Images/Edit.gif“ alt=“Editovat“ /></td>
    <td><img src=“Images/Delete.gif“ alt=“Smazat“ /></td>
  </tr>
.
.
  <tr class=“dontsort“>
   <td><input name=“ctl13:myFile“ id=“ctl13_myFile“ type=“file“ /></td>
    <td>&nbsp;</td>
    <td >&nbsp;</td>
    <td>&nbsp;</td>
    <td>&nbsp;</td>
  </tr>
  <tr class=“dontsort“>
    <td colspan=“5″>1</td>
 </tr>
</table>

Dále si popíšeme funkce v upraveném skriptu – jak jsem zmínil, bylo potřeba vyřešit několik problémů s českým formátem data, ale i tříděním, a umožnit netřídění vybraných řádků a sloupců:

 function sortables_init()
{
  if (!document.getElementsByTagName)
    return;
  tbls = document.getElementsByTagName(„table“);
  for (ti=0;ti<tbls.length;ti++)
  {
    thisTbl = tbls[ti];
    if (((‚ ‚+thisTbl.className+“).indexOf(„sortable“) != -1) && (thisTbl.id))
    {
      ts_makeSortable(thisTbl);
    }
  }
}
function ts_makeSortable(table)
{
  if (table.rows && table.rows.length > 0)
  {
    var firstRow =table.rows[0];
  }
  if (!firstRow)
    return;
  var cell;
  var txt;
  for (var i=0;i<firstRow.cells.length;i++)
  {
    cell = firstRow.cells[i];
    if (cell.className != ‚dontsort‘)
    {
     txt = ts_getInnerText(cell);
      cell.innerHTML = ‚<a href=“#“ class=“sortheader“ onclick=“ts_resortTable(this); return false;“>’+txt+'<span class=“sortarrow“></span></a>‘;
    }
  }
}
function addEvent(elm, evType, fn, useCapture)
{
  if(elm.addEventListener)
  {
    elm.addEventListener(evType, fn, useCapture);
    return true;
  }
  else if (elm.attachEvent)
  {
    var r = elm.attachEvent(„on“+evType, fn);
    return r;
  }
 else
  {
    alert(„Handler could not be removed“);
  }
}
var SORT_COLUMN_INDEX;
addEvent(window, „load“, sortables_init);

Skript nejprve „nachystá“ tabulky pro třídění. Nejprve pomocí vlastní funkce addEvent() přidá na událost onload spuštění funkce sortables_init(), která projde všechny tabulky v dokumentu. První řádky sloupců tabulek, které mají přidánu třídu sortable, jsou následně zpracovány funkcí ts_makeSortable(). Ta vytvoří u těch, které nemají nastavenu třídu dontsort, z textového obsahu odkaz pro třídění. Je zde také deklarována globální proměnná SORT_COLUMN_INDEX, která ve všech funkcích udává, s kterým sloupcem se právě pracuje.

function trim(myText)
{
  if (myText != null)
  {
    while (myText.indexOf(„. „) != -1 || myText.indexOf(„: „) != -1 || myText.indexOf(„\n“) != -1)
    {
      myText = myText.replace(/\. /,“.“);
     myText = myText.replace(/\: /,“.“);
      myText = myText.replace(/\s /,““);
    }
  }
  return myText;
}

Funkci trim() jsem dopsal pro dokonalejší detekci a třídění data – pokud jsou v datu mezi jednotlivými údaji mezery, nelze dost dobře vytvořit datum typu Date a třídění nelze provést. Tato funkce ze zadaného data mezery i znaky nového řádku vyřadí.

 function ts_getInnerText(el)
{
  if (typeof el == „string“)
    return el;
  if (typeof el == „undefined“)
    return el;
  if (el.innerText)
    return el.innerText; //Not needed but it is faster
  var str = „“;
  varcs = el.childNodes;
  var l = cs.length;
  for (var i = 0; i < l; i++)
  {
    switch (cs[i].nodeType)
    {
      case 1: //ELEMENT_NODE
        str += ts_getInnerText(cs[i]);
     break;
      case 3: //TEXT_NODE
        str += cs[i].nodeValue;
      break;
    }
  }
  return str;
}

Pomocí ts_getInnerText() je při třídění získáván textový obsah buňky tabulky – jak vidíme, funkce je schopná dohledat text i v případě, že je obalen nějakými elementy.

 function findValidRowInColumn(table,column)
{
  var ir = 1;
  for (ir=1 ;ir < table.rows.length; ir++)
  {
    if (table.rows[ir].cells[column] != undefined && table.rows[ir].cells[column].className != ‚dontsort‘ && ts_getInnerText(table.rows[ir].cells[column]).length > 0&& ts_getInnerText(table.rows[ir].cells[column]) != “ „)
      return ir;
  }
  return 0;
}
function getParent(el, pTagName)
{
  if (el == null)
    return null;
  else if (el.nodeType == 1 && el.tagName.toLowerCase() ==pTagName.toLowerCase())
    return el;
  else
    return getParent(el.parentNode, pTagName);
}

Funkce findValidRowInColumn() slouží k nalezení řádku se smysluplným obsahem – podle něj následující funkce ts_resortTable() stanoví způsob, jakým údaje ve sloupci třídit. Cyklem procházíme řádky od druhého do konce tabulky, přičemž cyklus pokračuje do té doby, dokud není nalezen řádek, který obsahuje nějaký text, neobsahuje pouze mezeru a nemá nastavenu třídu dontsort.

function ts_resortTable(lnk)
{
  var span;
  for (var ci=0;ci<lnk.childNodes.length;ci++)
  {
    if (lnk.childNodes[ci].tagName && lnk.childNodes[ci].tagName.toLowerCase() == ‚span‘)
     span = lnk.childNodes[ci];
  }
  var spantext= ts_getInnerText(span);
  var td = lnk.parentNode;
  var column = td.cellIndex;
  var table = getParent(td,’TABLE‘);
  if (table.rows.length <= 1)
    return;
  var vr = findValidRowInColumn(table,column);
  var itm =ts_getInnerText(table.rows[vr].cells[column]);
  if (itm.match(/^\d{1,2}[\/.\/-][ ]?\d{1,2}[\/.\/-][ ]?(\d{2}|\d{4})([ ]{0,3}\d{1,2}[:]{1}\d{1,2}([ ]{0}|[:]{1}\d{1,2}))?$/))
    sortfn = ts_sort_date;
  else if (itm.match(/^(([€L\$])|kč |Kč |KČ ){1}.*|.*(([€L\$])|kč |Kč |KČ ){1}$/))
   sortfn = ts_sort_currency;
  else if (itm.match(/^[\d\,]+$/))
    sortfn = ts_sort_numeric;
  else
    sortfn = ts_sort_caseinsensitive;
  SORT_COLUMN_INDEX = column;
  var firstRow = new Array();
  var newRows = new Array();
  varnoSortRows = new Array();
  firstRow[0] = table.rows[0];
  var tableRowIndex = 1;
  var newRowIndex = 0;
  var noSortIndex = 0;
  while (tableRowIndex < table.rows.length)
  {
    if (!table.rows[tableRowIndex].className ||(table.rows[tableRowIndex].className && (table.rows[tableRowIndex].className != ‚dontsort‘)))
    {
      newRows[newRowIndex] = table.rows[tableRowIndex];
      newRowIndex++;
    }
    else
   {
      noSortRows[noSortIndex] = table.rows[tableRowIndex];
      noSortIndex++;
    }
    tableRowIndex++;
  }
  newRows.sort(sortfn);
  if (span.getAttribute(„sortdir“) == ‚down‘)
  {
   ARROW = ‚&nbsp;&nbsp;&uarr;‘;
    newRows.reverse();
    span.setAttribute(‚sortdir‘,’up‘);
  }
  else
  {
    ARROW = ‚&nbsp;&nbsp;&darr;‘;
    span.setAttribute(‚sortdir‘,’down‘);
  }
  for(i=0;i<noSortRows.length;i++)
    newRows[newRows.length] = noSortRows[i];
  for (i=0;i<newRows.length;i++)
    table.tBodies[0].appendChild(newRows[i]);
  var allspans = document.getElementsByTagName(„span“);
  for (var ci=0;ci<allspans.length;ci++)
   if (allspans[ci].className == ‚sortarrow‘)
      if (getParent(allspans[ci],“table“) == getParent(lnk,“table“))
        allspans[ci].innerHTML = “;
  span.innerHTML = ARROW;
}

Funkce ts_resortTable() vykonává setřídění obsahu tabulky. Jejím úkolem je určit, na který sloupec bylo kliknuto, podle něj rozpoznat typ obsahu sloupce, zavolat odpovídající třídící funkci a nakonec zajistit vykreslení správné „šipky“ do setříděného sloupce. Podle odkazu, na který bylo kliknuto, se dohledá element span, ve kterém je text odkazu, dále se dohledá nadřízená buňka a tabulka. Z buňky získáme vlastností cellIndex index odpovídajícího sloupce a dále již popsanými funkcemi najdeme smysluplný řádek pro následné rozhodnutí, jakým způsobem budou řádky tříděny.

Pro stanovení třídící funkce jsou použity regulární výrazy. Nejprve se pokusíme porovnat, zda údaj neodpovídá některému z možných formátů data (může být zadán včetně času se sekundami nebo bez a to časem před nebo za datem). Pokud nejde o formát data, zkusíme, zda nejde o formát měny. Následně zkusíme, zda nejsou zadána číselná data. Pokud není údaj ani číselný, budeme jej považovat za běžný text. Následuje cyklus, který prochází všechny řádky tabulky mimo záhlaví a mimo řádků, které nemají být do třídění zahrnuty. Vznikne tak kolekce řádků, která má být následně setříděna, a zvlášť kolekce netříděných řádků. Na kolekci pro třídění je později aplikována třídící funkce.

Dále je rozhodnuto o tom, jakým směrem mají být data setříděna – je k tomu využit vlastní atribut sortdir, který je přidáván k elementu span, užitém pro zobrazení indikační šipky v odkazu pro třídění sloupce. Pokud je atribut přítomen a má hodnotu down, je na kolekci tříděných řádků ještě uplatněna metoda Reverse(), která otočí pořadí kolekce setříděných řádků. Při rozhodování o výsledném pořadí je do proměnné ARROW připraven také symbol indikační šipky. Do tabulky jsou poté nově přidány setříděné řádky a netříděné řádky. Nakonec je ze všech elementů span, které mají nastavenu třídu sortarrow, odebrán znak indikační šipky a do elementu span, odpovídajícímu zvolenému sloupci, je přidán znak šipky z proměnné ARROW.

Funkce pro třídění jednotlivých typů údajů přijímají jako vstupní parametry dva sousední řádky. Výsledkem je vždy hodnota udávající, zda je první řádek větší, menší nebo roven druhému řádku:

 function ts_sort_date(a,b)
{
  var aaDateTime;
  if (a.cells[SORT_COLUMN_INDEX] != undefined && a.cells[SORT_COLUMN_INDEX].className != ‚dontsort‘ && ts_getInnerText(a.cells[SORT_COLUMN_INDEX]).length > 0 && ts_getInnerText(a.cells[SORT_COLUMN_INDEX]) != “ „)
  {
    aa =ts_getInnerText(a.cells[SORT_COLUMN_INDEX]);
    var aaList = trim(aa).split(“ „);
    var aaDateList;
    var aaTimeList;
    if (aaList[0].indexOf(„.“) != -1)
    {// první je datum
      aaDateList= aaList[0].split(„.“);
      if (aaList[1] != null && aaList[1].indexOf(„:“) != -1)
      { // rozbrakovat čas
        aaTimeList = aaList[1].split(„:“);
        if (aaTimeList.length >2)
          aaDateTime = new Date(aaDateList[2],(aaDateList[1]-1),aaDateList[0],aaTimeList[0],aaTimeList[1],aaTimeList[2]);
        else
          aaDateTime = newDate(aaDateList[2],(aaDateList[1]-1),aaDateList[0],aaTimeList[0],aaTimeList[1],0);
      }
      else
      { // jenom datum
        aaDateTime = newDate(aaDateList[2],(aaDateList[1]-1),aaDateList[0]);
      }
    }
    else
    { // první je čas
      aaTimeList = aaList[1].split(„:“);
      aaDateList = aaList[0].split(„.“);
     if (aaTimeList.length > 2)
        aaDateTime = new Date(aaDateList[2],(aaDateList[1]-1),aaDateList[0],aaTimeList[0],aaTimeList[1],aaTimeList[2]);
      else
        aaDateTime = newDate(aaDateList[2],(aaDateList[1]-1),aaDateList[0],aaTimeList[0],aaTimeList[1],0);
    }
  }
  else
    aaDateTime = new Date(1900,0,1);
  var bbDateTime;
  if (b.cells[SORT_COLUMN_INDEX] != undefined && b.cells[SORT_COLUMN_INDEX].className !=’dontsort‘ && ts_getInnerText(b.cells[SORT_COLUMN_INDEX]).length > 0 && ts_getInnerText(b.cells[SORT_COLUMN_INDEX]) != “ „)
  {
    bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]);
    var bbList = trim(bb).split(“ „);
    var bbDateList;
   var bbTimeList;
    if (bbList[0].indexOf(„.“) != -1)
    {// první je datum
      bbDateList = bbList[0].split(„.“);
      if (bbList[1] != null && bbList[1].indexOf(„:“) != -1)
      { //rozbrakovat čas
        bbTimeList = bbList[1].split(„:“);
        if (bbTimeList.length > 2)
          bbDateTime = newDate(bbDateList[2],(bbDateList[1]-1),bbDateList[0],bbTimeList[0],bbTimeList[1],bbTimeList[2]);
        else
          bbDateTime = new Date(bbDateList[2],(bbDateList[1]-1),bbDateList[0],bbTimeList[0],bbTimeList[1],0);
     }
      else
      { // jenom datum
        bbDateTime = new Date(bbDateList[2],(bbDateList[1]-1),bbDateList[0]);
      }
    }
    else
    {// první je čas
      bbTimeList = bbList[1].split(„:“);
      bbDateList = bbList[0].split(„.“);
      if (bbTimeList.length > 2)
        bbDateTime = newDate(bbDateList[2],(bbDateList[1]-1),bbDateList[0],bbTimeList[0],bbTimeList[1],bbTimeList[2]);
      else
        bbDateTime = new Date(bbDateList[2],(bbDateList[1]-1),bbDateList[0],bbTimeList[0],bbTimeList[1],0);
    }
  }
 else
    bbDateTime = new Date(1900,0,1);
  if (aaDateTime == bbDateTime)
    return 0;
  else if (aaDateTime < bbDateTime)
    return -1;
  else
    return 1;
}

Funkce pro třídění datového sloupce ts_sort_date() je nejkomplikovanější, protože datum může být zadáno v mnoha různých podobách. Nejprve se rozhodne, zda je jako první zadáno datum nebo čas. U času je potřeba dále rozhodnout, zda je uveden včetně sekund nebo jen v minutách. Již dříve popsanou funkcí trim() odstraníme případné nadbytečné mezery a pomocí metody split() rozdělíme údaj nejprve podle zbylé mezery na datum a čas (pokud je obsažen, což zjistíme ověřením, zda se v něm vyskytuje znak dvojtečky pomocí indexOf()). Tyto údaje dále dělíme na jejich jednotlivé složky, ze kterých nakonec vytvoříme nové datum konstruktorem new Date(). Pokud se nepodaří získat z údaje datum, vytvoříme datum 1. 1. 1900, aby nedošlo při porovnávání k chybě. Nakonec data prostě porovnáme a vrátíme výsledek porovnání.

function ts_sort_currency(a,b)
{
  aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]).replace(/[^0-9.]/g,“);
  bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]).replace(/[^0-9.]/g,“);
  return parseFloat(aa) – parseFloat(bb);
}
function ts_sort_numeric(a,b)
{
  if(a.cells[SORT_COLUMN_INDEX] != undefined && a.cells[SORT_COLUMN_INDEX].className != ‚dontsort‘ && ts_getInnerText(a.cells[SORT_COLUMN_INDEX]).length > 0 && ts_getInnerText(a.cells[SORT_COLUMN_INDEX]) != “ „)
    aa = parseFloat(ts_getInnerText(a.cells[SORT_COLUMN_INDEX]));
  else
   aa = 0;
  if (b.cells[SORT_COLUMN_INDEX] != undefined && b.cells[SORT_COLUMN_INDEX].className != ‚dontsort‘ && ts_getInnerText(b.cells[SORT_COLUMN_INDEX]).length > 0 && ts_getInnerText(b.cells[SORT_COLUMN_INDEX]) != “ „)
    bb = parseFloat(ts_getInnerText(b.cells[SORT_COLUMN_INDEX]));
 else
    bb = 0;
  return aa-bb;
}

Při třídění podle měny ts_sort_currency() prostě z údaje sloupce zahodíme všechny nečíselné znaky metodou replace() a vrátíme rozdíl údajů, pro jistotu ještě předem opracovaných metodou parseFloat(). U sloupců měny jsem nepředpokládal, že by v některém řádku údaj chyběl, proto v ní není prováděno žádné důkladné testování. (Kdo by potřeboval, může také tuto funkci vycházející z původního skriptu, doplnit o otestování, obdobně jako ve funkci pro třídění numerických údajů.)

Třídění numerických údajů ts_sort_numeric() funguje stejně jako třídění podle měny, jen je vynecháno nahrazení symbolů měny. Funkci jsem oproti původnímu skriptu ale doplnil o důkladnější otestování, zda je v daném řádku smysluplný údaj a zda není řádek označen jako netříditelný.

 function char2Diacritic(transDiacritic)
{
  var charDiacritic = „ÁČĎÉÍĹĽŇÓÔŔŘŠŤÚŮÝŽ“;
  var numDiacritic = „ACDEILLNOORRSTUUYZ“;
  var tmpDiacritic = „“;
  var newDiacritic = „“;
  transDiacritic = transDiacritic.toUpperCase();
  for(i=0;i<transDiacritic.length;i++)
 {
    if (charDiacritic.indexOf(transDiacritic.charAt(i))!=-1)
      tmpDiacritic += numDiacritic.charAt(charDiacritic.indexOf(transDiacritic.charAt(i)))+’|‘;
    else
      tmpDiacritic += transDiacritic.charAt(i);
 }
  return tmpDiacritic;
}

Funkce char2Diacritic() nám poslouží při třídění textů s českou diakritikou. Protože JavaScript nijak nepodporuje třídění podle znaků národních abeced, navrhnul jsem si vlastní postup – diakritický znak v řetězci nahradím za dva znaky, znak bez diakritiky a znak svislé čáry. Pokud pak provádíme třídění, diakritické znaky jsou zatříděny správně za své nediakritické protějšky.

function ts_sort_caseinsensitive(a,b)
{
  var aa = „_“;
  var bb = „_“;
  if (a.cells[SORT_COLUMN_INDEX] != undefined && a.cells[SORT_COLUMN_INDEX].className != ‚dontsort‘ && ts_getInnerText(a.cells[SORT_COLUMN_INDEX]).length > 0)
    aa =char2Diacritic(ts_getInnerText(a.cells[SORT_COLUMN_INDEX]));
  if (b.cells[SORT_COLUMN_INDEX] != undefined && b.cells[SORT_COLUMN_INDEX].className != ‚dontsort‘ && ts_getInnerText(b.cells[SORT_COLUMN_INDEX]).length > 0)
  bb = char2Diacritic(ts_getInnerText(b.cells[SORT_COLUMN_INDEX]));
  if(aa==bb)
    return 0;
  else if (aa < bb)
    return -1;
  else
    return 1;
}

K plné funkci nám chybí už jen ts_sort_caseinsensitive(), kterou je zajištěno třídění běžných textů. Funkce ověří, že je k dispozici smysluplný obsah a řádek je možno třídit, a poté jsou prostě porovnány řetězce opracované již zmíněnou funkcíchar2Diacritic().

Je celkem jasné, že moje úpravy nejsou vším, čím by se dala původní myšlenka zdokonalit a vylepšit. Já jsem si upravil pouze to, co jsem potřeboval pro zamýšlené použití tohoto skriptu v tabulkách, generovaných ovládacími prvky v ASP.NET.

Pozn. šéfred.: Autora skriptu „sorttable“ jsem s nabídkou spolupráce kontaktoval koncem roku 2003, bez jakékoli odezvy. Protože jsem považoval tento skript za potenciálně velmi užitečný čtenářům i autorům Interval.cz, požádal jsem o jeho úpravu jednoho z našich specialistů. Shodou okolností jsem těsně před publikací tohoto článku dostal tématicky související nabídku od jiného autora, takže máte možnost zvolit si ze dvou různých řešení to, které vám bude vyhovovat lépe.

Související články

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

2 Příspěvků v diskuzi

  1. Dobry den,
    vyzkousela jsem uvedeny script, ale neradi mi spravne desetinna cisla (muzou byt i zaporna). Poradte, co s tim udelat. Cisla script radi textove.
    Toto je vzorek dat:
    9.75
    9.5
    9.25
    8.25
    65.5
    6.5
    58.25
    56.25
    5.5
    5.25
    44.25
    39.75
    37.5
    36.25
    31.5
    30.75
    3.75
    3.5
    3.5
    3
    29.5
    22.75
    204
    2.75
    2
    18
    17.5
    15.25
    14.25
    14
    12.25
    11.5
    10.25
    0.75
    0.25
    0
    -8
    -7.5
    -6.75
    -6.5
    -3.5
    -3
    -25.5
    -23.25
    -2

    Dekuji za pomoc.

  2. Mám pocit, že je to tím, že to sestupně třídí položky jako řetězce a nikoliv jako čísla – znak 9 je první (ať je za ní cokoliv) a pak je třeba 58, protože začíná 5, ta je v „abecedě“ níž – stejně a tak až za 9 – je to stejné jako u písmen B a A – tak nejdřív budou všichni Bartolomějové, Bedřichové… a pak teprve Alfrédové a Angeliky (a je úplně jedno, jak je jméno dlouhé nebo co znamená – pro počítač to jsou znaky a ty třídí podle „abecedy“, které říká ASCII tabulka)

Odpovědět