Pokud jste až doteď vnímali webový prohlížeč pouze jako aplikaci pro brouzdání po netu, pak tento článek nepochybně převrátí váš svět programovaní zcela naruby.
Před časem jsem se dostal k ne příliš zajímavému projektu, který měl následující zadání. Mělo jít o něco, co bude možné provozovat ve Windows bez možnosti připojení k Internetu, bude to mít jeden formulář, na kterém bude asi padesát otázek, přičemž na každou z nich je možné odpovědět výběrem jedné ze 4 odpovědí (pro všechny otázky stejných, řekněme něco na způsob „vůbec/středně/hodně/nevím“) s možností textové poznámky. Aplikace měla umožňovat vytisknout si celý formulář nebo jeho část na papír pro vyplnění „nanečisto“. Po vyplnění formuláře v aplikaci se měla vytvořit zpráva o výsledku, pro kterou se nad zadanými daty měla provést triviální operace, například odstranění otázek s odpovědí „nevím“. Tuto zprávu mělo být možné vytisknout, uložit a nebo poslat e-mailem. (Zadání projektu jsem pro účely tohoto článku mírně zjednodušil, ve skutečnosti šlo o kombinaci odpovědí ze tří takových formulářů vyplňovaných na různých strojích a k vytváření výsledné zprávy docházelo až po doplnění daty ze čtvrtého.)
Použití některého z kompilovaných jazyků mi přišlo pro tento účel jako používat dělo na zabití komára, nemluvě o tom, že vytváření zmíněného formuláře v kterémkoliv IDE mi přišlo příliš zdlouhavé a jeho dynamické generování zase příliš složité. Rovněž jsem v rámci soudobých módních trendů považoval za nezbytné, aby shromážděná data byla ukládána ve formě XML. Vytváření zprávy a jednoduché operace nad daty by se pak daly zvládnout v XSLT a výstup by mohl být v XHTML. Jak prosté! Zůstával pouze problém, jak donutit uživatele otevřít Notepad a zapsat odpovědi v XML.
Naštěstí pro uživatele jsem nedávno narazil na standard konsorcia W3C nazývaný XForms, který umožňuje na (X)HTML stránce definovat formulář, jehož vyplněním vznikne přímo XML na rozdíl od v současnosti běžného formátování dat vyplněných vk (X)HTML formulářích jako [klíč]=[hodnota]. Použití XForms bez webového serveru umožňuje fakt, že kromě klasických metod odeslání, jako je POST (kdy je XML obsaženo vk těle požadavku) a GET (snaha zakódovat XML do URL), znají XForms také metodu PUT, tedy přímý zápis souboru do souborového systému přes HTTP, FTP nebo rovnou na lokální HDD.
Protože výše zmíněná aplikace měla běžet pod Windows, ideální se zdála varianta XHTML stránky obsahující XForms, umístěné na lokálním disku, kterou uživatel otevře ve svém Internet Exploreru, po vyplnění stiskne tlačítko „funguj!“ a XML uložené pomocí PUT na disk bude s pomocí nějakého skriptovacího jazyka (říkejme mu třeba JScript) transformováno XSLT šablonou do XHTML.
Jenomže standardy W3C jsou jedna věc a jejich implementace v Internet Exploreru je zase věc druhá, takže ačkoliv Firefox již delší dobu obsahuje modul pro interpretaci XFroms, Internet Explorer tagy tohoto standardu prostě ignoruje. Byly tedy možné dvě varianty:
- Aplikace poběží ve Firefoxu, který se na PC nainstaluje v rámci instalace celé aplikace (vznosný to název pro kupku tagovaného textu se skripty).
- Aplikace poběží vk Internet Exploreru pomocí některé zk implementací XForms pro klientskou stranu.
Tenkrát jsem z určitých důvodů zvolil variatu 2 a pro interpretaci XForms posloužil plugin do Internet Exploreru od Novellu, který se prostě jednou na stroj nainstaluje a tento prohlížeč od toho momentu již rozumí XForms bez potřeby jakýchkoliv úprav. Toto řešení dostalo přednost před implementací XForms vk JScriptu (FormFaces), protože ta se při předběžných testech ukázala na mém postarším stroji neúnosně pomalou. Proti řešení pomocí Flash (Deng) hovořil ten fakt, že se mi tuto variantu nepodařilo vůbec rozchodit.
Na Widnows 2000 by dosud zmíněná aplikace skutečně bez větších problémů běžela, ale
korektně nastavená XP budou mít překvapivě problém s bezpečností skriptů v „XHTML stránce“ a protože lokální
disk PC nelze překvapivě nastavit jako „důvěryhodnou zónu“ (pamatujte, že i když nejste paranoidní, nezanamená to, že po vás nejdou…), je potřeba o něco robustnější řešení. To spočívá
v použití technologie HTA, tedy „HTML Application“, což znamená, že
do zmíněné XHTML stránky s XForms doplníme do sekce HEAD
tag HTA:APPLICATION
a celý
soubor uložíme s rozšířením „.hta“ namísto „.html“, což má za následek to, že Windows konečně začnou považovat kód za důvěryhodný a kromě toho můžeme vyšperkovat okno aplikace ikonkou systémového menu a dalšími drobnostmi.
<HTA:APPLICATION ID="dotaznik"
APPLICATIONNAME="Dotazník"
ICON="dotaznik.ico"
SINGLEINSTANCE="yes"
SCROLL="no"
MAXIMIZEBUTTON="no"
BORDER="dialog"
SELECTION="no"
/>
Abychom se nedostali jen z deště pod okap, reprezentovaného faktem, že plugin od Novellu zase neumí interpretovat XFroms v HTA, ale pouze v (X)HTML, je potřeba udělat poslední krok v návrhu a vyřešit tak nezbytné oddělení skriptů a XForms. Ten spočívá v konceptu úvodní stránky s několika tlačítky, která bude uložena jako „důveryhodná“ HTA a bude obsahovat veškerou logiku v JScriptu, přičemž jedno z tlačítek bude otevírat v novém okně Internet Exploreru XHTML stránku (HTA skutečně vždy otvírá nové okno v Internet Exploreru bez ohledu na nastavení výchozího prohlížeče systému) s XForms formulářem a další tlačítka budou otevírat jiná okna se zobrazenou výslednou zprávou či dialogem pro uložení výsledné zprávy nebo tisknout výslednou zprávu a rovněž budou umožňovat vyčištění formuláře od předchozího zadání nebo tisknout textovou šablonu formuláře pro vyplnění nanečisto.
Pro elegantní vzhled je potřeba tagu BODY
doplnit obsluhu
události ONLOAD
o změnu velikosti okna, kterou přímo tag HTA z nějakých důvodů neumožňuje.
.
.
function init() {
window.resizeTo(300,350);
}
.
.
<body onload="init()">
.
.
Nyní už k vlastnímu formuláři. Pro jednodušší ovládání jej doplníme o možnost základního filtrování. Dynamicky generovaná menu pro výběr kapitoly (barvy, roční období, jídla) nebo přímo jednotlivých otázek se zobrazí pouze pokud je to potřeba, tedy pokud je daná možnost (kapitoly, otázky) vybrána. Zde můžete narazit na první závažnou chybku pluginu, která spočívá v tom, že pokud nastavíte výchozí třídění na jiné než 'vše', tedy na cokoliv, co zahrnuje dynamické generování seznamu dle dat z XML, pak rozbalovací seznam bude prázdný. Dalším detailem, kterým usnadníme uživateli práci s formulářem, bude jeho automatické ukládání po každém přesunu mezi ovládacími prvky, takže jej nebudeme nutit mačkat nějaké tlačítko 'Uložit'. Textbox pro věk je uveden jako příklad vstupu, na který nemá zvolené filtrování vliv. Nebudu překládat specifikaci XForms, která je snadno pochopitelná se základy angličtiny. Pro rychlý start může posloužit i dokument XForms for HTML Authors.
Formulář také ukládá nastavení filtrů z instance variables, do souboru actual_variables.xml tak, aby v souboru variables.xml zůstalo zachováno výchozí nastavení filtrů na 'vše', což je nutný workaround kvůli výše popsanému bugu.
<variables>
<questionCode>1</questionCode>
<chapterCode>I</chapterCode>
<actualQuestionCode>OS</actualQuestionCode>
<actualChapterCode>I</actualChapterCode>
<filterOn>none</filterOn>
</variables>
Kdyby se totiž uchovávalo nastavení filtrů, tak by se uživateli mohl zobrazit prázdný seznam. Ze souboru actual_variables.xml se pak zjišťuje nastavení filtrů pro tisk textové šablony formuláře, která tak přebírá poslední nastavení fitrování z prohlížení formuláře a protože v momentě okamžitě po otevření formuláře uživatel vidí filtr nastaven na 'vše' (dle variables.xml) a v actual_variables.xml by bylo obsaženo ještě nastavení filtru na formuláři při jeho posledním zavření, je nutná ještě před otevřením formuláře synchronizace formou přepsání souboru actual_variables.xml vzorovým souborem variables.xml tak, aby to, co je vidět, souhlasilo s tím, co je uloženo.
var objFSO = new ActiveXObject("Scripting.FileSystemObject")
var objFile = null;
try {
objFile = objFSO.GetFile('variables.xml')
objFile.copy('actual_variables.xml')
} catch (e) {
window.alert('Nastala neočekávaná chyba při kopírování souboru variables.xml. '+e.message);
}
Zobrazení textové šablony formuláře je pak snadné. Postačí triviální XSLT transformace a obsah okna rovnou posíláme na tiskárnu. Uživatel tak má možnost vyplnění nanečisto.
Vytváření výsledné zprávy z uložených dat bude o něco obtížnější. Jednak musíme provést nějakou základní kontrolu validity dat a za druhé zajistit předání stavu zaškrtávátka z úvodní obrazovky do XSLT šablony.
function createReport() {
try {
var xmlSummary = new ActiveXObject("MSXML2.DOMDocument.4.0"); //new ActiveXObject('Microsoft.XMLDOM');
xmlSummary.async = false;
xmlSummary.validateOnParse = false;
xmlSummary.load('data.xml');
if (xmlSummary.selectSingleNode('/quest/responder/age').text == '')
throw new Error ("Není zadán věk odpovídajícího.");
var sOutputType = null;
var invalidQuestions = null;
invalidQuestions = xmlSummary.selectNodes('//question[rating/grade=\'\']');
if (invalidQuestions.length > 0) {
throw new Error ("Nebyly zodpovězeny všechny otázky.");
} else {
if (badOnly.status) {
sOutputType = 'bad'
} else {
sOutputType = 'all'
}
}
var xsl = new ActiveXObject("MSXML2.FreeThreadedDOMDocument.4.0")
xsl.async = false
xsl.load('result.xsl')
var xslt = new ActiveXObject("Msxml2.XSLTemplate.4.0")
xslt.stylesheet = xsl
var xslproc = xslt.createProcessor()
xslproc.input = xmlSummary
xslproc.addParameter("outputType", sOutputType)
xslproc.transform()
var result = xslproc.output
var objFSO = new ActiveXObject("Scripting.FileSystemObject")
var objFile = objFSO.CreateTextFile('report.html')
objFile.write(result)
objFile.close()
return xmlSummary;
} catch (e) {
window.alert('Výslednou zprávu se nepodařilo vytvořit. '+e.message)
return null;
}
}
.
.
<input type="checkbox" id="badOnly" checked="checked" value="">Ve výstupu zobrazit pouze otázky zodpovězené odpovědí 'vůbec'</input>
.
.
Ačkoliv výslednou zprávu je možné tisknout přes klávesovou zkratku Ctrl + P, mnoha uživatelům by toto činilo problémy, a proto jim poskytneme ještě tlačítko na úvodní obrazovce.
function printReport() {
if (createReport()) {
wndReport = window.open('report.html','report','scrollbars=yes,resizable=yes,status=yes');
wndReport.defaultStatus = 'Tuto sestavu lze tisknout stisknutím Ctrl + P'
wndReport.status = wndReport.defaultStatus;
wndReport.focus();
wndReport.print();
} else {
window.alert('Výsledek nelze vytisknout.')
}
}
Ukládání výsledné zprávy má jedno drobné úskalí a tím je fakt, že pokud chceme v (X)HTML mít například obrázek, musíme ho mít ve zvláštním souboru a pokud by uživatel poslal někomu samotné (X)HTML e-mailem, pak by bez obrázku nevypadalo dobře. Pokud ukládáte v Internet Exploreru nějakou (X)HTML stránku, můžete ji jednak uložit jako (X)HTML plus složka s ostatními soubory, se kterou je pak Windows schopné provádět všechny automaticky všechny operace, které provádíte s HTML souborem, takže pokud jej někam kopírujete, pak kopírujete HTML soubor i se složkou. Druhá možnost, jak uložit komplexní (X)HTML stránku, je formát MHTML s rozšířením „.mht“. Jde o zakódování více souborů do jednoho pomocí standardu MIME. Stejný postup se používá i v případě, že posíláte v Outlooku e-mail a místo formátu „prostý text“ zvolíte formát „HTML“. Proto také pokud přejmenujete soubor „.mht“ vytvořený Internet Explorerem na rozšíření „.eml“, tak si můžete uloženou HTML stránku i s obrázky prohlížet v Outlooku. Uvedený postup funguje i naopak, čehož využijeme.
Abychom se nemuseli s MIME patlat sami, použijeme objekty ze skupiny CDO (Collaborative Data Objects), které jsou v použitelné verzi přítomné na každých Windows 2000. De facto vytvoříme HTML e-mail jako EML soubor a uložíme jej s kocovkou „.mht“, což způsobí, že jej Windows budou otevírat v Internet Exploreru. Do souboru přidáme ještě jako bonus datový XML soubor, což umožní případné strojové zpracování odpovědí uložených v MHT souborech. V Internet Exploreru jej neuvidíme, ale pokud přejmenujeme „.mht“ na „.eml“, pak jej uvidíme v Outlooku jako přílohu. Soubor MHT lze dokonce otevřít v Microsoft Wordu. Na Windows XP jsou CDO také, ale mají drobný, leč dosti zásadní, bug, pro který je potřeba stáhnout patch.
function saveReport() {
var filePath = selectFolder()
var sState = 'vytváření výsledné zprávy';
if (filePath != '') {
try {
var xSummary = createReport();
if (!xSummary) {
throw 'Nebylo možné vytvořit report.html';
}
var sReportFile = filePath+'Výsledná zpráva.mht';
var sPath = dotaznik.commandLine.substring(1, dotaznik.commandLine.lastIndexOf('\\')+1);
sState = 'vytváření objektu zprávy';
var iMsg = new ActiveXObject("CDO.Message");
sState = 'vytváření objektu konfigurace';
var iCfg = new ActiveXObject("CDO.Configuration");
sState = 'nastavení konfigurace';
iCfg.Fields("http://schemas.microsoft.com/cdo/configuration/sendusing") = 1;
iCfg.Fields("http://schemas.microsoft.com/cdo/configuration/smtpserverpickupdirectory") = filePath;
iCfg.Fields.Update();
sState = 'nastavení zprávy';
iMsg.Configuration = iCfg;
iMsg.AutoGenerateTextBody = false;
sState = 'přidávání souborů do MHT';
iMsg.CreateMHTMLBody('file://'+sPath+'report.html', 0);
iMsg.AddAttachment('file://'+sPath+'data.xml');
sState = 'ukládání zprávy do MHT';
iMsg.GetStream().SaveToFile(sReportFile, 1) // 1= create AND 2 = overwrite
window.alert('Výsledek byl uložen do "'+sReportFile+'".');
} catch(e) {
window.alert('Soubor "'+sReportFile+'" nebylo možné uložit. '+e.message+' Chyba nastala ve stavu "'+sState+'". Chyba může být způsobena tím, že soubor tohoto jména již existuje. Zkuste jej v takovém případě nejprve smazat a pak uložení opakujte.');
}
} else {
window.alert('Výsledek nelze uložit.');
}
}
Zdrojové kódy a nezbytné knihovny
Novell XForms Explorer – plugin interpretující XForms v IE6 od Novellu
aplikace.zip – zdrojový kód aplikace zde popsané
Reference
Top 10 XForms Engines – souhrn existujících implementací XForms
Ten Favorite XForms Engines – starší souhrn existujících implementací XForms
XForms Essentials – online verze klíčové literatury k XForms
XForms for HTML Authors – dokument od W3C pro snadný start sXForms, three ways – článek o 3 implementacích XForms