Ukládání draftu komentářů
Taky vás dokáže naštvat, když delší dobu píšete komentář, dáváte si záležet na každé formulaci a pak se něco stane a o celý komentář přijdete? To něco může být chyba serveru, vaše nepozornost při zavírání oken nebo třeba agresivní WiFi, která jakýkoliv pokus o nahrání stránky přesměruje na svůj přihlašovací formulář.
Jako tvůrce aplikace tomu můžete zabránit ukládáním pracovní verze komentáře do nějakého úložiště. Komunikace se serverem po napsání každého znaku by byla příliš drahá, cookies nemají dostatečnou kapacitu a práce s nimi je nepohodlná, ale dá se použít úložiště localStorage. Je dostatečně velké (obvykle 5 MB na doménu) a ukládá se na klientovi, takže práce s ním je rychlá a nezabírá místo na serveru.
Kód je celkem přímočarý:
<textarea id="comment" name="comment" rows="20" cols="80"></textarea>
<script type="text/javascript">
if (window.localStorage) {
var comment = document.getElementById('comment');
var key = 'comment:' + location.pathname + location.search;
comment.onkeyup = function () {
localStorage.setItem(key, this.value);
};
if (!comment.value) {
comment.value = localStorage.getItem(key);
}
}
</script>
Kód funguje správně, pokud je na stránce jen jeden komentář. Pokud jich máme více, tak každému musíme přiřadit unikátní identifikátor, což ostatně můžeme udělat tak jako tak. URL stránky jsem zvolil jen v zájmu o obecnost a často to nemusí být nejlepší adept.
Ke kódu mám pár poznámek:
- Pokud uživatel vloží data ze schránky pomocí myši, tak se text neuloží, protože nedojde ke stisku klávesy. Pokud vám to vadí, můžete implementovat událost
onpaste
nebo data ukládat v pravidelném intervalu.
- Když si uživatel otevře tu stejnou stránku vícekrát, načte se mu do oken stejný obsah. Také se zachrání jen poslední text, který upravil. Nenapadá mě moc, jak by se to dalo řešit.
- Před nastavením komentáře se kontroluje, jestli je prázdný. To je proto, že některé prohlížeče za určitých ukolností zachovají obsah formulářů i po obnovení stránky (např. Firefox, pokud je povoleno ukládání stránky do keše). To řeší alespoň část předchozího bodu.
Zbývá smazání draftu po uložení finální verze. Mohlo by svádět to udělat v onsubmit
, to by ale nepokrylo několik problematických scénářů. Skript, který provádí zápis do databáze, obvykle nic nevypisuje a jen provede přesměrování. Tam to tedy taky udělat nemůžeme. Můžeme si ale nastavit session proměnnou, kterou zkontrolujeme na stránce, kam uživatele přesměrujeme:
<?php
if (isset($_SESSION["comment_saved"])) {
unset($_SESSION["comment_saved"]);
?>
<script type="text/javascript">
if (window.localStorage) {
var key = 'comment:' + location.pathname + location.search;
localStorage.removeItem(key);
}
</script>
<?php } ?>
Klíč by bylo vhodné inicializovat na centrálním místě, v ukázce jsem to rozdělil jen kvůli pochopitelnosti.
Diskuse
Michal:
Jak tak koukám, funguje to i zde na webu. Nicméně se neukládá poslední znak. (Chrome Verze 31.0.1650.63 m)
18.12.2013 00:48:00
Díky za upozornění, opravil jsem to. Byla použitá starší verze kódu.
18.12.2013 00:59:02
Michal:
U mě chyba stále přetrvává.
18.12.2013 10:04:59
Díky za vytrvalost. Starší verze kódu byla dokonce i v článku. Je potřeba použít událost onkeyup, nikoliv onkeypress.
18.12.2013 17:40:20
Michal:
Již ok
18.12.2013 18:33:54
Michal:
Stejně tak zůstává text ve storage, pokud jej vyberu a smažu naráz pomocí Backspace či Delete.
18.12.2013 00:52:09
Díky za upozornění, opravil jsem to. Byla použitá starší verze kódu.
18.12.2013 00:59:13
Myslím, že by to chtělo, jak je napsáno v článku, rovnou něco více než onkeypress, hlídání klávesy Delete a podobně. Text je také třeba možné vyjmou do schránky, což se v současném stavu neprojeví (zůstane uložená varianta před vyjmutím).
Poznámku 2 může být zajímavé řešit synchronisací napříč stránkami. Potom by situace, kdy si několik stejných stránek bude navzájem přepisovat zálohu, neměla nastat.
18.12.2013 01:47:59
Synchronizace napříč stránkami je ve většině případů nežádoucí. Můžu si chtít otevřít tu stejnou stránku dvakrát a psát v nich dva různé komentáře (třeba reakce na dva různé příspěvky).
18.12.2013 05:47:36
Potom by ale neměl být problém to zohlednit. A ukládat příspěvek do klíče „url-stranky-reakce-na-xxx“.
Jinak synchronisace se používá třeba na Diskusi JPW při focusu a bluru u <textarea>. Funguje to docela dobře.
18.12.2013 20:47:54
Michal:
I tato chyba zůstává. Z cache to není.
18.12.2013 10:06:20
Díky, i tohle už by mělo fungovat.
18.12.2013 17:42:15
Michal:
Taktéž v pořádku.
18.12.2013 18:34:11
Jan Tojnar:
Ukládání stavu formuláře sice usnadní spoustě lidem práci, ale myslím si, že by se o to, stejně jako o změnu velikosti pole pomocí táhla, měl starat spíše prohlížeč. Tím se obejdou zmiňované problémy a na všech stránkách bude mít uživatel konzistentní prostředí.
Programátor by se měl zaměřit hlavně na možnost obnovení odeslání formuláře (mimo jiné i na předvyplnění polí v případě znovuodeslání požadavku s již neplatným CSRF tokenem), pokud dojde k selhání.
V současnosti funkci ukládání polí nabízí třeba rozšíření Lazarus (
http://getlazarus.com/).
18.12.2013 01:35:26
Jan Tojnar:
Nicméně dokud tato funkce nebude dostatečně rozšířena, tak proč ji neponechat.
18.12.2013 01:36:52
Werkov:
Zcela souhlasím s prvním příspěvkem.
U toho druhého je to trochu filozofický/sociální problém. Pokud bude funkce ponechána, nebude to možná nutit vývojáře prohlížečů potažmo jejich uživatele mít tuto featuru v prohlížeči.
2.1.2014 17:26:55
Hever:
Ve wbových aplikacích můžou být formuláře generované dynamicky/javascriptem, v tom případě prohlížeč pomoct nemůže a je potřeba, aby to řešila aplikace.
10.1.2014 16:01:02
paranoiq:
více stejných formulářů na jedné URL by šlo řešit tak, že by se jako klíč použil celý stav formuláře krom samotného inputu (např. když ve skrytém inputu je id příspěvku, ke kterému komentář píšu)
18.12.2013 09:21:24
Pěkný příspěvek. Zrovna jsem si nedávno všiml, že text zapsaný ve vlákně BaseCampu zůstane, i když zavřete prohlížeč a vrátíte se jindy. Říkal jsem si, že to asi řeší přes cookies, localStorage mě nenapadlo. Článek mi ušetřil dlouhé zkoumání, děkuji :-)
18.12.2013 09:36:09
O:
Neni lepsi to proste ukladat kazdych 10s? Pak nam odpadaji uplne vsechny problemy s udalostma, onkeypress, on paste atd atd.
18.12.2013 12:36:24
Já to nemám rád z několika důvodů:
1. Při ladění občas používám funkci zastavení při dalším spuštění kódu (umí to Firebug). Hodí se to třeba k ladění toho, jaká událost se vyvolá. Časovače tohle dost znepříjemňují.
2. Někdy si text napíšu vedle, zkopíruji a do prohlížeče vložím najednou. Když se něco pokazí, než se text stihne uložit, tak jsem přišel o celý text.
3. Někdy třeba opravím překlep uvnitř komentáře. Kontrolovat, jestli se tahle změna už uložila nebo ještě ne, je dost otrava.
18.12.2013 17:49:16
O:
1. True, asynchroni veci se spatne debuguji.
2. True, ale v tom pripade to mas ulozeny vedle. Nejde o to mit uplne posledni pismenko, kdyz ti spadne prohlizec. Ale o to zachranit co se da. 10s textu kazdy dopise znova.
3. Nic se nekontroluje, proste kazdych 10s ulozis cely text v komentari, je to akce klienta, server se nezatezuje (aspon tvuj pripad, jinak by se to pres ajax asi posilalo na server)
Podle me tento zpusob dela i google a vubec skoro vsude, v podstate sem ten tvuj zpusob nikde snad ani nevidel. Jak je to v FB?
18.12.2013 23:51:48
2. Jde o to, že vedle už to můžu mít zavřené.
3. Jako autor: Dopíšu komentář, opravím překlepy, spadne to. Příště se to nahraje znova, ale není jasné, které opravy se ještě stihly uložit a které ne.
Třeba Google Keep ukládá data okamžitě, ne až po timeoutu. FB to pokud vím neukládá vůbec.
20.12.2013 04:11:43
Visitor:
Obě metody by šly zkombinovat. Ukládat na onKeyUp a zároveň co 5 sekund (pro ostatní případy).
20.12.2013 13:08:33
Michal Sänger:
Ještě by šlo použit SessionStorage
19.12.2013 20:10:54
To se smaže, když se zavře prohlížeč nebo panel.
20.12.2013 18:19:43
myšnekočklokanutrie:
Díky za článek. Už jsem se s tím někde setkal (memá to náhodou Wikipedie?).
Já osobně bych volil ukládání po určitém časovém intervalu a nejspíš bych i hlídal přepisování stejného obsahu - myslím, že by to bylo méně přesunů dat a řešilo by to i to vložení myší.
Ad otevření ve více oknech) šlo by to vyřešit následujícně - php náhodně vygeneruje ID HTML elementu (místo "comment" např. "comment77864", a zapamatuje si to) a do Javascriptu se vloží kód ad hoc pro toto ID (pro použití v samostatném souboru by se mohl kód napsat jako funkce a předávat id jako parametr).
20.12.2013 00:11:43
Když otvírám novou stránku a něco je uložené: Jak poznám, že chci psát nový komentář nebo obnovit ten starý protože něco spadlo?
Když něco spadlo a komentářů mám uložených víc: Který obnovit?
Bylo by potřeba k tomu udělat uživatelské rozhraní.
20.12.2013 18:26:57
NoxArt:
To mi právě přijde jako dobrá volba, mít roletku a možnost vybrat si, co obnovit. Něco v tom smyslu má myslim Word a zdá se, že i výše zmiňovaný Lazarus.
Taky by to řešilo bod #3, tam je problém, že jsme mohli naopak nějaké pole vymazat a tudíž by v zapamatovaných byla špatná informace. Pokud je polí hodně, uživatel si nemusí všimnout (tedy neřeším momentálně komentáře s jediným polem, ale obecně formuláře).
22.12.2013 11:29:12
a ten paste by sa dal vyriešiť cez input event. Takže takto aj so synchronizáciou medzi tabmi a s paste udalosťou:
<textarea id="comment" name="comment" rows="20" cols="80"></textarea>
<script type="text/javascript">
if (window.localStorage) {
var comment = document.getElementById('comment');
var key = 'comment:' + location.pathname + location.search;
if (!comment.value) {
comment.value = localStorage.getItem(key);
}
window.addEventListener('storage', function(e) {
if (e.key == key)
comment.value = e.newValue;
}, false);
window.addEventListener('input',function(e){
if (e.srcElement == comment)
localStorage.setItem(key, e.srcElement.value);
}, false);
}
</script>
20.12.2013 02:22:41
Na
http://flowreader.com úspěšně používáme. Až na "synchronizaci" mezi tabama to hlídá veškerý vstup. Takže za mě LocalStorage +1.
2.1.2014 16:12:51
Hever:
Rozlišit formuláře více oken/tabů - k tomu mě napadá tak jedině to párovat s nějakým unikátním identifikátorem v url za křížkem # (location.hash).
10.1.2014 16:06:39
myšnekočklokanutrie:
To může být problém. Třeba jQuery UI tabs hash mění - a to je jen jeden příklad z mnoha.
11.2.2014 21:05:05