Contents tagged with Javascript

  • Offline Storage for Progressive Web Apps

    02/02/2018

    Offline Storage for Progressive Web Apps

    The Pokedex.org Progressive Web App uses IndexedDB for application state and the Pokemon data set while the Cache API is used for URL addressable resources.

    2016 will hopefully be the year we build for network resilience.

    Internet connections can be flakey or non-existent on the go, which is why offline support and reliable performance are common features in Progressive Web Apps. In this post, I’ll summarize some ideas around offline data storage for PWAs — think the JSON payloads, images and general static data required to provide a meaningful experience offline.

    A recommendation for storing data offline:

    For URL addressable resources, use the Cache API (part of Service Worker). For all other data, use IndexedDB (with a Promises wrapper).

    Some quick answers to common questions on why:

    • Both APIs are asynchronous (IDB is event based and the Cache API is Promise based). They also work in Web Workers, Window and Service Workers.
    • IDB is available everywhere. Service Workers (and the Cache API) are available in Chrome, Firefox, Opera and are in development for Edge.
    • While IDB doesn’t support Promises, several strong libraries giving us Promise wrappers exist. See below for recommendations. The API has mandatory complexity (transactions, schema versioning) that these libraries also try to smooth over where possible.
    • Native support for IDB Promises has been proposed as have observers.
    • How much can you store? In Chrome and Opera: Your storage is per origin (rather than per API). Both storage mechanisms will store data until the browser quota is reached. Apps can check how much quota they’re using with the Quota Management API. Firefox: no limits, but will prompt after 50MB data stored. Mobile Safari: 50MB max, Desktop Safari: unlimited (prompts after 5MB), IE10+ maxes at 250MB and prompts at 10MB. PouchDB track IDB storage behavior. Future facing: For apps requiring more persistent storage, see the on-going work on Durable Storage.
    • Safari 10 has fixed many long-standing IDB bugs in their latest Tech Previews. That said, some folks have run into stability issues with Safari 10’s IDB and PouchDB have found it to be a little slow. Until more research has been done here, YMMV. Please do test and file browser bugs so the folks @webkit and related OSS library authors can take a look. LocalForage, PouchDB, YDN and Lovefield use WebSQL in Safari by default due to UA sniffing (there wasn’t an efficient way to feature-test for broken IDB at the time). This means these libraries will work in Safari 10 without extra effort (just not using IDB directly).
    • URL addressable resources are typically static resources that surprisingly..live at a URL. For PWAs, you could cache the static files composing your application shell (JS/CSS/HTML files) in the Cache API and fill in the offline page data from IndexedDB. There are no hard and fast rules around this however. Some applications might be sufficiently simple that they can just use the Cache API alone while others may find it valuable to partially cache their JSON payloads in IDB so that in browsers without Cache API support you still get the benefit of some local caching during the session.
    • Debugging support for IndexedDB is available in Chrome (Application tab), Opera, Firefox (Storage Inspector) and Safari (see the Storage tab). Debugging IDB is not currently supported in Edge (however, it is possible to debug the underlying JetDB) — vote here for built in support.

    IndexedDB Libraries worth checking out

    • localForage(~8KB, Promises, good legacy browser support)
    • idb-keyval (<500 bytes, Promises, use if only need key-value support)
    • idb (~1.7KB, Promises, also does iteration, indexing)
    • Dexie (~16KB, Promises, complex queries, secondary indices)
    • PouchDB (~45KB (supports custom builds), synchronization)
    • Lovefield(relational)
    • LokiJS (in-memory)
    • ydn-db(dexie-like, works with WebSQL)

    Service Worker Libraries worth checking out

    • sw-toolbox (offline caching for dynamic/runtime requests)
    • sw-precache (offline precaching for static assets/application shells)
    • Webpack users can directly use the above or offline-plugin

    What about other storage mechanisms?

    • Web Storage (e.g LocalStorage) is synchronous, has no Web Worker support and is size-limited (only strings). Although ideas for async LS have been kicked around in the past, current focus is on getting IndexedDB 2.0in a good state. I would personally use IDB + a library.
    • Cookies have their uses but are synchronous, lack Web Worker support and are size-limited. In previous projects I’ve used js-cookie for handling cookies via JS (~800bytes gzipped). Support for an Async Cookies API is being sketched out right now with a polyfill in the works.
    • WebSQL is asynchronous (callback-based), however it also has no Web Worker support and was rejected by Firefox and Edge but is in Chrome and Safari.
    • File System API is asynchronous too (callback-based) and does work in Web Workers and Windows (albeit with a synchronous API). Unfortunately it doesn’t have much interest outside of Chrome and is sandboxed (meaning you don’t get native file access).
    • File API is being improved over in the File and Directory Entries API and File API specs. A File API library exists and for file-saving, I’ve been using FileSaver.js as a stop-gap. The writable-files proposal may eventually give us a better standards-track solution for seamless local file interaction.

    Current and future offline storage work

    If offline storage interests you, the below efforts are worth keeping an eye on. I’m particularly excited about Promises support in IndexedDB being possible without the need for a library.

    IDB with some sweet await goodness.

    Resources

    Offline storage isn’t quite magical and an understanding of the underlying APIs will go far in helping you make the most out of what we now have available. Whether you prefer to directly use these APIs or work with an abstraction library, take some time to get familiar with your options.

    Hopefully this will help craft an offline experience that makes your PWA ✨

    With thanks to Nolan Lawson, Joshua Bell (whose work on Open Web Storage and BlinkOn talk heavily inspired this article), Jake Archibald, Dru Knox and others for their previous work in the web storage space.

    Update September 2nd 2016 with some more FAQ:

    What eviction gotchas exist for IndexedDB and the Cache API? Is it possible to currently guarantee that a response will always be available offline?

    An origin is given an amount of space to do with as it pleases. This free space is shared across all forms of origin storage (IndexedDB, Cache API, localStorage etc). This amount given isn’t specified and will vary depending on device and storage conditions (e.g if the device is already pretty constrained).

    When web storage is low (under pressure), a UA will clear storage to make space available. This can suck for offline apps and so the recently updated Storage spec defines a “persistent” and “best effort” strategy here with “best effort” being the default. “best effort” means the storage can be cleared without interrupting the user, but means it is less durable for long-term or super critical data. IndexedDB and the Cache API fall into the “best effort” bucket today.

    “persistent” storage is not automatically cleared when storage is low and the user will need to manually clear out this storage (via browser settings) themselves. Chrome has been experimenting with support for Persistent Storage under an origin trial, and the latest news suggests it will be shipping in Chrome 55.

    How can I tell how much storage space my app is using?

    The Quota Management API lets you query for the size of storage space currently used and how much is available to the application. It also enables requesting for more storage if needed. A newer Storage Quota Estimate APItries to make it even easier to discover how much quota an origin is using with support for Promises.

    Referência: https://medium.com/dev-channel/offline-storage-for-progressive-web-apps-70d52695513c


  • Tabela de acentos em JavaScript

    20/07/2016

    Quem trabalha com desenvolvimento em Javascript de vez em sempre precisa utilizar frameworks de terceiros (ExtJS, Bootstrap, Jquery, AngularJS, DevExtreme e outras) ou mesmo construir uma própria código nativo.

    Sobre acentuação, em alguns casos a meta tag charset pode não ser suficiente para corrigir problemas de textos exibidos ao usuário com caracteres estranhos.

    Para a situação citada você pode substituir os acentos por códigos.

    Abaixo uma tabela com os tais caracteres e os seus substitutos:

    á = \u00e1

    à = \u00e0

    â = \u00e2

    ã = \u00e3

    ä = \u00e4

    Á = \u00c1

    À = \u00c0

    Â = \u00c2

    Ã = \u00c3

    Ä = \u00c4


    é = \u00e9

    è = \u00e8

    ê = \u00ea

    ê = \u00ea

    É = \u00c9

    È = \u00c8

    Ê = \u00ca

    Ë = \u00cb


    í = \u00ed

    ì = \u00ec

    î = \u00ee

    ï = \u00ef

    Í = \u00cd

    Ì = \u00cc

    Î = \u00ce

    Ï = \u00cf


    ó = \u00f3

    ò = \u00f2

    ô = \u00f4

    õ = \u00f5

    ö = \u00f6

    Ó = \u00d3

    Ò = \u00d2

    Ô = \u00d4

    Õ = \u00d5

    Ö = \u00d6


    ú = \u00fa

    ù = \u00f9

    û = \u00fb

    ü = \u00fc

    Ú = \u00da

    Ù = \u00d9

    Û = \u00db


    ç = \u00e7

    Ç = \u00c7


    ñ = \u00f1

    Ñ = \u00d1


    &amp; = \u0026

    ' = \u0027

    Referências:


  • Login em uma URL com redirecionamento para outra após a autenticação

    24/06/2016


    A necessidade é bem simples, autenticar num site web e então após isso redirecionar para outra URL.

    O fato esperado é que após ocorrer o submit para a URL da action o contexto da página corrente não existe mais, claro, então rotinas colocadas por exemplo no evento "onsubmit" do form não funcionarão. Pelo mesmo motivo quaisquer rotinas a serem executadas por um setTimeout() na página também não serão executados, que foi um caso que tentei, um timer local considerando que se passaram X segundos e que nesse período a autenticação ocorreu.

    Além desse problema de contexto alterado houve ainda problemas sobre CORS quando tentei utilizar um código mais limpo com XMLHttpRequest fazendo o POST para a URL da action, então a solução mais simplista e funcional foi:

    1. Copiar o HTML original da página de autenticação.
    2. Pré-preencher os valores de usuário e senha.
    3. Simular o clique no botão, na verdade não o método click, mas o método submit.
    4. Meta tag para fazer o redirecionamento para a página destino desejada.
    Mas o segredo mesmo para se manter no contexto foi o "target" do form ser um iframe, nesse caso usar a Metatag foi só para evitar mais um script, eu poderia usar um setTimeout(), mesmo o onsubmit funcionaria.



    Abaixo a solução para o mundo ideal (usuário e senha corretos por exemplo).
    Veja que a solução é HTML+Javascript puro, nada de JQuery ou outra framework.

    
    <meta http-equiv="refresh" content="5; url=http://URL_APOS_AUTENTICAR">
     
    <form action="http://URL_DE_AUTENTICA" method="post" name="acesso" id="acesso" target="logado" hidden="hidden">
        <input name="nome" type=text size=30 maxlength=30 value='usuário'>
        <input name="senha" type=password size=30 maxlength=30 value='senha'>
        <input type="submit" name="Entrar" id="Entrar" value="Entrar">
    </form> 
     
    <iframe name="logado" id="logado" hidden="hidden"></iframe>
     
    <h2>Executando processo de autenticação, aguarde...</h2>
     
    <script>
     
        // autentica para o iframe escondido.
        document.getElementById('acesso').submit();
     
    </script>
    
    
    
    
    
    
    Referências: 
    • http://pt.wikipedia.org/wiki/Cross-origin_resource_sharing
    • http://www.w3schools.com/xml/xml_http.asp
    
    


  • Problema de rolagem de HTML em iframe no iOS

    21/06/2016

    O "Causo"

    Recentemente tive um problema que, consultando vários fóruns, confirmei que realmente existe uma situação única de problema de rolagem de conteúdo HTML em um iframe em qualquer browser executado no iOS.


    Como é que funciona?

    Os browsers em dispositivos móveis adequam a rolagem de conteúdo com aquela forma elástica que ocorre quando a gente segura a tela com o dedo e arrasta para cima ou para baixo e então soltando o conteúdo vai rolando na direção que soltou sozinho até parar.

    Nessa condição a scrollbar padrão, aquela feiura antiquada, dá lugar a outra, e o comportamento de clique muda também para se comportar da maneira citada, toque, segura e solta.

    Essa mudança de visual e funcionamento difere da execução em desktop em que aparece a barra de rolagem e o conteúdo rola apenas ao clicar na barra lateral ou com teclas de atalho (page down, up e etc), e se segurar o cursor do mouse com o clique em algum ponto irá marcar texto (alguns browser permitem mudar esse comportamento para tipo o estilo mobile).

    Sim, mas e daí?

    Esbarrei pelo problema usando o Ext.Net (ExtJS), na verdade não é culpa dele, mas sim do iframe em que o conteúdo HTML é embutido. O funcionamento do sistema era abrir rotas/URLs em janelas dentro da principal simulando o funcionamento de janelas do Windows por exemplo, mas para isso a parte interna das janelas é um iframe.

    O fato apenas ocorre no iOS, em qualquer Android funciona, no caso não havia a mudança para o estilo mobile, continuava o HTML com rolagem estilo desktop.

    Então depois de algumas pesquisas em fóruns, vários indicavam a pequena iscrolljs.com.

    Basicamente o funcionamento é o de se colocar uma DIV dentro de outra DIV, cada uma com o seu devido CSS aplicado (tem tudo no site), e no onLoad() da página instanciar um objeto da iScroll (bastante sugestivo o prefixo do nome).

    Na minha aplicação passei por mais problemas, pois trabalhando com o ExtJS vi que não poderia utilizar os Ext.Panels padrões pela injeção de HTML indesejado, cheguei até a testar mas não funcionou, de qualquer maneira consegui contornar usando o iScroll na forma mais simples, pelos próprios exemplos do site dele.

    Eu cheguei a ver algumas soluções baseadas em ExtJS mas achei muito, mas muito complexas, exageradamente...criação de componentes com herança, eventos e etc, muita ladainha para uma necessidade mais direta de aplicar, e olha que no meu caso eu tinha alguns poréns a mais:

    1. Store é atualizada disparando evento DataChanged...
    2. ...DataView atrelada à Store renderiza conteúdo...
    3. ...captura do HTML renderizado na DataView...
    4. ...injeção do HTML dentro da DIV mais interna usada pelo iScroll.

    Só serve para isso?

    Não!

    Existem algumas outras funções bastante interessantes como zoom, scroll infinito (carga de HTML por demanda) e outras, que um dia irei explorar, o importante é saber que há como fazer e o iScroll é a solução atual mais simples e leve de aplicar.

    Mas lembre-se!

    Apenas para ficar ciente, o comportamento do scroll de conteúdo passará a ser "estilo" mobile mesmo em browser no desktop, ou seja, para rolar tem-se que clicar, segurar e jogar o cursor para uma direção, prático como se utiliza um tablet e celular, mas pode ser que alguns usuários estranhem no desktop.


  • Editor WYSIWYG - Colando uma imagem da área de trabalho no Chrome

    21/06/2016

    Quem trabalha com desenvolvimento web provavelmente já precisou implementar um "editorzinho" de textos básico com formatação (negrito e etc, alteração de fonte e etc), o famoso editor no estilo WYSIWYG, quase um Word. :)

    Existem diversas soluções no mercado que podem ser integradas a projetos de terceiros e eu creio que uma das mais completas seja o CKeditor, no meu caso optei por algo mais enxuto, mas com o mínimo necessário além de um API que me atendesse, o SCEditor.

    Apesar de N tipos de editores, alguns muito simples, outros bem completos, todos os que vi funcionam com a mesma lógica de disponibilização da área que será editada, a definição da propriedade "ContentEditable=true" na tag HTML que então será a área do editor.

    ...então caímos no assunto da publicação, nem todos os browsers aceitam colar uma imagem no editor!

    Quando o usuário faz uma captura de tela pela tecla Print Screen ou com uma ferramenta (SnagIt por exemplo), a imagem fica na área de transferência, então ao colar no editor a mesma é codificada para o padrão texto Base64 e embutida em uma tag IMG no HTML , recurso idêntico ao GMail quando incorpora imagens como anexo, bem, esse é o comportamento correto, mas...no Chrome nada acontece.

    Testei no Internet Explorer 11 e no Firefox 35 e funciona certinho, mas no Chrome 40 nada acontece, quando a gente tenta colar uma imagem nem mesmo aparece algum indicador que foi tentado, já com texto ou HTML na área de transferência o mesmo é colado sem problemas.

    Tentei encontrar alguma chave de configuração no Chrome que permitisse colar imagem mas não encontrei nada, algo tipo "Enable Image Paste".


    Então resolvi apelar testando códigos de interceptação do "paste" da imagem pelo usuário, e 

    não é que funcionou!



    A SOLUÇÃO

    A idéia básica é interceptar o "paste" dado pelo usuário, interpretar o conteúdo da área de trabalho como imagem, gerar a cadeia base64 da image e então injetar no HTML do editor a tag IMG com a string.

    Não há uma solução genérica, uma que funcione em qualquer browser, a que funcionou está na referência 2 abaixo, e ainda precisei fazer algumas alterações.

    Como o problema é apenas no Chrome descartei o código para os outros browsers.

    Ao invés de incorporar a imagem como base64 eu poderia salvá-la em disco e então adicionar uma tag IMG com o link da imagem no servidor, isso não seria um problema, o mais difícil foi achar uma rotina que interceptasse o "paste" no Chrome.

     

    [Atualização em 25/fev/15]

    Após colocar o comentário sobre o problema num fórum do site do SCEditor que eu utilizo, o autor mandou uma solução mais enxuta da mesma idéia, e que pelo o que testei funciona de forma genérica (são algumas poucas alterações do código original que montei):

    var IMAGE_MIME_REGEX = /^image\/(p?jpeg|gif|png)$/i;
    
    var loadImage = function (file) {
        var reader = new FileReader();
        reader.onload = function(e){
            var img = document.createElement('img');
            img.src = e.target.result;
            
            var range = window.getSelection().getRangeAt(0);
            range.deleteContents();
            range.insertNode(img);
        };
        reader.readAsDataURL(file);
    };
    
    document.onpaste = function(e){
        var items = e.clipboardData.items;
    
        for (var i = 0; i &lt; items.length; i++) {
            if (IMAGE_MIME_REGEX.test(items[i].type)) {
                loadImage(items[i].getAsFile());
                return;
            }
        }
        
        // Normal paste handling here
    }

    Referências:

    1. http://pt.wikipedia.org/wiki/WYSIWYG
    2. https://gist.github.com/Buildstarted/4577472/
    3. http://www.w3schools.com/jsref/event_onpaste.asp
    4. http://pt.wikipedia.org/wiki/Base64
    5. http://stackoverflow.com/questions/237254/how-do-you-handle-oncut-oncopy-and-onpaste-in-jquery
    6. http://www.sceditor.com/posts/how-to-upload-and-insert-an-image/
    7. http://ckeditor.com/
    8. http://jsfiddle.net/6yocdszm/ (atualização de 25/fev/15)


  • Javascript :: console.log() + console.trace()

    20/06/2016

    Quem desenvolve usando client side frameworks (ex.: Bootstrap, ExtJS e outras) de vez em quando se pega usando console.log() para rastrear a execução, mesmo que o debug seja mais eficiente.

    Uma dica interessante para casos assim é usar em conjunto com o console.log() - usado para colocar conteúdo visível no console do browser - também o console.trace(), assim poderá rastrear não só a chamar à função, mas também o contexto de chamada e parâmetros passados.


  • Javascript :: quando a expressão true == true retorna false

    20/06/2016

    Recentemente precisei fazer uma validação de valor boolean vindo de um request Ajax.

    A lógica seria mais ou menos:

    // retornoRequest.Campo tinha o valor "true" (via debug e console).

    if (retornoRequest.Campo == "true")

    {

       "faça"

    }

    Mas essa verificação estava dando falso por algum motivo.

    Mesmo comparar com === "true" também.

    Então a solução:

    if (retornoRequest.Campo.toString() == "true")

    {

       "faça"

    }

    Somente aplicando forçadamente a conversão para string que foi possível usar o valor real da expressão, estranhamente pois pelo visual os valores eram iguais. Não avaliei o tipo, mas certamente estava nisso o problema.


  • Wijmo, muito bom, fácil de implantar e leve, mas peca em documentação

    15/06/2016

    O Wijmo, muito bom, fácil de implantar e leve, mas peca em documentação.

    Você rala para descobrir funções, pesquisa artigos e então descobrei que o problema é que as soluções em artigos estão obsoletas e que a documentação não explica o que deveria.

    Exemplo recente, precisa recuperar os eventos (appointments) do Event Calendar, segundo a documentação bastaria acessar à propriedade "appointments", mas esta sempre retorna vazio.

     appointments: /// &lt;summary&gt;  
         /// The event objects array. This option is deprecated:  
         ///     please, use eventsData option, instead.  
    

    Somente abrindo o código que agora a propriedade é "eventsData" na versão atual (desde quando?) que é a 2013.1.

    Então a linha de código correta é:

     var eventos = $("#eventscalendar").wijevcal("option", "eventsData");  
    

    É o velho problema dos manuais que não acompanham a evolução dos softwares produzidos.