Skip to content

Instantly share code, notes, and snippets.

@idahopotato1
Last active April 8, 2021 23:47
Show Gist options
  • Save idahopotato1/8715085ca0f1b543806646a9a6a63243 to your computer and use it in GitHub Desktop.
Save idahopotato1/8715085ca0f1b543806646a9a6a63243 to your computer and use it in GitHub Desktop.
Drag and Drop Cells - design ad decision tree

Drag and Drop Cells

This is a drag and drop list implemented and styled to be on a table view. Styles needs refactoring but will do for now.

The Brand(Nike, Adidas) cannot be dragged and dropped at all.

The Main Category(Male,Female) cannot be dragged and dropped beyond its parent Brand.

The Sub Category(Male kids, Male adult, Female kids, Female adult) cannot be dragged and dropped beyond its Main Category.

The Product cannot be dragged and dropped beyond its parent Sub Category

A Pen by Avelino P. Tiu III on CodePen.

License.

<html lang="en" ng-app="demo">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body ng-controller="SimpleDemoController">
<div class="container-fluid">
<div class="simpleDemo row">
<div class="col-md-8">
<div ng-repeat="list in lists" class="table-format">
<span class="brand">
<div class="width-holder">{{list.brand}}</div>
</span>
<ul class="categories-list"
dnd-list="list.categories"
dnd-allowed-types="list.allowedTypes">
<li ng-repeat="category in list.categories"
dnd-draggable="category"
dnd-type="category.type"
dnd-moved="list.categories.splice($index, 1)"
dnd-effect-allowed="move">
<span class="category-name">
<div class="width-holder">{{ category.name }}</div>
</span>
<ul class="subcategories-list"
dnd-list="category.subcategories"
dnd-allowed-types="category.allowedTypes">
<li ng-repeat="subcategory in category.subcategories"
dnd-draggable="subcategory"
dnd-type="subcategory.type"
dnd-moved="category.subcategories.splice($index, 1)"
dnd-effect-allowed="move">
<span class="subcategory-name">
<div class="width-holder">{{subcategory.name}}</div>
</span>
<ul class="products-list"
dnd-list="subcategory.products"
dnd-allowed-types="subcategory.allowedTypes">
<li ng-repeat="product in subcategory.products"
dnd-draggable="product"
dnd-type="product.type"
dnd-moved="subcategory.products.splice($index, 1)">
<span class="product-name">
<div class="width-holder">{{product.name}}</div>
</span>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
</div>
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Generated Model</h3>
</div>
<div class="panel-body">
<pre class="code">{{modelAsJson}}</pre>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
/**START OF drog and drop directive https://github.com/marceljuenemann/angular-drag-and-drop-lists **/
angular.module("dndLists",[]).directive("dndDraggable",["$parse","$timeout","dndDropEffectWorkaround","dndDragTypeWorkaround",function(e,n,r,t){return function(a,o,d){o.attr("draggable","true"),d.dndDisableIf&&a.$watch(d.dndDisableIf,function(e){o.attr("draggable",!e)}),o.on("dragstart",function(i){i=i.originalEvent||i,i.dataTransfer.setData("Text",angular.toJson(a.$eval(d.dndDraggable))),i.dataTransfer.effectAllowed=d.dndEffectAllowed||"move",o.addClass("dndDragging"),n(function(){o.addClass("dndDraggingSource")},0),r.dropEffect="none",t.isDragging=!0,t.dragType=d.dndType?a.$eval(d.dndType):void 0,e(d.dndDragstart)(a,{event:i}),i.stopPropagation()}),o.on("dragend",function(i){i=i.originalEvent||i;var f=r.dropEffect;a.$apply(function(){switch(f){case"move":e(d.dndMoved)(a,{event:i});break;case"copy":e(d.dndCopied)(a,{event:i});break;case"none":e(d.dndCanceled)(a,{event:i})}e(d.dndDragend)(a,{event:i,dropEffect:f})}),o.removeClass("dndDragging"),n(function(){o.removeClass("dndDraggingSource")},0),t.isDragging=!1,i.stopPropagation()}),o.on("click",function(n){d.dndSelected&&(n=n.originalEvent||n,a.$apply(function(){e(d.dndSelected)(a,{event:n})}),n.stopPropagation())}),o.on("selectstart",function(){this.dragDrop&&this.dragDrop()})}}]).directive("dndList",["$parse","$timeout","dndDropEffectWorkaround","dndDragTypeWorkaround",function(e,n,r,t){return function(a,o,d){function i(e,n,r){var t=y?e.offsetX||e.layerX:e.offsetY||e.layerY,a=y?n.offsetWidth:n.offsetHeight,o=y?n.offsetLeft:n.offsetTop;return o=r?o:0,o+a/2>t}function f(){var e;return angular.forEach(o.children(),function(n){var r=angular.element(n);r.hasClass("dndPlaceholder")&&(e=r)}),e||angular.element("<li class='dndPlaceholder'></li>")}function l(){return Array.prototype.indexOf.call(D.children,v)}function g(e){if(!t.isDragging&&!E)return!1;if(!c(e.dataTransfer.types))return!1;if(d.dndAllowedTypes&&t.isDragging){var n=a.$eval(d.dndAllowedTypes);if(angular.isArray(n)&&-1===n.indexOf(t.dragType))return!1}return d.dndDisableIf&&a.$eval(d.dndDisableIf)?!1:!0}function s(){return p.remove(),o.removeClass("dndDragover"),!0}function u(n,r,o,d){return e(n)(a,{event:r,index:o,item:d||void 0,external:!t.isDragging,type:t.isDragging?t.dragType:void 0})}function c(e){if(!e)return!0;for(var n=0;n<e.length;n++)if("Text"===e[n]||"text/plain"===e[n])return!0;return!1}var p=f(),v=p[0],D=o[0];p.remove();var y=d.dndHorizontalList&&a.$eval(d.dndHorizontalList),E=d.dndExternalSources&&a.$eval(d.dndExternalSources);o.on("dragover",function(e){if(e=e.originalEvent||e,!g(e))return!0;if(v.parentNode!=D&&o.append(p),e.target!==D){for(var n=e.target;n.parentNode!==D&&n.parentNode;)n=n.parentNode;n.parentNode===D&&n!==v&&(i(e,n)?D.insertBefore(v,n):D.insertBefore(v,n.nextSibling))}else if(i(e,v,!0))for(;v.previousElementSibling&&(i(e,v.previousElementSibling,!0)||0===v.previousElementSibling.offsetHeight);)D.insertBefore(v,v.previousElementSibling);else for(;v.nextElementSibling&&!i(e,v.nextElementSibling,!0);)D.insertBefore(v,v.nextElementSibling.nextElementSibling);return d.dndDragover&&!u(d.dndDragover,e,l())?s():(o.addClass("dndDragover"),e.preventDefault(),e.stopPropagation(),!1)}),o.on("drop",function(e){if(e=e.originalEvent||e,!g(e))return!0;e.preventDefault();var n,t=e.dataTransfer.getData("Text")||e.dataTransfer.getData("text/plain");try{n=JSON.parse(t)}catch(o){return s()}var i=l();if(d.dndDrop&&(n=u(d.dndDrop,e,i,n),!n))return s();var f=a.$eval(d.dndList);return a.$apply(function(){f.splice(i,0,n)}),u(d.dndInserted,e,i,n),r.dropEffect="none"===e.dataTransfer.dropEffect?"copy"===e.dataTransfer.effectAllowed||"move"===e.dataTransfer.effectAllowed?e.dataTransfer.effectAllowed:e.ctrlKey?"copy":"move":e.dataTransfer.dropEffect,s(),e.stopPropagation(),!1}),o.on("dragleave",function(e){e=e.originalEvent||e,o.removeClass("dndDragover"),n(function(){o.hasClass("dndDragover")||p.remove()},100)})}}]).directive("dndNodrag",function(){return function(e,n){n.attr("draggable","true"),n.on("dragstart",function(e){e=e.originalEvent||e,e.dataTransfer.types&&e.dataTransfer.types.length||e.preventDefault(),e.stopPropagation()}),n.on("dragend",function(e){e=e.originalEvent||e,e.stopPropagation()})}}).factory("dndDragTypeWorkaround",function(){return{}}).factory("dndDropEffectWorkaround",function(){return{}});
/**END OF drog and drop directive https://github.com/marceljuenemann/angular-drag-and-drop-lists **/
'use strict';
angular.module("demo",["dndLists"]).controller("SimpleDemoController", function($scope) {
$scope.lists = [
{
brand: "Nike",
allowedTypes: ['b1'],
categories: [
{
name: "Male",
type: "b1",
allowedTypes: ['c1'],
subcategories: [
{
name: "Male Adults",
type: "c1",
allowedTypes: ['sc1'],
products: [
{name: "Air 21 Shoes", type: "sc1"},
{name: "Air 33 Shoes", type: "sc1"}
]
},
{
name: "Male Kids",
type: "c1",
allowedTypes: ['sc1.2'],
products: [
{name: "Air 1 Kids Shoes", type: "sc1.2"},
{name: "Air 2 Kids Shoes", type: "sc1.2"}
]
}
]
},
{
name: "Female",
type: "b1",
allowedTypes: ['c2'],
subcategories: [
{
name: "Female Adults",
type: "c2",
allowedTypes: ['sc2'],
products: [
{name: "Free 3.0 Flyknit Shoes", type: "sc2"},
{name: "Free 5.0 Flash Shoes", type: "sc2"}
]
},
{
name: "Female Kids",
type: "c2",
allowedTypes: ['sc2.2'],
products: [
{name: "Free 3.0 Flyknit Kids Shoes", type: "sc2.2"},
{name: "Free 5.0 Flash Kids Shoes", type: "sc2.2"}
]
}
]
}
],
},
{
brand: "Adidas",
allowedTypes: ['b2'],
categories: [
{
name: "Male",
type: "b2",
allowedTypes: ['c3'],
subcategories: [
{
name: "Male Adults",
type: "c3",
allowedTypes: ['sc3'],
products: [
{name: "D Rose 773 4 Shoes", type: "sc3"},
{name: "J Wall 2.0 Shoes", type: "sc3"}
]
},
{
name: "Male Kids",
type: "c3",
allowedTypes: ['sc3.2'],
products: [
{name: "D Rose 773 4 Kids Shoes", type: "sc3.2"},
{name: "J Wall 2.0 Kids Shoes", type: "sc3.2"}
]
}
]
},
{
name: "Female",
type: "b2",
allowedTypes: ['c4'],
subcategories: [
{
name: "Female Adults",
type: "c4",
allowedTypes: ['sc4'],
products: [
{name: "Energy Boost 3 Shoes", type: "sc4"},
{name: "Supernova Glide 8 Shoes", type: "sc4"}
]
},
{
name: "Female Kids",
type: "c4",
allowedTypes: ['sc4.2'],
products: [
{name: "Energy Boost 3 Kids Shoes", type: "sc4.2"},
{name: "Supernova Glide 8 Kids Shoes", type: "sc4.2"}
]
}
]
}
],
}
];
// Model to JSON for demo purpose
$scope.$watch('lists', function(model) {
$scope.modelAsJson = angular.toJson(model, true);
}, true);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.14/angular.min.js"></script>
/**
* For the correct positioning of the placeholder element, the dnd-list and
* it's children must have position: relative
*/
.simpleDemo ul{
margin:0;
}
.simpleDemo ul[dnd-list],
.simpleDemo ul[dnd-list] > li {
position: relative;
}
/**
* The dnd-list should always have a min-height,
* otherwise you can't drop to it once it's empty
*/
.simpleDemo ul[dnd-list] {
min-height: 42px;
padding-left: 0px;
}
/**
* The dndDraggingSource class will be applied to
* the source element of a drag operation. It makes
* sense to hide it to give the user the feeling
* that he's actually moving it.
*/
.simpleDemo ul[dnd-list] .dndDraggingSource {
display: none;
}
/**
* An element with .dndPlaceholder class will be
* added to the dnd-list while the user is dragging
* over it.
*/
.simpleDemo ul[dnd-list] .dndPlaceholder {
display: block;
background-color: #ddd;
min-height: 42px;
}
/**
* The dnd-lists's child elements currently MUST have
* position: relative. Otherwise we can not determine
* whether the mouse pointer is in the upper or lower
* half of the element we are dragging over. In other
* browsers we can use event.offsetY for this.
*/
.simpleDemo ul[dnd-list] li {
background-color: #fff;
display: block;
padding: 10px 0;
margin-bottom: -1px;
}
/**
* Show selected elements in green
*/
.simpleDemo ul[dnd-list] li.selected {
background-color: #dff0d8;
color: #3c763d;
}
.table-format,
.categories-list li,
.table-format .subcategories-list li,
.table-format .products-list li{
overflow: hidden;
zoom:1;
}
/* remove padding for li for now */
.categories-list li,
.table-format .subcategories-list li,
.table-format .products-list li{
padding: 0 !important;
}
.table-format .products-list li{
border-bottom: 1px solid red;
border-spacing: 0px;
}
.table-format .subcategories-list{
border-bottom: 1px solid red;
}
.table-format .subcategories-list li{
border-spacing: 0px;
}
.table-format .products-list li:last-child{
border-bottom: 2px solid red;
}
.table-format .brand,
.table-format .category-name,
.table-format .subcategory-name,
.table-format .product-name{
display: table-cell;
vertical-align: top;
padding-right: 10px;
padding-left: 10px;
border-right:1px solid red;
border-bottom: 1px solid red;
}
.table-format .category-name,
.table-format .subcategory-name{
border-bottom: 2px solid red;
}
.table-format .brand .width-holder,
.table-format .category-name .width-holder,
.table-format .subcategory-name .width-holder,
.table-format .product-name .width-holder{
width: 110px;
display: inline-block;
margin:5px 0;
}
.table-format .categories-list,
.table-format .subcategories-list,
.table-format .products-list{
display: table-cell;
vertical-align: top;
width: 10000px;
overflow: hidden;
zoom:1;
}
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment