Bug em textos com negrito carregados via ajax no IE7

Escrito por leandro em 02/06/2008 | Ajax, CSS, Javascript

Palavra do Editor: nosso amigo Leandro publicou esse artigo relatando um bug, sem solução (a solução dele é “não use”.) Se você conhece uma solução melhor, por favor, escreva um comentário ou um novo artigo em resposta a esse.

O problema

Estava lá eu, todo pimpão e alegre em casa, fazendo minhas requisições em ajax funcionar em um projeto que ando trabalhando e quando decido testar em outros navegadores, tal como o nosso excelentíssimo Internet Explorer 7 eu percebo: “Olha mãe! A requisição funcionou beleza no ie7″. Sim, funcionou sim com um pequeno inconveniente: parte do conteúdo carregado “on-the-fly” pela requisição ‘ajax’ veio meio distorcida (print screen com o trecho bugado) e eu então percebi que essas distorções (que na verdade são problemas de anti-aliasing desabilitado no ie7 ou algo do gênero) seguem um padrão para que elas aconteçam: qualquer texto que esteja em negrito carregado pela requisição ajax sofrerá desse problema.

Causas

  • Processar algoritmos de anti-aliasing (anti serrilhamento) deve ser um processo diferente para textos em negrito e talvez o pessoal do desenvolvimento da MS esqueceram de mandar processar esses algoritmos para textos em negritos carregados via ajax e ninguém avisou eles! Coitadinhos!
  • Ou simplesmente o algoritmo não funciona nesses casos de requisição via ajax. Afinal, who cares!

Solução

Para falar a verdade o porque disso acontecer não é muito importante. O importante é que eu aparentemente não achei solução para esse problema e a melhor dica que dou para contornar isso é: evite usar textos em negritos dentro de pedaços de html oriundas de requisições em ajax no ie7. Existem N maneiras de se fazer isso e a mais simples delas, IMO, é modificar os seletores CSS de modo que não tenham valores que os negritem (no caso font-weight:700; ou font-weight:bold; são exemplos de propriedade/valor que negritam o texto) somente no Internet Explorer 7.

Uma das formas de se fazer isso é fazer um seletor para tais texto parecido com esse (CSS):

.no-ie7-bold {font-weight:700 !important;font-weight:normal;}

Aí toda vez que você tiver textos que precisem ser negritados oriundos de requisições em ajax use uma classe semelhante a essa acima nos devidos elementos. Ela fará com que o texto não seja negritado apenas no IE7 (minto, no IE6 também não será e talvez nos seus ancestrais idem). Mas acredito eu que existam hacks de seletores que apenas o IE7 entenda. Sinta-se livre para melhorar a receita do bolo. Meu papel aqui é basicamente reportar esse bug nefasto do IE7.

Upload Assíncrono de Arquivos com Mootools

Escrito por JulioGreff em 25/05/2008 | Ajax, Frameworks

Por questões de segurança, os navegadores não permitem que o JavaScript acesse o sistema de arquivos do cliente, inviabilizando o upload via ajax no ambiente web. Assim, temos que criar alternativas para o upload assíncrono. Vários já criaram soluções para o problema, mas a maioria como funções autônomas. Como sou fã da Mootools, decidi criar a minha solução, e vou aproveitar para explicá-la aqui.

A Lógica

A lógica para essa “gambiarra” não é nova, já existe desde que o HTML é HTML. Existe o atributo target da tag form. Através dele, podemos escolher em que janela queremos que seja feita a submissão. Então a página não recarregará, e o resultado do formulário será carregado em outra janela.

Aí surge o primeiro porém: não queremos abrir uma nova janela (pop-ups são chatos, não concorda?)! Lembro que, nas minhas primeiras experiências com HTML e frames, podíamos fazer com que links abrissem em determinado frame através do mesmo atributo. Mas como também não queremos frames, vamos utilizar um irmão mais novo: o iframe.

Simplificando, a idéia é enviar o formulário para um iframe invisível, e recuperar a resposta quando este tiver terminado de carregar.

Preparando o Script

Desde o começo, pensei em fazer o script bem integrado à Mootools, como se fosse um plugin. Para isso, usei o mesmo processo utilizado para todas as funcionalidades relacionadas ao HTML: estendi a classe Element com um método upload:

1
2
3
4
5
Element.extend({
  "upload": function(options) {
    if(this.getTag() != "form") return false
  }
})

Também incluí uma pequena verificação na linha 3: se não for um formulário, não devemos submeter!

Criando o IFrame

Como não devemos deixar um iframe perdido pelo documento, vamos criá-lo pelo JavaScript também. Seguindo o código:

4
5
6
7
8
9
10
var iFrame = new Element("iframe", {
  "id": "fileUpload",
  "name": "fileUpload",
  "styles": { "display": "none" }
})
this.adopt(iFrame)
window.frames["fileUpload"].name = "fileUpload" // IE... Sempre ele...

Para criá-lo, utilizei a classe Element, nativa da Mootools. Em uma chamada, já defini todos os atributos necessários. Primeiro, precisamos de um ID para localizarmos o iframe quando for necessário. O nome serve para o iframe ser identificado no target. O estilo só serve pra esconder o iframe.

Logo abaixo, fiz com que o formulário “adotasse” o iframe. Ele precisa estar em algum lugar para funcionar… Ah, e a correção made-for-IE, não esqueça dela!

Agora, precisamos saber quando o iframe carregou, para podermos disparar um callback com o retorno da requisição. Para isso, usamos o método addEvent, também da Mootools. No “load” do iframe, disparamos uma função onComplete, definida no parâmetro lá na definição da função.

11
12
13
14
15
16
iFrame.addEvent("load", function() {
  iFrame.removeEvent("load", arguments.callee) // removemos o evento, para que ele não dispare novamente
  if($type(options.onComplete) == "function")
    options.onComplete(iFrame.contentDocument.body.innerHTML)
  iFrame.remove.delay(20, iFrame)
})

Note que usei a propriedade contentDocument para recuperar a resposta da requisição. Com a “Same Origin Policy” (Política de Mesma Origem), só conseguimos recuperar a resposta de requisições feitas dentro do mesmo domínio. Existem maneiras de se driblar isso, mas o script se tornaria muito menos versátil, pois não consegui uma forma de executar funções com a resposta.

Executado o papel do nosso iframe, não precisamos mais dele populando nosso HTML, mas não podemos removê-lo imediatamente, pois assim causaríamos um “loading” que não terminaria. Então “atrasei” a função em alguns milissegundos, evitando esse probleminha.

Adaptando o Formulário

Se estamos criando um script para isso, você não quer ir lá e definir todos os atributos manualmente, quer? Então fiz com que o próprio script cuidasse disso. Apenas defini o target, o encoding e o método do formulário (só por segurança, caso ele não tenha sidio definido):

17
18
19
20
21
22
23
this.setProperties({
  "target": "fileUpload",
  "enctype": "multipart/form-data",
  "encoding": "multipart/form-data",
  "method": "post"
})
return this

Código Completo

Juntando tudo isso, temos o código abaixo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Element.extend({
  "upload": function(options) {
    if(this.getTag() != "form") return false
    var iFrame = new Element("iframe", {
      "id": "fileUpload",
      "name": "fileUpload",
      "styles": { "display": "none" }
    })
    this.adopt(iFrame)
    window.frames["fileUpload"].name = "fileUpload"
    iFrame.addEvent("load", function() {
      iFrame.removeEvent("load", arguments.callee)
      if($type(options.onComplete) == "function")
        options.onComplete(iFrame.contentDocument.body.innerHTML)
      iFrame.remove.delay(20, iFrame)
    })
    this.setProperties({
      "target": "fileUpload",
      "enctype": "multipart/form-data",
      "encoding": "multipart/form-data",
      "method": "post"
    })
    return this
  }
})

Utilização

Para utilizar o código, é ainda mais simples. Basta definir, no onSubmit do formulário, nossa função:

$(window).addEvent("load", function() { // primeiro esperamos a janela carregar
  $("form").addEvent("submit", function() {
    this.upload({"onComplete": function(response) { alert(response) } })
  })
})

Se quiser ver um exemplo funcionando ou fazer download do script, veja Async Upload - Upload Assíncrono de Arquivos. E espero que eu tenha conseguido mostrar que há vida além de jQuery… Até mais!

Mais importante que saber, é saber O QUE procurar.

Escrito por Eduardo Ottaviani em 06/02/2008 | Boas práticas, Básico, Javascript

Olá, achei muito interessante a idéia desse blog e me cadastrei porque queria dar alguma contribuição. Estou aqui pra falar sobre técnicas de programação em Javascript, já que eu gosto mais da parte algorítmica da coisa. Não sou fã de frameworks e ainda que eu use para facilitar e agilizar meu trabalho não é minha paixão.

Eu precisava de uma função que pegasse elementos pela classe e vi várias implementações de “getElementsByClassName”, mas queria aprender como fazer e fazer da minha forma.

Pensei, pensei, pensei, testei, testei, pensei…NADA! Tinha muito erro de lógica, cada vez que eu consertava criava um problema novo. Precisava procurar um algoritmo que resolvesse esse problema, pra isso tive que aprender um conceito novo (para mim) uma estrutura de dados chamada Árvores.
Afinal de contas a estrutura do HTML não é a de uma árvore?

Aprendi no Wikipédia o algoritmo, peguei o primeiro código recursivo que tinha em “C” lá e implementei em Javascript. Vergonhoso, a solução tinha 7 linhas. Ok, mas a recursão para funcionar empilha os dados, sabendo disso era só implementar uma Pilha (isso eu sabia o que era) para fazer a versão iterativa da travessia da árvore em pré-ordem (filhos são processados após o pai):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function getEBA(no, classe){
  var retorno=[]
  var pilha=[]
  pilha[pilha.length]=no
 
  //Enquanto a pilha contiver elementos...
  while(pilha.length > 0){
 
    // Guardo o primeiro elemento da pilha em "no"
    no=pilha[pilha.length-1]
 
      // Se o nodo tiver classe e for a que eu procuro,
      // empilho o elemento no array de retorno.
      if(typeof no.className!=”undefined” && no.className==classe)
      retorno[retorno.length]=no
 
    // Desempilho o nó atual da pilha.
    pilha.pop()
 
      // Se o nó atual tiver filhos, empilho os filhos na pilha[]
      if(no.childNodes)
        for(var x in no.childNodes)
        pilha[pilha.length]=no.childNodes[x]
    }
 
  return retorno
}

Mas o algoritmo tinha limitações como os outros que tinha visto. Eu não poderia escolher uma condição… a função só funcionaria para classes. E se eu quisesse usar um dia a função para pegar elementos pelo atributo “type” ou “language” ou qualquer outro? A função precisaria ser mais genérica. Outro problema.

Solução? Usar um pouquinho de Programação Funcional:

12
13
14
15
      // Se o nodo tiver classe e for a que eu procuro,
      // empilho o elemento no array de retorno.
      if(fn(no))  // A condição é definida pela função.
      retorno[retorno.length]=no

A condição então não é mais fixa, ela é definida por uma outra função passada como argumento na função principal e esse não será mais “classe” e sim “fn”.

Então, se quisesse pegar os elementos do div “divPai” que tenham apenas uma classe chamada “h-card”:

17
18
19
20
21
22
23
24
25
var classes=getEBA(
  document.getElementById("divPai"),
    function(obj){
      return(
      typeof obj.className!="undefined" &&
      obj.className=="h-card"
      )
    }
)

Agora sim. Nesta minha “busca algorítimica” implementei 4 conceitos muito interessantes que não havia dado muita atenção: Pilhas, Árvores, Programação Funcional e até Expressões Regulares.

Expressões Regulares? Sim, se eu quisesse pegar elementos que tenham “ieHOVER” sendo que esses elementos poderiam ter mais de uma classe:

27
28
29
30
31
32
33
34
35
var ieHover=getEBA(
  document.getElementById("divPai"),
    function(obj){
      return(
      typeof obj.className!="undefined" &&
      obj.className.match(/ieHOVER/)
      )
    }
)

Não chamo de “getElementsByClassName”, chamo de “getElementsByAnything”, o getEBA =). Se for interessante para você, pode executar a tarefa nos nodos usando a função passada como argumento ao invés de fazer isso percorrendo o array retornado, que agora é uma mera opção. =P

Mas minha intenção não era o código em si mas mostrar alguns dos obstáculos inevitáveis que surgem durante o processo de desenvolvimento e como contorná-los diminuindo drásticamente sua dificuldade.

É importante conhecer as diversas técnicas de programação e mais importante que conhecê-las é saber identificar a natureza do seu problema para só então procurar a solução e botar a mão na massa. Já pensaram nas coisas complicadas, para que perder tempo pensando do zero tudo de novo???

Bom, é isso… desculpem ficou longo o post, to engatinhando ainda e é meu primeiro post em blog (se for aceito).

Comentem, sugestões e críticas construtivas são bem-vindas. Você tem uma idéia melhor? Me ensina!

Ah…a frase do título foi inspirada numa citação de Albert Einstein.

Abraços e ótimo 2008 para todos.

Colocando um mapa dentro do site com JQuery e JMap2

Escrito por Leandro Facchinetti em 29/01/2008 | Ajax, jQuery

Google maps dentro do sitePubliquei esse texto no meu blog e, como está dentro do tema do Client-side, resolvi tentar minha sorte em ser publicado aqui.

Li sobre JQuery pela primeira vez neste blog. Então quando fui iniciar um novo projeto, resolvi testá-lo. É realmente impressionante o que se consegue fazer com ele. Mostra que programar pode ser divertido e simples.

Há, ainda, a possibilidade de usar plugins para complementar as funcionalidades dele. O que é ótimo, o limite do que você consegue fazer é a sua imaginação.

Infelizmente um problema em boa parte dos plugins é a documentação escassa. Tenho penado lendo comentários deixados dentro do código, e muitas vezes tendo que descobrir como usá-lo lendo o fonte. Talvez seja só azar, justamente nos plugins que usei isso acontece. O próprio JQuery é bem documentado, por exemplo. Saberei melhor no futuro.

No intuito de colaborar com os desenvolvedores e os interessados, eis um tutorial de como inserir um mapa do Google Maps no seu site (é o primeiro tutorial que faço, então o feedback será muito bem vindo. Com o tempo eu pego o jeito ;) ):

O efeito final é esse.

  1. Antes de começar é preciso criar uma chave para a API do Google Maps. Não é necessário cadastro, basta colocar a url do site no qual ela será usada nessa página. Ela serve para todo o domínio, inclusive subpastas. Se você tentar usar uma chave que não foi feita para o seu domínio ela não funcionará.
  2. Scripts necessários: são dois, o JQuery e o JMap2.
  3. O html: é preciso que o navegador esteja com o javascript habilitado para que o Google Maps funcione, então o html do exemplo é apenas a tag body:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
    <meta name="author" content="Leandro Facchinetti" />
    <script type="text/javascript" src="jquery-1.2.2.min.js"></script>
    <script type="text/javascript" src="http://maps.google.com/maps?file=api&amp;v=2&amp;key=sua_chave"></script>
    <script type="text/javascript" src="jquery.jmap2.js"></script>
    <script type="text/javascript">
    <!--
    //o código virá aqui
    -->
    </script>
    <title>Exemplo de colocação de mapas dentro do site com JQuery e JMap2</title>
    </head>
    <body>
    </body>
    </html>

    Atente para as importações de scripts. São três, o JQuery, a API do Google Maps e o JMap2. Lembre-se de mudar para a sua chave da API, onde diz “sua_chave” no código.

  4. Na parte onde está escrito “//o código virá aqui” é que escreveremos nosso código. Logicamente é preferível que você faça isso num documento externo e o importe, estou fazendo assim para simplificar.
  5. O código a ser inserido é este, veja os comentários para entendê-lo:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    $(function (){//sintaxe do JQuery para a função ser executada quando o DOM estiver pronto
    $("body").append("&lt;a href=\"\"&gt;Clique aqui para ver no mapa&lt;/a&gt;").children("a").click(function (){//aqui é colocado o link, não faria sentido colocá-lo no html porque quem não tivesse javascript ficaria com um link vazio. Após a colocação do link é associada uma função a ser executada quando ele é clicado
    $(this).after("&lt;div id=\"mapa\" style=\"width:450px; height:320px; display:none; background-color:#e5e3df; \"").parent().children("div#mapa").slideDown("slow", function (){//é inserida uma div na qual irá o mapa. Atente para a colocação de atributos width e height, o mapa será do tamanho que você setar aqui. Depois da colocação vem a função do efeito que faz a div aparecer deslizando. Lógico que isso é opcional, fiz assim porque é mais estético
    $(this).jmap({//aqui é acolocação do mapa em si, dentro dessa função vai um objeto com as opções, não listarei todas, só as que julgo serem mais importantes
    mapCenter: [-27.608000, -48.53770],//as coordenadas da onde o mapa será aberto. Para descobrí-las entre no Google Maps, ache a região que lhe interessa e clique em "Criar link para esta página", observe a url gerada, procure por duas sequência de número logo no início, são elas que você deve colocar aqui
    mapZoom: 17,//nível de zoom do mapa quando aberto
    mapShowOverview: false,//mostrar pequeno mapa da região no canto inferior direito
    mapShowType: false//mostrar o tipo de mapa: mapa, satélite, terreno
    }).addMarker({//adicionar aquele marcador vermelho
    pointLat: -27.608450,//as coordenadas de onde o marcador deve ser criado
    pointLng: -48.53770,
    pointHTML: "&lt;h3&gt;Paralelo 22&lt;/h3&gt;&lt;p&gt;Praça Abdon Batista&lt;br /&gt;Saco dos Limões - Florianópolis - SC&lt;/p&gt;",//html a ser exibido no balão do marcador, é possível manipular a formatação desse html pelo css da página. Incrível, não?        openHTMLEvent: "mouseover"//evento que dispara a abertura do marcador
    })
    })
    return false;//impedir a ação padrão do link
    })
    })
  6. Pronto, não é fantástico o que pode ser feito com tão pouco código?