Chamando a atenção na barra de tarefas

Escrito por elcio em 12/10/2009 | Básico, Javascript

Um aluno está desenvolvendo um chat e me perguntou esses dias como fazer para alertar o usuário da chegada de uma nova mensagem quando a janela do bate-papo estiver em segundo plano. “Como o MSN”, ele disse. Bom, você não faz isso. Se alguém conhecer um truque secreto, por favor nos conte.

O que você pode fazer é mudar o texto do título da janela afim de chamar atenção do usuário. Não é a mesma coisa, mas é o melhor que temos.

Preparei então um pequeno código de exemplo. Arquivo titleRotator.js:

titleRotator={
  chars:['#',' ','%',' '],
  rotating:false,
  position:0,
  rotate:function(){
    if(this.rotating){
      this.position=(this.position+1)%this.chars.length
      this.clear()
      document.title=this.chars[this.position]+' '+document.title
    }
  },
  clear:function(){
    document.title=document.title.replace(/^\W /,'')
  },
  start:function(){
    if(!this.rotating){
      document.title=this.chars[this.position]+' '+document.title
      this.rotating=true
    }
  },
  stop:function(){
    if(this.rotating){
      this.clear()
      this.rotating=false
    }
  },
  toggle:function(){
    this.rotating?this.stop():this.start()
  }
}
setInterval("titleRotator.rotate()",150);

Você pode ver um exemplo disso funcionando aqui. Sugestões de melhoria são muito bem-vindas.

Menos é mais, quanto mais simples melhor

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

É, este é um dos tópicos do manifesto deste site.

Eu queria na verdade falar sobre 3 deles, mas este tópico tem o foco principal neste post. Parece uma frase simples, mas no desenvolvimento esta frase poderia evitar muitos e muitos problemas.

Numa conversa com um colega de trabalho meu da Agência, o Danilo, percebi que MUITOS plugins existentes para jQuery tem quase o mesmo tamanho ou é maior que a própria biblioteca!!!Na maioria das vezes desnecessariamente.

Isto porque na tentativa de facilitar o uso do código e pensar (ou tentar pensar) em todas as ocasiões possíveis, o desenvolvedor acaba codificando MUITO MAIS do que deveria.

Eu precisei fazer um esquema para fazer upload de múltiplos arquivos e utilizei um plugin para fazê-lo. Acabei precisando fazer uma gambiarra para fazer funcionar, porque não existia uma opção no plugin para o propósito.

Quando fui abrir o código-fonte para tentar estender o plugin, me deparei com um monstro de tantos KB. Fazia de tudo, abria modal, dava piruetas etc.

Trocentas linhas de código para fazer uma coisinha simples: mandar múltiplos arquivos ao submeter o formulário.Eu usei apenas um dos quinhentos métodos daquele plugin, imagina o que foi baixado para a página só para aquele funcionamento mais o jQuery!.

Mais tarde acabei fazendo uma versão minha deste código, apenas usando Javascript, e ainda assim ficou MUITO menor que a versão que utilizava jQuery…absurdo.

Pensando nisso, resolvi fazer uma implementação simples, rápida e estensível de um script antigo que o Elcio fez de máscaras, este aqui, e mostrar a minha forma, de usar os tópicos 2, 4 e 5 do manifesto do ClientSide, para resolver um problema simples, de forma simples, reaproveitando o código e usando um pouco de orientação a objetos em Javascript.

Pensei então em um objeto do tipo JSM, guardando algumas variáveis, sendo uma delas (JSM.add) para estender o objeto com novas máscaras, a outra (JSM.methods) para armazenar todos os métodos de mascaramento existentes no objeto e a outra uma pseudo-classe.

1
2
3
4
5
6
7
  function
    JSM (form){ return new JSM.Class(form) }  
 
    JSM . add = function(json){
      JSM.methods[json.name] = json.method
    }
    JSM . methods = {}

JSM é uma função, mas é uma função que tem atrelada a ela outras variaveis, e retorna uma instância de uma “classe”, a JSM.Class, passando como parâmetro a string contendo o name do formulário.
A pseudo-classe:

1
2
3
4
5
6
7
8
9
10
11
12
13
  JSM . Class = function(form){
    //Private:
    var form = document[form]
    //Public:
    this.inputs = {}
    this.form = function(){return form}
    this.test = function(object, mask){
      var fn = JSM.methods[mask]
      setTimeout( function(){
  object.value = fn( object.value )
      },1 )
    }
  }

Assim, sempre que eu pensar em estender a classe, eu vou estender JSM.Class. Ela que vai armazenar o escopo do objeto retornado. É um padrão que eu tenho adotado em alguns projetos meus.

O método add é uma abstração da forma como é estendido o objeto na adição de máscaras novas, o desenvolvedor não precisa pensar em como foi montado o objeto JSM para estendê-lo corretamente, basta utilizar este método auxiliar que abstrai esta complexidade.

Então se eu quisesse estender o objeto com uma máscara nova eu faria:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  JSM.add({
    "name":"telefone",
    "method": function(v){
      //Remove tudo o que não é dígito
      v=v.replace(/\D/g,"") 
 
      //Coloca parênteses em volta dos dois primeiros dígitos
      v=v.replace(/^(\d\d)(\d)/g,"($1) $2") 
 
      //Coloca hífen entre o quarto e o quinto dígitos
      v=v.replace(/(\d{4})(\d)/,"$1-$2")
      return v
    }
  })

O desenvolvedor utilizaria esse método dando um nome e fornecendo um método que pega o valor, trata e retorna o novo valor mascarado, sem se preocupar no funcionamento do objeto.

Adicionado este método já poderia usar na instância do objeto JSM assim:

  JSM("formulario").mask({
    "campoTelefone" : "telefone"
  })

É mais rápido e mais fácil pegar os elementos pelo nome nos formulários, então o parâmetro do JSM é um name, do formulário, “campoTelefone” é o name de um input text deste formulário e telefone é a mascara que queremos utilizar no campo.

O JSM quebra a string da máscara quando esta tem um “|” (pipe), ou seja, na chamada da função você pode adicionar duas ou mais máscaras para o mesmo campo se necessário:

  JSM("formulario").mask({
    "campoTelefone" : "leech|digitos"
  })

- “Você esqueceu de fornecer um método para definir um maxlength!!”
Isto não é a função do objeto, é função do html, portanto, deve estar definido na própria tag! Simplifique.

Não fiz o objeto percorrer o html para achar uma classe conveniente e fazer o mascaramento, porque não é o papel dele, ele deve apenas mascarar, esta é sua função.

Se quiser fazer um microformato para ele, usando uma classe Css para este funcionamento, deve montar uma outra aplicação com este propósito, importando e utilizando o objeto JSM.

Eu queria mostrar, uma maneira simples de solucionar um problema de cada vez, com um objeto encapsulado. É muito fácil desacoplar os métodos adicionados para as máscaras e colocar em arquivos separados e mesmo outros métodos que estendam a classe JSM.Class, importando naquela página apenas os métodos que precisa utilizar, ou então utilizando as partes em uma aplicação terceira que faça outras coisas mais…

Eu uso muito namespaces, modularizando meu projeto. Portanto, se eu quisesse fazer uma extensão da classe com alguma frescura que não necessariamente seja algo de útil, eu faria outro arquivo para importar, usando namespace: Ex: JSM.frescura.js.

Entenderam? Sacaram a parada? Postei o código inteiro, com todas as máscaras que vi do site do Elcio e com suas explicações aqui para quem quiser.

Abraço.

Feature Detecting

Escrito por Eduardo Ottaviani em 08/02/2009 | Boas práticas, Javascript, jQuery

E aí povo!?! Feliz 2009 para todos!

O ano passa rápido, só agora que vim postar de novo percebi que postei há quase um ano aqui no ClientSide. Bom, neste post gostaria de comentar mais uma vez sobre algumas técnicas de programação em javascript, levando carona na febre jQuery.

Para mim, o mais interessante dessas bibliotecas é como os desenvolvedores utilizam as mais variadas técnicas para resolver seus problemas. Vocês viram que saiu a versão 1.3 do jQuery?

Uma coisa que me chamou muita atenção nessa nova versão é o uso de uma técnica chamada “Feature Detecting”, que além de aumentar o desempenho ainda aumentou a gama de compatibilidade com os navegadores.

Eu não quero pegar pesado, então vou falar a grosso modo de como funciona essa técnica para que maioria ou todos possam entender.No desenvolvimento com Js, foi e ainda é muito comum o uso de browser sniffing, que é a detecção de qual navegador o usuário está usando para poder usar determinado script. Como este código.

O grande problema do uso desta técnica é que ela limita o número de browsers compatíveis com a sua aplicação, excluindo outros browsers que não entram na detecção.

Outro problema é que a cada trecho do código é necessário executar uma verificação para descobrir qual browser está sendo usado e o tamanho do código aumenta generosamente conforme a necessidade de acrescentar um novo browser compatível.

Há outras desvantagens, mas neste post não caberiam todas elas.Eu usava feature detecting mesmo sem saber que tinha este nome, meio sem querer, confesso que usava por pura preguiça, porque eu odiava aquela história de navigator.appName.

Terrível isso. A solução que eu usava e compartilho com vocês foi essa:

1
2
3
4
function feature( support ){
  for ( var x in support )
    feature[x] = support[x]
}

Eu renomeei as funções para fazer sentido à este post, usava outros nomes. A idéia é usar um objeto feature com as funcionalidades que desejo e usar uma função support, para definir quais delas existirão.

Já tentaram pegar o estilo de um elemento que foi definido apenas no Css? Assim:

1
alert( document.getElementById("caixa").style.height )

O retorno seria vazio, o Js só conseguiria retornar o valor se este fosse antes definido pelo Js ou se usasse style inline (credo…).

Para pegar o estilo do elemento definido apenas pelo Css, nos browsers comuns usamos : document.defaultView.getComputedStyle

Porém no iE, se usa: document.getElementById(”caixa”).currentStyle

Agora, utilizando as funções anteriores, o feature detecting ficaria:

1
2
3
4
5
6
7
8
9
10
11
function support(){
  var support = {}
  // Style:
  try{
    document.defaultView.getComputedStyle
    support["computedStyle"] = function(el, att){
      return document.defaultView.getComputedStyle(el, null).getPropertyValue(att) }
    }
    catch(e){ support[“computedStyle”] = function() {return null} }
  return support
}

Este código não verifica qual navegador o usuário está usando, porque aqui não interessa.

O script vai testar se o navegador possui a propriedade, se possuir, constrói um objeto support[“computedStyle”] , que é uma função que retorna o estilo do objeto HTML. Definindo esta propriedade no support, eu teria meus objetos features da seguinte forma:

feature( support() )
// …. Códigos e mais códigos
alert( feature.computedStyle ) // Mostraria o corpo da função computedStyle.

O instinto nos levaria a colocar a função que contém a propriedade currentStyle no catch e fim. Mas aí estaríamos assumindo que todos os navegadores usam a propriedade do document.defaultView e os que não usam são os iE´s da vida. Mas não é a melhor forma de pensar.

A melhor forma de fazer isso seria aninhar mais um teste verificando se o navegador possui a propriedade currentStyle, e retornar uma função conveniente. Há um sério problema nesse caso, não é possível testar o currentStyle, porque ele pertence à um objeto HTML, que não sabemos qual vai ser até a função ser chamada.

Ele tem a forma : elemento.currentStyle.height. O support definiria todas as funções antes mesmo do documento estar pronto, não daria para testar um elemento, sem saber qual elemento é.

Outro instinto ( assassino) seria testar um activeXObject para saber se é um iE ou ainda testar pelo navigator.appName se é um iE6. Isso seria completamente o oposto do que a filosofia do feature detecting prega. Estaria retornando às técnicas de browser sniffing.

A solução que eu encontrei foi essa:

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
28
29
30
31
function feature( support ){
  for ( var x in support )
    feature[x] = support[x]
}  
 
function support(){
  var support = {}
  // Style:
  try{
    document.defaultView.getComputedStyle
    support["computedStyle"] = function(el, att){
      return document.defaultView.getComputedStyle(el, null).getPropertyValue(att) }
    }
    catch(e){
      support["computedStyle"] = function(el, att){
        var ieComputedStyle = function(el, att){
          if( att == "opacity" && el.filters )
            return parseFloat(support["computedStyle"](el, "filter").replace(/\D/gi, '')/100)
          return el.currentStyle[att]
        }
          if(el.currentStyle){
            feature["computedStyle"] = ieComputedStyle
            return ieComputedStyle(el, att)
          }
        return feature["computedStyle"] = function(){ return null }
      }
    }
}    
 
feature( support() )
alert( feature.computedStyle )

No catch a função computedStyle testa se o elemento passado como argumento possui uma propriedade currentStyle. Se possuir, o objeto feature.computedStyle é alterado e passa a ser uma função que retorna o currentStyle do elemento. Essa função também teria de tratar o atributo “opacity” , porque se for um iE que estiver executando e este iE não tiver opacity, mas um filter, ele vai funcionar da mesma forma.

Neste ponto ela é recursiva, porque chama ela mesma para retornar a propriedade filter. Para os navegadores que implementam o document.defaultView.getComputedStyle o método computedStyle do feature vai ser implementado logo no load do código. Já os outros, o método seria definido apenas após a sua primeira chamada.

É fácil de enxergar isso fazendo:

feature( support() )
var obj = document.getElementById("box")
alert( feature.computedStyle ) // Mostra função de teste
alert( feature.computedStyle ( obj, "height" ) ) // Chama a função de teste e define o método novo para feature.computedStyle e retornando o valor do height.
alert( feature.computedStyle ) // Mostra o método final

Não seria mais necessário haver testes, cada navegador implementaria a sua função. Entenderam a idéia mais ou menos?

Então, esse trecho acima deve funcionar no Opera, Chrome, FF2, FF3, IE6, iE7 sem que fosse necessário qualquer detecção de browser, em nenhum momento especifiquei no código que navegador deve funcionar em cada trecho.

Para os que quiserem uma literatura mais refinada sobre esse assunto, sugiro este link:

http://docs.jquery.com/Utilities/jQuery.support.

Lá possui 3 links sobre feature detecting, usei um deles no primeiro exemplo. Tem um ótimo exemplo para um método clipBoardData. Eu também deixei um trecho com mais “features”, XML, Ajax e opacity para ajudar a entender a lógica que usei.

Para quem estiver interessado, está aqui :

http://edu.110mb.com/demonstracao/feature.detecting.js.

Um abraço a todos

Valeu!

Validar extensões de arquivos com javascript

Escrito por Wadson Gomes em 05/01/2009 | Javascript

A função que desenvolvi tem com objetivo facilitar a vida de quem for validar extensões de arquivos com javascript antes de enviar ao servidor.

  • Seu uso é fácil já que ela só retorna um valor, sendo este true ou false.
  • Aceita varias extensões para um mesmo campo
  • Pode ser aplicada a vários campos usando um array de uma TagName ou ClassName
  • Usa uma expressão regular para validar a extensão
  • Pode ser usada no onblur do campo

Codigo da função

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function LexExt(dom,ext){
  var campo=dom.value;
  var exts=ext.split(',');
  if(ext.length>=1){
    for(i=0;i<exts.length;i++){
      var reg=new RegExp("."+exts[i]+"$");
      if(campo.match(reg)==null){
        if(i==exts.length-1){
          return false;
        }
      }else{
        return true;
      }
    }
  }
}

Chamando a função

O primeiro parâmetro é o campo que receberá a validação
O segundo parâmetro são as extensões (sem o ponto)
Ex: LexExt($(’my’),’jpg,gif,png,html,css,js’);

Exemplos práticos

// usando pelo id do campo
LexExt(document.getElementById(‘my’),’doc,docx’);
 
// usando atraves de um array
var dom = document.getElementsByClassName(‘file’);
for(var i=0;i<dom.length;i++){
LexExt(dom[i],’jpg,gif’);
}