28 de setembro de 2009

31Interface 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();
	}
});

Cookie de posicionamento

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;
});
Clique aqui para conferir este exemplo em ação.

31 leitores comentaram este artigo

  • 29/09/2009
    11:30

    Everton escreveu:

    Parabéns Davi!
    Essa atualização chegou em boa hora.
    Código perfeito, explicação clara... exatamente o que eu precisava. Valeu mesmo!!!
    Abraço

    Responder

  • 04/10/2009
    00:26

    Marcio escreveu:

    Parabens pelo excelente tutorial, so fiquei voando pra fazer com tres colunas utilizando a opção de salvar cookie, tem como dar um help??

    Responder

  • 05/10/2009
    15:11

    Davi Ferreira escreveu:

    Fala Marcio, é só adicionar na hora de posicionar e ordenar. Se você utilizar o ID #drop-meio, ele seria o ordem[2] no array depois do split. Tem que replicar as funções que tem pro drop-esquerda/drop-diretia pra esse drop-meio.

    Mas, pensando bem, dá pra fazer mais fácil com CSS, depois reformulo e publico no post.

    Valeu!

    Responder

  • 02/11/2009
    14:36

    William Bauch escreveu:

    Davi boa tarde, vc conhece algum artigo que mostre como salvar a posição usando um banco de dados (MySql)? obrigado

    Responder

  • 04/11/2009
    08:12

    Davi Ferreira escreveu:

    Fala aí, William!

    Cara, é só adaptar a função salvaCookie (ou criar uma salvaMysql :)). Toda vez que você define a ordem, faz uma chamada com $.post ou $.get do jQuery pra salvar no banco via AJAX.

    Se tiver dificuldades me dá um toque que te mostro um exemplo. Ou se conseguir colocar em prática compartilha aí com a galera.

    Valeu!

    Responder

  • 17/02/2010
    06:38

    Bruno escreveu:

    Coloca ai pra gente como se faz. Estou tentando fazer mais não consigo.

    Responder

  • 04/11/2009
    21:37

    AsSuStAdO escreveu:

    Cara! Muito bonito esse site hein! Parabéns.
    Só passei pra dizer isso mesmo! ehehhe

    Responder

  • 05/11/2009
    08:21

    Isaque Siqueira escreveu:

    Bom dia! Não consegui baixar os arquivos de exemplo .zip, se puder me disponibilizar agradeço. Parabéns muito bom o código.

    Responder

  • 05/11/2009
    09:28

    Davi Ferreira escreveu:

    Fala, Isaque.

    Corrigi lá o link, tenta agora. Valeu pelo toque!

    Responder

  • 05/11/2009
    22:32

    Sergio escreveu:

    Primeiramente gostaria de parabenizar.
    Estou precisando adicionar mais duas colunas, porém na hora de ordenar fica complicado pois no caso de duas colunas apenas se o box não estiver em um container vai estar no outro, mas no caso de quatro colunas é mais complicado. Obrigado desde já. Um Abraço!

    Responder

  • 17/11/2009
    17:47

    Fernando Vulcano escreveu:

    E Aí Davi Belezera? eu tô com a mesma dúvida do William. Tem como vc colocar um exemplo pra gravar no mysql? abração

    Responder

  • 01/12/2009
    09:20

    Davi Ferreira escreveu:

    Galera, é só ler o cookie e salvar as posições da forma que você achar melhor. O cookie criado fica acessível pra qualquer linguagem, php, asp etc.

    O nome no exemplo é: 'df_draganddrop'.

    Você pode salvar com AJAX via jQuery, ou toda vez que atualizar a página lê o cookie, ou criar um botão salvar etc. Existem diversos métodos.

    Responder

  • 17/11/2009
    17:48

    Sergio escreveu:

    Sugestão para um melhor efeito:

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

    Responder

  • 17/11/2009
    17:55

    Fernando Vulcano escreveu:

    Sérgio, vc ja fez gravar no bd de alguma vez?

    Responder

  • 30/11/2009
    21:27

    Righi escreveu:

    Meu problema é o seguinte:

    Tenho algumas áreas que são 'sortables', como janelas. Dentro dessas áreas tenho vários objetos que também são 'sortables'. Quero mover esse objetos de uma janela para outra, ou ainda movê-los para fora dessas áreas, no mesmo nível das mesmas.

    Já tentei de várias formas, porém quando pego algum objeto fora das áreas o jQuery entende que estou posicionando o objeto relativamente a área, e reposiciona a mesma pra mim. A questão é que se eu 'soltar' o objeto em cima da área, gostaria que o objeto passasse a fazer parte da 'sub-lista' que está dentro da área, e não fosse posicionado acima ou abaixo, reposicionando a área.

    Alguém tem alguma dica?

    Valeu!

    Responder

  • 01/12/2009
    09:18

    Davi Ferreira escreveu:

    Fala Righi,

    acho que não entendi muito bem sua dúvida. Tem como disponibilizar um HTML de exemplo?

    Abraços!

    Responder

  • 03/12/2009
    06:37

    Righi escreveu:

    Olá Davi,

    Na realidade eu já consegui resolver. Apenas utilizei o parâmetro 'items: ...' que eu não havia utilizado.

    O que estava tentando fazer era colocar um sortable dentro de outro (nested), mas ele estava se perdendo, e utilizando este parâmetro ele passou a funcionar.

    O problema agora é que com nested sortables o Internet Exploder não funciona corretamente.

    Abraços.

    Responder

  • 20/02/2010
    07:54

    Emerson Brôga escreveu:

    Muito bom o tutorial cara ....gostei mesmo ...Valeu!!!

    Responder

  • 24/02/2010
    12:31

    Alessandro Gonzalez escreveu:

    Muito bom Post Davi...


    Parabéns.

    Responder

  • 20/03/2010
    19:10

    pedro costa neves escreveu:

    e pra fazer uma parada assim? http://appear.dk/uk/
    acho que o cara usou prototype mais nao consegui entender o codigo.
    é possivel com jquery? com drag'n'drop? D: não consegui ainda se tu souber como faz seria otimoo

    Responder

  • 12/04/2010
    11:29

    Paulo Junior escreveu:

    Fala Davi,

    Estou retornando ao Blog mais uma vez, pois vi um site muito maneiro este dias e queria saber se tem como você nos passar uma explicação para montar um parecido, uma vez que ele também utiliza o conceito de Drag and Drop. O mesmo é www.netvibes.com, achei muito maneiro ele, os efeitos que ele realiza no Drag And Drop, quando colocamos uma caixa de tamanho diferente ele se adapta ao local, muito show.
    E outro efeito maneiro também, é a possibilidade de direcionarmos as caixas por setas e também poder editar sua cor, estas ações podemos contribuir para o blog é dificil de se fazer?
    abs.,
    Paulo Junior

    Responder

  • 26/05/2010
    16:40

    wake escreveu:

    Entendi tudo direitinho, só não entendi como se adiciona uma terceira coluna o #Drop-Meio, se der posta ai pra gente Davi, vlws!

    Responder

  • 27/05/2010
    07:26

    Davi Ferreira escreveu:

    Fala wake,

    é só adicionar um novo div com a classe recebeDrag. Quantos você quiser, na verdade. Aí é só posicionar via CSS.

    Valeu,

    Responder

  • 27/05/2010
    19:46

    Pedro escreveu:

    Poxa fica muito feio ler o cookie pra salvar no banco de dados, faz aí um exemplo simples de como podemos proceder para salvar no BD, da forma que vc fala parece ser mais simples e pratico entao pq reinventar a roda com cookies? faz aí, vlw

    Responder

  • 02/06/2010
    22:59

    Joabe escreveu:

    Olá Dai, otimo Post, parabens!

    Gostaria de saber se nao tem cmo nos dar uma ajuda em um exemplo desse drop drag com tres colunas? estou tendo dificuldades em implementa-lo aqui! desde já agradeço.

    Responder

  • 08/06/2010
    04:19

    Alface escreveu:

    Quando utilizo a versão do código JS sem cookie que você postou ao soltar o elemento, ele some.

    Isso deve acontecer pois eles não esta salvando no cookie

    Se tiver como me ajudar postando um código que funcionase como na versão anterior desse post fico muito grato.

    Obrigado

    Responder

  • 05/08/2010
    10:38

    Vitor Mozer escreveu:

    Olá Davi!

    Primeiro, parabéns pelo artigo. Foi de grande ajuda!

    Pode me ajudar em algo?
    Estou "printando" uma tabela que é um tipo de lista de tarefas, e nela tenho dados como "data", "horario", "titulo" e etc.. São dados tabulados, então estou utilizando a tag "table" Gostaria muito de usar o drag and drop para alterar as posições nessa lista. O banco é Mysql e faço isso alterando esses dados de "data" e "horario" ( que são a PK da tabela ) por meio de um formulário. Ou seja, quando eu movimentasse uma dessas linhas "" e preciso inverter no banco seus valores. Sei o usar o $.post do Jquery... o problema mesmo esta sendo funcionar corretamente o drag and drop e capturar os valores que vou utilizar p'ra atualizar o banco...

    Pode me ajudar, por favor?

    Um abraço e obrigado.

    Vitor

    Responder

  • 30/08/2010
    09:00

    Gabriel Ribeiro escreveu:

    Cara, tem como fazer com resize também? e tipo google wave? *-*

    Responder

  • 30/08/2010
    09:08

    Davi Ferreira escreveu:

    Oi, Gabriel.

    É possível sim, tem que usar o componente resizable:
    http://jqueryui.com/demos/resizable/

    Valeu,
    Davi

    Responder

Deixe seu comentário

Todos os campos são obrigatórios.

(Seu e-mail não será divulgado - serve apenas para validação e gravatar.)

(Tags HTML permitidas: <strong> <em> <code> <a>)

Cancelar ou Salvar

Receber alertas?

Opções

Ordenar por

Modo de exibição