Vylepšení upload formuláře k získání obrázku z URL

11. února 2010

V tomto článku si ukážeme, jak v ASP.NET rozšířit již existující formulář pro upload o možnost stažení obrázku ze zadané URL.

Upload obrázku, který uživatel vyhledá na svém lokálním disku a vloží do políčka formuláře, je známá technika a řada dnešních aplikací ji nabízí. V článku Jak stáhnout obrázek ze zadané URL máme příklad, jak stáhnout obrázová data. Zde si ukážeme, jak formulář, který klasicky obsahuje pole pro nalistování souboru pro upload, vylepšit tak, aby bylo možné zadávat i URL. A to vše při zachování pokud možno co nejvyšší přístupnosti.

Předpokládáme, že chceme rozšířit nějakou starší aplikaci/službu, formulář s dříve hojně využívaným upload políčkem tedy již v naší aplikaci nejspíše máme. Formulář také doplníme o náhled obrázku před odesláním, budeme se proto muset poprat s tím, že z bezpečnostních důvodů jsou možnosti práce s obsahem pole pro výběr souboru omezené. Náhled obrázku a korektní práce s jednotlivými políčky bude zajištěna klientským skriptem – i přesto formulář zůstane funkční i bez JavaScriptu a tedy přístupný a použitelný. Existují však rozdíly v implementaci vstupního pole pro výběr souboru v různých prohlížečích, a tak v některých prohlížečích náhled lokálního souboru ani nebude možný.

Příklad rozšířeného formuláře

Formulář tedy bude obsah klasické textové pole pro zadání URL, pole pro výběr lokálního souboru, náhledový obrázek a tlačítko pro odeslání a též mnohdy zatracované Reset tlačítko. Reset tlačítko jsem se rozhodl doplnit proto, aby bylo možné jednoznačně zrušit volbu obrázku a to i v prohlížeči, který neumožňuje políčko smazat klávesnicí (např. FireFox).

Pro zjednodušení vynechávám části týkající se stylů (CSS), ukázka ke stažení je nastylovaná většinou in-line stylopisem z demonstrativních důvodů, rovněž přiřazení obslužných funkcí událostem jsem vepsal přímo do HTML ze stejného důvodu.

<form name="frmImage" id="frmImage" method="post" action="FetchImageForm.aspx" enctype="multipart/form-data">
  <div>

    <fieldset>
      <legend>Náhled obrázku</legend>
      <div>
        <img src="Spacer.gif" id="imgUploadPreview" height="160" title="Preview" onload="enableSubmit();" />
      </div>
    </fieldset>

    <fieldset>
      <legend>Zdroj obrázku</legend> 
      <div>
        <label for="inputUrl">Internetová adresa</label>
        <br />
        <input name="inputUrl" id="inputUrl" type="text" onpropertychange="previewImage(this);" title="Zadejte URL obrázku." onchange="previewImage(this);" style="width:620px;" />
      </div>
      <div>
        <label for="inputImg">Soubor v tomto počítači</label>
        <br />
        <input name="inputImg" id="inputImg" type="file" size="64" onpropertychange="previewImage(this);" onchange="previewImage(this);" title="Vyberte soubor obrázku." />
      </div>
    </fieldset>

    <div>
      <input type="submit" name="btnSubmit" value="Odeslat" id="btnSubmit" />
      <input type="button" value="Nové zadání" title="Vyčistit formulář pro nové zadání." onclick="resetForm(this.form);" />
    </div>

  </div>
</form>

Podpůrné funkce Javascriptu

Nejprve dvě jednoduché. Funkci enableSubmit() volá obsluha události onload náhledového obrázku. Tak se zajistí, že tlačítko pro odeslání bude povoleno až po načtení náhledu obrázku – a tedy ověření, že obrázek vůbec je dostupný.

function enableSubmit()
{
  var btnSubmit = document.getElementById('btnSubmit');
  if (btnSubmit)
  btnSubmit.disabled = false;
}

Funkce resetForm() zde podporuje vymazávací tlačítko tak, aby i po jeho stisku byl zrušen náhled obrázku.

function resetForm(formObj)
{
  formObj.reset();
  previewImage(formObj.inputImg);
  previewImage(formObj.inputUrl);
}

Nejsložitější funkcí je previewImage(), která zajišťuje zobrazení náhledu obrázku a také jednoznačné určení typu zdroje obrázku – URL nebo lokální soubor. Myšlenka je taková, že jakmile vyplním jeden prvek, druhý prvek je zakázán a nelze jej dále použít dokud již použitý prvek nevymažu. Funkce je volána jako obsluha událostí onchange a poněkud nestandardní, avšak funkční (v IE) onpropertychange.

Funkce tedy musí zajistit povolování/zakazování jednoho z prvků v závislosti na tom, s kterým prvkem uživatel pracuje. Prvek, který je povolen, pak také dává údaje pro zobrazení náhledu obrázku.

Při návrhu jsem se musel poprat s tím, že políčko pro upload je z bezpečnostních důvodů vždy read-only a jeho obsah tedy nelze skriptem ani vymazat. Jediné co můžeme, je přinutit uživatele políčko smazat ručně anebo mu dát k dispozici již zmíněné tlačítko Reset.

Dalším problémem je fakt, že pro náhled lokálního souboru potřebujeme celou cestu umístění obrázku na disku. Z bezpečnostních (a pochopitelných) důvodů však většina prohlížečů předává pouze název souboru. Pouze IE a to ještě při povolení v nastavení zabezpečení dává korektně celou cestu použitelnou pro náhled. Ostatní případy bylo nutné nějak „hacknout“, a tak se mi podařilo vytvořit skript, který:

  • ověří, jestli políčko pro upload podporuje vlastnost (kolekci) files – pokud ano, pokusí se získat binární data vybraného obrázku metodou getAsDataURL() – funguje ve FireFoxu 3.6+.
  • pokud hodnota políčka neobsahuje znak „:“ anebo obsahuje řetězec „\fakepath\“ (IE) nebo „\fake_path\“ (Opera), pak prohlížeč neudává korektní cestu obrázku pro náhled, proto:
    • se pokusí políčko pro upload celé označit pomocí selection.createRange() a označený text zkopírovat – funguje v IE a v některých starších verzích FireFoxu.
    • poté políčko zase „odznačit“ a to buď pomocí document.selection.empty(); nebo window.getSelection().removeAllRanges(); podle toho, co prohlížeč podporuje.
  • v ostatních případech bohužel nastaví obrázek náhledu na „Unknown.gif“, zde bohužel náhled lokálního obrázku nebude k dispozici (Opera, Safari, Chrome…).
  • všechny potenciálně kolizní pokusy jsou ošetřeny přes try-catch konstrukci, aby se předešlo úplnému selhání funkcí formuláře a chybovým hlášením.

Funkce rozlišuje, které z políček ji volá, a proto pokud je obsah políčka korektní, zároveň zakáže druhé políčko. Pokud je políčko vyprázdněno, funkce naopak povolí druhé políčko, aby uživatel opět mohl použít obě. Totéž se stane při použití tlačítka Reset.

Proměnnou isLocked zajišťujeme, aby metoda byla i při vícenásobném vyvolání událostí zavolána pouze jednou nebo pouze tehdy, pokud již zrovna neprobíhá, bráníme se tímto zámkem vícenásobnému spuštění.

var isLocked = false;
function previewImage(pathObj)
{
  if (!isLocked)
  {
    isLocked = true;
    var btnSubmit = document.getElementById('btnSubmit');
    var inputImage = document.getElementById('inputImg');
    var inputUrl = document.getElementById('inputUrl');
    var preview = document.getElementById('imgUploadPreview');

    if (btnSubmit)
      btnSubmit.disabled = true;

    if (pathObj.value.length > 4)
    {
      if (pathObj == inputImage)
      { // upload políčko; získat cestu k obrázku pro náhled nebude jednoduché
        if (inputImage.files)
        {//podporuje kolekci files
          try
          { // pokus převzít pro náhled obrázku binární data zakodovaná do podoby URL
            preview.src = inputImage.files.item(0).getAsDataURL();
          }
          catch(err)
          { // nezdařilo se, náhled neznámý
            preview.src = 'Unknown.gif';
          }
        }
        else if (pathObj.value.indexOf(':') == -1 || pathObj.value.indexOf(':\\fakepath\\') || pathObj.value.indexOf(':\\fake_path<a href="file://'">\\'</a>))
        { // prohlížeč nevrací korektní cestu k obrázku
          try
          { // pokus o označení políčka a získání textu obsahujícího celou cestu
            pathObj.select();
            preview.src=document.selection.createRange().text;
            if (preview.src.length != 0)
            {// zřejmě se zdařilo, tak ještě po sobě uklidit - odznačit políčko
              if (document.selection) // v IE
                document.selection.empty();
              else if (window.getSelection) // ve FireFoxu
                window.getSelection().removeAllRanges();
            }
            else
            { // políčko se nezdařilo označit, náhled neznámý
              preview.src = 'Unknown.gif';
            }
          }
          catch(err)
          {
            preview.src = 'Unknown.gif';
          }
        }
        else
        { // prohlížeč vrátil korektní cestu pro náhled, rovnou ji použijeme
          preview.src = pathObj.value;
        }
      }
      else // u textového políčka pro URL hodnotu jednoduše rovnou použijeme
      preview.src = pathObj.value;
    }
    else
      preview.src = 'Spacer.gif';
    if (pathObj == inputImage)
    { // volá událost pole inputImg, není-li prázdné, protějšek inputUrl zakážeme
      if (pathObj.value.length==0)
        inputUrl.disabled = false;
      else
        inputUrl.disabled = true;
      inputUrl.value = '';
    }
    else if (pathObj==inputUrl)
    { // volá událost pole inputUrl, není-li prázdné, protějšek inputImg zakážeme
      if (pathObj.value.length==0)
        inputImage.disabled=false;
      else
        inputImage.disabled = true;
    }
    isLocked = false;
  }
}

Kompletní aplikace by měla určitě obsahovat validátor a splňovat řadu dalších požadavků. Inspirovat se můžete také na The 8 Best Image Uploading Websites & Applications.

Určitě by se formulář dal udělat úplně ultra-cool s nějakým frameworkem jako jQuery, o to mi ovšem nešlo – chtěl jsem vytvořit poctivý, přístupný a použitelný základ – a teprve tento nechť si každý vyšperkuje alternativní „lepší“ funkcionalitou avšak závislou na klientském skriptování. Uvedený formulář totiž i přes prošpikování skriptem zůstává funkční i při kompletním vypnutí JavaScriptu – obrázek lze na server nahrát i bez náhledu a jediné co na serveru musíme ošetřit je stav, kdy by někdo vyplnil obě políčka, a tedy zvolit, kterému z obrázků dát přednost.

Kód formuláře si můžete stáhnout zde.

Mohlo by vás také zajímat

Nejnovější

1 komentář

  1. Murdej

    Úno 15, 2010 v 8:30

    Jen drobnost k : v ceste k souboru.
    Na jiných os než windows se nemusí být nutně : v plné cestě k souboru ale teoreticky může být i v názvu souboru

    Odpovědět

Napsat komentář: Murdej Zrušit odpověď na komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *