Drag and Drop HTML Components with jQuery

3

I'm developing a project for my academic course and I have the following code:

// Esse é um json que vem do banco de dados
var Componentes = {"input": {"Label": "Textbox","Tag": "input",
"Attributes": {"type": "text"},"Class": "form-control"},"btn": 
{"Label": "Button","Tag": "button","Attributes": {"type": "button"},
"Class": "btn","Childrens":[{"Type": "text","Value": "Botão"}]},
"label": {"Label": "Label","Tag": "label","Class": "","Childrens":[
{"Type": "text","Value": "Label:"}]},"form-help":{"Label": "Form help",
"Tag": "span","Class": "help-block","Childrens":[{"Type": "text",
"Value": "I'm a help text."}]},"textbox": {"Label": "Textbox Group",
"Tag": "div","Class": "form-group","Siblings":[],"Childrens":[
{"Type": "component","Value":"label"}, {"Type": "component",
"Value":"input"},{"Type": "component","Value": "form-help"}],
"Rules": {"AllowChilds": false}}};


// Evento dos botões, tem que implementar um evento de Drag n Drop
$(document).on('click', '#componentes .add', function(event){
   event.preventDefault();
   AddComponent( $(this).data('componente') );
});

// Método que adiciona o componente
function AddComponent(componente, retorna){
   // Parâmetro para recursividade (adicionar elementos filhos)
   retorna = retorna == undefined ? false : retorna;

   // Componente escolhido foi armazenado na variável c
   var c = Componentes[componente];

   // Objeto para registro dos componentes, pode ignorar
   var cmp = {
      'CID': Date.now(),
      'Type': componente
   };

   // Elemento criado 
   var $element = $('<'+c.Tag+' />');
   $element.addClass(c.Class+" component "+c.EditClass);

   // Adiciona todos os atributos padrões no elemento
   if (c.Attributes != undefined && c.Attributes.length > 0)
      for(attr in c.Attributes)
         $element.attr(attr, c.Attributes[attr]);

   // Atributo de controle de edição, pode ignorar
   $element.attr('data-component', cmp.Type)
         .attr('data-cid', cmp.CID);

   // Adiciona todos os elementos filhos
   if (c.Childrens != undefined && c.Childrens.length > 0){
      for(children in c.Childrens){

         switch(c.Childrens[children].Type){
            case "component":
               $element.append( AddComponent(c.Childrens[children].Value, true) );
               break;
            case "text":
               $element.text( c.Childrens[children].Value );
               break;
         }

      }
   }

   // Verifica se é pra adicionar a área de design ou retornar o elemento
   // para ser adicionado a um elemento pai
   if (retorna)
      return $element;
   else
      $('#edit-area .active').append($element);
}
/* CSS meramente para demostração*/
#edit-area {
  display: block;
  margin-bottom:20px;
  box-sizing: content-box;
  min-height: 200px;
  position: relative;
  overflow: auto;
  background: #E0E0E0;
}

.row{
    background:white;
    box-shadow: 2px 2px 5px rgba(0,0,0,0.2);
    margin:10px;
    padding:5px;
    min-height:30px;
}
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script><divclass="row">
   <div class="col-lg-9 col-md-9">
      <div id="edit-area">
          <div class="row">
              <div class="col-lg-12 col-md-12 active"></div>
          </div>
      </div>
   </div>
   <div class="col-lg-3 col-md-3" id="componentes">
      <div class="list-group">
        <button class="add list-group-item" data-componente="textbox">Textbox Group</button>
        <button class="add list-group-item" data-componente="input">Input</button>
        <button class="add list-group-item" data-componente="btn">Botão</button>
      </div>
   </div>
</div>
  

The code is purely for demonstration, testing and implementation, the actual code is a little bit "bigger". Here you only have the essentials to create the element (s).

With this code I can create a component by clicking a button in the list. My goal is to use the Drag n Drop style, click on a button and drag to the area I want.

More or less in this style.

How could I do this using just jQuery (without using plugins or other libraries). If possible have option to reposition them after they are created.

Fiddler for testing

There is no problem creating new elements for handling the component, because the components are stored in objects (with ids, types, properties, etc.) and then saved in the database the generated HTML elements are only for the visual designer.

    
asked by anonymous 17.07.2015 / 23:06

1 answer

3

This question is rather broad because this functionality is relatively complex. Still, it is very useful and I leave here a suggestion on the part of drag & drop.

To drag an element you need to know several things:

  • mouse position
  • the element you are dragging
  • the position of the target element

When dropping the element we need to know if it is inside the destination element to confirm or cancel the drag.

So I created functions for what I mentioned above and global variables that keep "the state of things".

$(document).ready(function () {
    var targetEl = document.getElementById('edit-area'); // elemento destino
    var target = targetEl.getBoundingClientRect(); // dimensões 
    var startPosition; // para guardar a posição inicial do elemento caso queiramos cancelar
    var dragging = null; // flag para saber se há drag ativo
    var offset; // diferença entre canto do elemento e onde o mouse tocou

    function offsetDiff(el, ev) { // calcular diferença entre canto do elemento e onde o mouse tocou
        var elPos = el.getBoundingClientRect();
        return {
            x: ev.pageX - elPos.left,
            y: ev.pageY - elPos.top
        }
    }

    function isInsideTarget(el) { // saber se elemento arrastado está dentro do elemento destino
        var elPos = el.getBoundingClientRect();
        var alignedX = target.left <= elPos.left && target.right >= elPos.right;
        var alignedY = target.top <= elPos.top && target.bottom >= elPos.bottom;
        if (alignedX && alignedY) return true;
        return false;
    }

    function drop(el, cancel) { // largar ou cancelar
        dragging = null;
        el.style.position = 'relative';
        el.style.left = 'auto';
        el.style.top = 'auto';
        if (cancel) return;
        el.classList.remove('add');
        $(targetEl).append(el);
    }

    $(document.body).on("mousemove", function (e) {
        if (!dragging) return;
        $(dragging).offset({
            top: e.pageY - offset.y,
            left: e.pageX - offset.x
        });

    });


    $(document.body).on('mousedown', '.add', function (e) {
        dragging = e.target;
        offset = offsetDiff(dragging, e);
        startPosition = dragging.getBoundingClientRect();

    });

    $(document.body).on("mouseup", function (e) {
        if (!dragging) return;
        var isDroppable = isInsideTarget(dragging);
        drop(dragging, !isDroppable);
    });
});

jsFiddle: link

    
18.07.2015 / 01:04