Interface drag and drop com jQuery (atualizado)

Muita gente chega no meu blog pesquisando por “drag and drop”. Em respeito a esses leitores resolvi dar uma atualizada no meu primeiro artigo sobre o tema. Na época em que foi publicado, ainda não existia a parte de interface oficial do jQuery - muitos elementos, aliás, foram incorporados dos plugins de interface não-oficiais do site eyecon.ro.

Com o lançamento oficial dos plugins ui.jQuery ficou muito mais fácil desenvolver aquela mesma interface drag and drop do primeiro artigo. Agora, com duas linhas de configuração você já consegue criar duas colunas com boxes configurados para arrastar e soltar.

$("#drop-direita").sortable({connectWith: ["#drop-esquerda"]});
$("#drop-esquerda").sortable({connectWith: ["#drop-direita"]});

UPDATE: Na verdade, precisamos de apenas uma linha. Não tinha me ligado que dá pra fazer a conexão dos sortables com uma classe. Fica assim:

$('.recebeDrag').sortable({connectWith: ['.recebeDrag']});

A diferença agora é que não necessitamos mais dos eventos Draggable and Droppable (por mais irônico que isso possa parecer). O método Sortable resolve tudo sozinho.

Para saber mais sobre a biblioteca de interface do jQuery acesse o site: ui.jquery.com. Lá você conhece outros efeitos e widgets.

Aproveitando o upgrade do artigo, vou implementar algumas funções muito solicitadas via e-mail:

  • Salvar o posicionamento dos boxes em um cookie e lembrar na próxima visita.
  • Minimizar e remover os boxes.

Configuração inicial

Elementos da interface drag and drop

Vamos manter a mesma estrutura do artigo anterior, com elementos DIV da classe ‘recebeDrag’ servindo de base para os DIVs com a classe itemDrag. O pulo do gato aqui é a opção ‘connectWith’ do sortable. Com este parâmetro ligamos dois ou mais elementos para funcionarem como objetos que permitam ordenação entre si. Está feito nosso drag and drop.

Caso adicionássemos uma terceira coluna, #drop-meio, por exemplo, nosso código ficaria assim:

$("#drop-esquerda").sortable({connectWith: ["#drop-direita","#drop-meio"]});
$("#drop-meio").sortable({connectWith: ["#drop-direita","#drop-esquerda"]});
$("#drop-direita").sortable({connectWith: ["#drop-esquerda","#drop-meio"]});

No arquivo final do exemplo, utlizei alguns métodos do sortable para melhorar o visual e os efeitos do nosso drag and drop. São eles:

  • placeholder: aqui definimos que a classe ‘dragHelper’ vai servir para indicar a área vazia que estará recebendo o elemento arrastado.
  • scroll: marcando a opção scroll como true, obrigamos a barra de rolagem do navegador a ir até a posição do mouse.
  • revert: essa é firula total, apenas adiciona um efeito de transição quando um elemento é liberado.

Nossa chamada fica assim:

$('.recebeDrag').sortable({
  connectWith: ['.recebeDrag'],
  placeholder: 'dragHelper',
  scroll: true,
  revert: true,
  stop: function( e, ui ) {
    salvaCookie();
  }
});

Sim, no código acima já deixei preparado a função para salvar nosso cookie. Pra ela funcionar direitinho, precisamos primeiro do plugin jquery.cookie.

Quando termina o movimento de arrastar e soltar, através da opção ‘stop’, executamos a função salvaCookie(). A função grava em dois índices de um array a sequência de IDs dos elementos de cada box (esquerda e direita) utilizando o método toArray.

var salvaCookie = function() {
  var ordem = $('#drop-esquerda').sortable('toArray');
  ordem += '|' + $('#drop-direita').sortable('toArray');
  $.cookie('df_draganddrop', ordem);
};

E, toda vez que a página é carregada, o jQuery busca pelo cookie ‘df_draganddrop’ (pode ser o nome que você quiser) e configura o posicionamento dos boxes. O ideal é fazer isso via PHP (ou qualquer linguagem de programação que você esteja utilizando), desenvolvi em javascript só para ter um exemplo mais básico.

if( $.cookie('df_draganddrop') ) {
  var ordem = $.cookie('df_draganddrop').split('|');
  // posiciona boxes nos containers certos
  $('#drop-esquerda div.itemDrag').each(function(){
    if( ordem[0].search( $(this).attr('id') ) == -1 ) $('#drop-direita').append($(this));
  });
  $('#drop-direita div.itemDrag').each(function(){
    if( ordem[1].search( $(this).attr('id') ) == -1 ) $('#drop-esquerda').append($(this));
  });
  // ordena containers
  var esquerda = ordem[0].split(',');
  for( i = 0; i< = esquerda.length; i++ ) $('#drop-esquerda').append($('#'+esquerda[i]));
  var direita = ordem[1].split(',');
  for( i = 0; i<= direita.length; i++ ) $('#drop-direita').append($('#'+direita[i]));
} else {
  $.cookie('df_draganddrop', '', { expires: 7, path: '/' });
}

Primeiro verificamos se o cookie já existe. Se não existir, criamos um novo cookie com validade de uma semana.

Minimizando e removendo

Outro funcionamento muito legal é minimizar e remover boxes, personalizando totalmente uma listagem de conteúdos. Por ser um exemplo bem básico, não vamos salvar nada disso em nosso cookie de posicionamento. O ideal seria, para poder excluir no cookie, ter uma opção de adicionar boxes. Fica aí como dever de casa!

Para minimizar utilizaremos o efeito slideUp nativo do jQuery, mas nada impede você de utilizar fade ou animate. A opção de remover utiliza o fadeOut. Determinamos através do método bind que os links com as classes ‘lnk-minimizar’ e ‘lnk-remover’, nos seus respectivos cliques, minimizam e removem boxes de nossa interface drag and drop.

$('.lnk-minimizar').click(function(){
  var ul = $(this).parent().parent().parent().find('ul');
  if( $(ul).is(':visible') ) {
    $(ul).slideUp();
    $(this).html('[ + ]');
  } else {
    $(ul).slideDown();
    $(this).html('[ - ]');
  }
  return false;
});

$('.lnk-remover').click(function(){
  $(this).parent().parent().parent().fadeOut();
  return false;
});

Código javascript final

Segue abaixo o javascript completo. Não deixe de fazer o download dos arquivos de exemplo, com todo o HTML/CSS e Javascript necessário. E se publicar um site com essa interface não deixe de mandar o link nos comentários!

$(function(){
  // configura drag and drop
  $('.recebeDrag').sortable({
    connectWith: ['.recebeDrag'],
    placeholder: 'dragHelper',
    scroll: true,
    revert: true,
    stop: function( e, ui ) {
      salvaCookie();
    }
  });
  // minimizar boxes
  $('.lnk-minimizar').click(function(){
    var ul = $(this).parent().parent().parent().find('ul');
    if( $(ul).is(':visible') ) {
      $(ul).slideUp();
      $(this).html('[ + ]');
    } else {
      $(ul).slideDown();
      $(this).html('[ - ]');
    }
    return false;
  });
  // remover box
  $('.lnk-remover').click(function(){
    $(this).parent().parent().parent().fadeOut();
    return false;
  });
  // configuração inicial do cookie
  if( $.cookie('df_draganddrop') ) {
    var ordem = $.cookie('df_draganddrop').split('|');
    // posiciona boxes nos containers certos
    $('#drop-esquerda div.itemDrag').each(function(){
      if( ordem[0].search( $(this).attr('id') ) == -1 ) $('#drop-direita').append($(this));
    });
    $('#drop-direita div.itemDrag').each(function(){
      if( ordem[1].search( $(this).attr('id') ) == -1 ) $('#drop-esquerda').append($(this));
    });
    // ordena containers
    var esquerda = ordem[0].split(',');
    for( i = 0; i< = esquerda.length; i++ ) $('#drop-esquerda').append($('#'+esquerda[i]));
    var direita = ordem[1].split(',');
    for( i = 0; i<= direita.length; i++ ) $('#drop-direita').append($('#'+direita[i]));
  } else {
    $.cookie('df_draganddrop', '', { expires: 7, path: '/' });
  }
});
// salva cookie
var salvaCookie = function() {
  var ordem = $('#drop-esquerda').sortable('toArray');
  ordem += '|' + $('#drop-direita').sortable('toArray');
  $.cookie('df_draganddrop', ordem);
};

Update: 16/03/2010

O leitor Paulo Junior perguntou sobre uma funcionalidade interessante, a de maximizar os boxes para ocuparem toda a janela do navegador. Fica fácil implementar isso com os métodos width e height() nativos do jQuery.

Primeiro precisamos adicionar um link que executará a ação de maximizar/restaurar.

<a href="#" class="lnk-maximizar">[ [] ]</a>

As chamadas $(window).height() e $(window).width() retornam as dimensões da parte visível da janela do navegador. Como nossos elementos div de drag and drop possuem uma pequena margem, subtraímos 16 pixels dessas medidas. Além disso precisamos atribuir position absolute para trazer o elemento “para frente” de nosso site. Ao restaurar, devolvemos os atributos CSS iniciais do div.

// maximizar boxes
$('.lnk-maximizar').click(function(){
  var div   = $(this).parent().parent().parent();
  var largura  = ( $(window).width() - 16 );
  var altura  = ( $(window).height() - 16 );
  if( $(div).width() == largura && $(div).height() == altura )
  {
    $(div).css( {
      position: 'relative',
      width: '480px',
      height: '250px',
      top: 0,
      left: 0,
      zIndex: 1
    });
  }
  else
  {
    $(div).css( {
      position: 'absolute',
      top: $(window).scrollTop(),
      left: $(window).scrollLeft(),
      width: largura + 'px',
      height: altura + 'px',
      zIndex: 10
    });
  }
  return false;
});