Как стать автором
Обновить

Drag-n-drop группировка элементов по нескольким параметрам одновременно — sortable, а так же «Ленивый градиент»

Пару дней назад задумался о том, чтобы облегчить задачу сортировки по группам (параметрам), как только можно проще.
И как не странно решение было реализовано на стороне клиента.
Прочитав статью, мне сразу стало понятно в каком русле мне требуется двигаться.
Набравшись терпением я сел писать код. вот результат: демо.

Вот исходный код который у меня получился:
index.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>jQuery UI Sortable - Default functionality</title>
  <script src="//code.jquery.com/jquery-1.9.1.js"></script>
  <script src="//code.jquery.com/ui/1.10.4/jquery-ui.js"></script>
  <style id="mainStyle">

	#TableSortable ul { 
		top:0px;
		min-height:50px;
		list-style-type: none; 
		margin: 0 0 auto 0; 
		padding: 0; 
		width: 100%;
		border:0px solid #000;
		cursor: move;
		text-overflow: ellipsis;
		white-space: nowrap;
		word-wrap: normal;
	}
	
	#TableSortable tr td:last-child ul:before{
		content:'+ Новая группа';margin:20px 10px;
		text-shadow: 1px 1px 1px black;
	}
	#TableSortable tr td:last-child ul {
		border:1px dashed #597BA5;
		background-color:#A3B6C9;
		 color: #EFF1F3;
		 text-decoration: none;
		 font-size: 40px;
		 font-family: helvetica, arial;
		 font-weight: bold;
		 display: block;
		 text-align: left;
		 
		  /* BOX SHADOW */
		 -moz-box-shadow: 0 1px 3px black;
		 -webkit-box-shadow: 0 1px 3px black;
		 box-shadow: 0 1px 3px black;

	}
	
	#TableSortable ul li { 
		border:1px solid #666;
		border-radius:5px;
		margin: 0 3px 3px 3px; 
		padding: 0.4em; 
		padding-left: 1.5em; 
		font-size: 1.4em; 
		min-height: 18px; 
		z-index:0;
	}
  #TableSortable ul li span { 
	position: absolute; margin-left: -1.3em; 
	z-index:1;
 }
  #TableSortable ul li div#noTextSelect { 
	position: absolute;
	z-index:2;
	width:100%;
	height:100%;
	border:none;
	margin:-0.4em -0.4em -0.4em -1.5em; 
 }
 
	
	#TableSortable .element {
	 line-height: 70%;
	 color: white;
	 text-decoration: none;
	 font-size: 25px;
	 font-family: helvetica, arial;
	 font-weight: bold;
	 display: block;
	 text-align: left;
	 position: relative;
	 border: 1px solid rgb(255,255,255);

	 /* BORDER RADIUS */
	 -moz-border-radius: 5px;
	 -webkit-border-radius: 5px;
	 border-radius: 5px;


	 /* TEXT SHADOW */

	 text-shadow: 1px 1px 1px black;

	 /* BOX SHADOW */
	 -moz-box-shadow: 0 1px 3px black;
	 -webkit-box-shadow: 0 1px 3px black;
	 box-shadow: 0 1px 3px black;
	}
	
	/* WHILE BEING CLICKED */
	#TableSortable .element:active {
		-moz-box-shadow: 0 2px 6px black;
		-webkit-box-shadow: 0 2px 6px black;
	}
	
	

</style>
<script type = "text/javascript">

		
	var blockTable = 'TableSortable';
	var idElement = 'sortable';
	var QGroup = 0;
	
	function scanGroup(id){	
		var arr = [], 
			i	= 0;
			$('#'+idElement+id).children().each(function() {	
				 arr.push( { 'id' : this.id, 'idY': i++ } ); //и заносим в массив id текущего элемента
			});
		arr = { 'idX' : id, 'elements': arr };
		return arr;
	}
	
	//добавляет новую свободную группу
	function addNewGroup(){
		var id = QGroup+1;
		var appendTr = '<td valign="top" id="td'+id+'"><ul id="'+idElement+id+'"></ul></td>';
		$('#'+blockTable+' tr').html($('#'+blockTable+' tr').html() + appendTr);
		sartSelector();
		elementGradient(id);
	}
	
	//функция пробегает по всем группам начиная с указанной
	//смещает группу на денницу влево и сканирует ее
	function bustGroup(idStart){
		var arr = [];
		var result = '';
		for(var id = idStart; id <= QGroup; id++){
			$('#'+idElement+id).attr('id',idElement+(id-1));
			$('#'+blockTable+' #td'+id).attr('id','td'+(id-1));
			if(id != QGroup){
				result = scanGroup(id-1);
				arr.push( result );
			}
		}
		return arr;
	}
	
	//удаляем элемент
	function delEmptyGroup(id){
		var arr = [];
		var result = '';
		
		//если это предпоследняя группа
		if(id != QGroup-1){
			$('#'+blockTable+' #td'+id).remove();
			result = bustGroup(id + 1);
			arr.push( result );
		}else{
			$('#'+blockTable+' #td'+QGroup).remove();
		}
		sartSelector();
		return arr;
	}
	
	//запоминаем все измененные группы
	var bufferTableSortable = [];
	function setBuffer(element){	
		bufferTableSortable[bufferTableSortable.length]=element;
		if(element == QGroup){
			addNewGroup();
		}
	}
	
	function submitAjax(){
		var bufferLangth = bufferTableSortable.length;
		if(bufferLangth > 0){
			var arr = [],
				arr1 = [],
				arr2 = [],
				result = null,
				groupe = 0;
				
			//перебираем массив затронутых групп
			for(var i=0; i < bufferLangth; i++){
				groupe = bufferTableSortable[i];
				if($('#'+idElement+groupe).children().length == 0){
					arr1 = delEmptyGroup(groupe);
					if(bufferLangth > 1){
						if( groupe < bufferTableSortable[(i+1)]){
							break;
						}
					}
				}else{
					result = scanGroup(groupe);
					arr2.push( result );
				}				
			}
			arr = arr1.concat(arr2);
			//alert(JSON.stringify(arr[0]));
			alert(JSON.stringify(arr));
			
			bufferTableSortable =[];
		}
	}	
	
	//Функция для супер ленивых верстальщиков
	//генерирует цвета  градиента относительно основного цвета
	function elementGradient(id){
		//массив расцветки элементов
		var rgb=[
				"72,116,203",
				"201,160,75",
				"111,201,111",
				"222,33,222"
				];
		//длина массива расцветок
		var rgbLength=rgb.length;
		var idRgb = id-1;
		if(idRgb >= rgbLength){
			idRgb = idRgb % rgbLength;
		}else{
			idRgb = idRgb;
		}
		
		var sep = ',';
		rgb=rgb[idRgb].split(sep);
		var r = rgb[0];
		var g = rgb[1];
		var b = rgb[2];
		
		//Вычисляем градиент 
		var rgb1 = Math.abs(r) + sep + Math.abs(g) + sep + Math.abs(b);
		var rgb2 = Math.abs(r-33) + sep + Math.abs(g-36) + sep + Math.abs(b-38);
		var rgb3 = Math.abs(r-26) + sep + Math.abs(g-31) + sep + Math.abs(b-51);
		var rgb4 = Math.abs(r-44) + sep + Math.abs(g-44) + sep + Math.abs(b-59);

		var rgb1_2 = Math.abs(r-20) + sep + Math.abs(g-20) + sep + Math.abs(b-20);
		var rgb2_2 = Math.abs(r-33-20) + sep + Math.abs(g-36-20) + sep + Math.abs(b-38-20);
		var rgb3_2 = Math.abs(r-26-20) + sep + Math.abs(g-31-20) + sep + Math.abs(b-51-20);
		var rgb4_2 = Math.abs(r-44-20) + sep + Math.abs(g-44-20) + sep + Math.abs(b-59-20);
		
		if(true){//css - чтоб можно было свернуть этот большой блок в редакторе
			var css = '\
			#'+blockTable+' #'+idElement+id+' .element {\
				/* BACKGROUND GRADIENTS */\
				 background: rgb('+rgb1+');\
				 background: -moz-linear-gradient(top,\
					rgb('+rgb1+'),\
					rgb('+rgb2+') 50%,\
					rgb('+rgb3+') 51%,\
					rgb('+rgb4+'));\
				 background: -webkit-gradient(linear, left top, left bottom,\
					color-stop(0, 	rgb('+rgb1+')),\
					color-stop(.5, 	rgb('+rgb2+')),\
					color-stop(.5, 	rgb('+rgb3+')),\
					to(				rgb('+rgb4+')));\
				border: 1px solid rgb(255,255,255);\
				border-top: 1px solid rgb(255,255,255);\
			}\
			/* WHILE HOVERED */\
			#'+blockTable+' #'+idElement+id+' .element:hover {\
				background: rgb('+rgb1_2+');\
				background: -moz-linear-gradient(top,\
					rgb('+rgb1_2+'),\
					rgb('+rgb2_2+') 50%,\
					rgb('+rgb3_2+') 51%,\
					rgb('+rgb4_2+'));\
				background: -webkit-gradient(linear, left top, left bottom,\
					color-stop(0, 	rgb('+rgb1_2+')),\
					color-stop(.5, 	rgb('+rgb2_2+')),\
					color-stop(.5, 	rgb('+rgb3_2+')),\
					to(				rgb('+rgb4_2+')));\
			}';
		}

		var head = document.getElementsByTagName('head')[0];
		var style = document.createElement('style');

		style.type = 'text/css';
		if (style.styleSheet){
		  style.styleSheet.cssText = css;
		} else {
		  style.appendChild(document.createTextNode(css));
		}

		head.appendChild(style);
	 }
	 
	function sartSelector(){
		//Выбираем все сортируемые группы
		var qElement = document.querySelectorAll('#'+blockTable+' ul');
		var qElementLength =qElement.length;
		//проходим по каждой группе 
		for(var i=0; i < qElementLength; i++) {
			//Запускаем все группы
			$(function() {
				$( '#'+qElement[i].id ).sortable({
					connectWith: qElement, 
					update: function(event, ui) {setBuffer(parseInt(this.id.replace(/[^\d]*/,'')));},
					stop: function(event, ui) {submitAjax();}
					});
			});
		}
		//равномерная ширина столбцов таблицы
		$('#'+blockTable+' td').width(''+(100 / qElementLength) +'%');
		QGroup = qElementLength;
	}
	
	window.onload=function(){		
		sartSelector();
		
		//проходим по каждой группе 
		for(var i=1; i <= QGroup; i++) {
			//применяем стили
			elementGradient(i);
		}		
	}
	
	 //http://habrahabr.ru/post/124013/
	 //http://jqueryui.com/demos/sortable/
	 //http://slyweb.ru/jquerydoc/sortable-options.php - русская документация

	
    </script>
</head>
<body>
<table width="100%" height="100%" border="0" cellspacing="0" cellpadding="0" id="TableSortable">
	<tr id='1235'>
	<td valign="top" id="td1">
		<ul id="sortable1">
		  <li class="ui-state-default element" id='1'><div id="noTextSelect"></div><span class="ui-icon ui-icon-arrowthick-2-n-s"></span>Техника</li>
		  <li class="ui-state-default element" id='2'><div id="noTextSelect"></div><span class="ui-icon ui-icon-arrowthick-2-n-s"></span>Телефон</li>
		  <li class="ui-state-default element" id='3'><div id="noTextSelect"></div><span class="ui-icon ui-icon-arrowthick-2-n-s"></span>Планшет</li>
		  <li class="ui-state-default element" id='4'><div id="noTextSelect"></div><span class="ui-icon ui-icon-arrowthick-2-n-s"></span>Ноутбук</li>
		  <li class="ui-state-default element" id='5'><div id="noTextSelect"></div><span class="ui-icon ui-icon-arrowthick-2-n-s"></span>ВебКамера</li>
		  <li class="ui-state-default element" id='6'><div id="noTextSelect"></div><span class="ui-icon ui-icon-arrowthick-2-n-s"></span>Фотоаппарат</li>
		  <li class="ui-state-default element" id='7'><div id="noTextSelect"></div><span class="ui-icon ui-icon-arrowthick-2-n-s"></span>Плеер</li>
		</ul>
	</td>
	<td valign="top" id="td2">
		<ul id="sortable2">
		  <li class="ui-state-default element" id='19'><div id="noTextSelect"></div><span class="ui-icon ui-icon-arrowthick-2-n-s"></span>Быт.Приборы</li>
		  <li class="ui-state-default element" id='25'><div id="noTextSelect"></div><span class="ui-icon ui-icon-arrowthick-2-n-s"></span>Стиральная машина</li>
		  <li class="ui-state-default element" id='34'><div id="noTextSelect"></div><span class="ui-icon ui-icon-arrowthick-2-n-s"></span>Посудомойка</li>
		  <li class="ui-state-default element" id='15'><div id="noTextSelect"></div><span class="ui-icon ui-icon-arrowthick-2-n-s"></span>Пылесос</li>
		  <li class="ui-state-default element" id='12'><div id="noTextSelect"></div><span class="ui-icon ui-icon-arrowthick-2-n-s"></span>Обогреватель</li>
		  <li class="ui-state-default element" id='18'><div id="noTextSelect"></div><span class="ui-icon ui-icon-arrowthick-2-n-s"></span>Фен</li>
		</ul>
	</td>
	<td valign="top" id="td3">
		<ul id="sortable3">
		  <li class="ui-state-default element" id='31'><div id="noTextSelect"></div><span class="ui-icon ui-icon-arrowthick-2-n-s"></span>Кухня</li>
		  <li class="ui-state-default element" id='81'><div id="noTextSelect"></div><span class="ui-icon ui-icon-arrowthick-2-n-s"></span>Кофеварка</li>
		  <li class="ui-state-default element" id='27'><div id="noTextSelect"></div><span class="ui-icon ui-icon-arrowthick-2-n-s"></span>Блендер</li>
		  <li class="ui-state-default element" id='29'><div id="noTextSelect"></div><span class="ui-icon ui-icon-arrowthick-2-n-s"></span>Печка</li>
		  <li class="ui-state-default element" id='35'><div id="noTextSelect"></div><span class="ui-icon ui-icon-arrowthick-2-n-s"></span>Микроволновка</li>
		  <li class="ui-state-default element" id='36'><div id="noTextSelect"></div><span class="ui-icon ui-icon-arrowthick-2-n-s"></span>Чайник</li>
		  <li class="ui-state-default element" id='47'><div id="noTextSelect"></div><span class="ui-icon ui-icon-arrowthick-2-n-s"></span>Холодильник</li>
		</ul>
	</td>
	<td valign="top" id="td4">
		<ul id="sortable4">
		</ul>
	</td>
</tr>
</table>
<div id="info"></div>
</body>
</html>

Немного пояснений:
В результате получится многомерный массив, типа такого:
[{"idX":1,"el6ments":[{"id":"19","idY":0},{"id":"25","idY":l}, {"id":"15","idY":2},{"id":"12","idY":3},{"id":"18","idY":4}]}]

Где: idX — номер группы (у меня первая группа начинается с 1),
el6ments — массив с элементами в группе,
id — уникальный кодификатор в БД,
idY — позиция в группе (у меня первая группа начинается с 0).
На серверной же стороне это будет выглядеть примерно так:
mysql_query("UPDATE `table` SET `idX`=1,`idY`=0 WHERE `id`=19");

Для визуализации я использовал обычный alert();, который нужно заменить на обычный ajax и с этой задачей я думаю вы справитесь.

Как оказалось, мене сложно подбирать градиент, тк я не дизайнер. Поэтому я написал свою функцию, которую я назвал "Ленивый градиент", все что только нужно — это заменить RGB цвета на свои.
Ленивый градиент

	//Функция для супер ленивых верстальщиков
	//генерирует цвета  градиента относительно основного цвета
	function elementGradient(id){
		//массив расцветки элементов
		var rgb=[
				"72,116,203",
				"201,160,75",
				"111,201,111",
				"222,33,222"
				];
		//длина массива расцветок
		var rgbLength=rgb.length;
		var idRgb = id-1;
		if(idRgb >= rgbLength){
			idRgb = idRgb % rgbLength;
		}else{
			idRgb = idRgb;
		}
		
		var sep = ',';
		rgb=rgb[idRgb].split(sep);
		var r = rgb[0];
		var g = rgb[1];
		var b = rgb[2];
		
		//Вычисляем градиент 
		var rgb1 = Math.abs(r) + sep + Math.abs(g) + sep + Math.abs(b);
		var rgb2 = Math.abs(r-33) + sep + Math.abs(g-36) + sep + Math.abs(b-38);
		var rgb3 = Math.abs(r-26) + sep + Math.abs(g-31) + sep + Math.abs(b-51);
		var rgb4 = Math.abs(r-44) + sep + Math.abs(g-44) + sep + Math.abs(b-59);

		var rgb1_2 = Math.abs(r-20) + sep + Math.abs(g-20) + sep + Math.abs(b-20);
		var rgb2_2 = Math.abs(r-33-20) + sep + Math.abs(g-36-20) + sep + Math.abs(b-38-20);
		var rgb3_2 = Math.abs(r-26-20) + sep + Math.abs(g-31-20) + sep + Math.abs(b-51-20);
		var rgb4_2 = Math.abs(r-44-20) + sep + Math.abs(g-44-20) + sep + Math.abs(b-59-20);
		
		if(true){//css - чтоб можно было свернуть этот большой блок в редакторе
			var css = '\
			#'+blockTable+' #'+idElement+id+' .element {\
				/* BACKGROUND GRADIENTS */\
				 background: rgb('+rgb1+');\
				 background: -moz-linear-gradient(top,\
					rgb('+rgb1+'),\
					rgb('+rgb2+') 50%,\
					rgb('+rgb3+') 51%,\
					rgb('+rgb4+'));\
				 background: -webkit-gradient(linear, left top, left bottom,\
					color-stop(0, 	rgb('+rgb1+')),\
					color-stop(.5, 	rgb('+rgb2+')),\
					color-stop(.5, 	rgb('+rgb3+')),\
					to(				rgb('+rgb4+')));\
				border: 1px solid rgb(255,255,255);\
				border-top: 1px solid rgb(255,255,255);\
			}\
			/* WHILE HOVERED */\
			#'+blockTable+' #'+idElement+id+' .element:hover {\
				background: rgb('+rgb1_2+');\
				background: -moz-linear-gradient(top,\
					rgb('+rgb1_2+'),\
					rgb('+rgb2_2+') 50%,\
					rgb('+rgb3_2+') 51%,\
					rgb('+rgb4_2+'));\
				background: -webkit-gradient(linear, left top, left bottom,\
					color-stop(0, 	rgb('+rgb1_2+')),\
					color-stop(.5, 	rgb('+rgb2_2+')),\
					color-stop(.5, 	rgb('+rgb3_2+')),\
					to(				rgb('+rgb4_2+')));\
			}';
		}

		var head = document.getElementsByTagName('head')[0];
		var style = document.createElement('style');

		style.type = 'text/css';
		if (style.styleSheet){
		  style.styleSheet.cssText = css;
		} else {
		  style.appendChild(document.createTextNode(css));
		}

		head.appendChild(style);
	 }


Прототипом данной функции являлся простой php скрипт:
Скрипт

<?php 
$rgb="222,33,33";
$sep=',';
$rgb=explode($sep, $rgb);
$r=$rgb[0];
$g=$rgb[1];
$b=$rgb[0];

$rgb1=abs($r).$sep.abs($g).$sep.abs($b);
$rgb2=abs($r-33).$sep.abs($g-36).$sep.abs($b-38);
$rgb3=abs($r-26).$sep.abs($g-31).$sep.abs($b-51);
$rgb4=abs($r-44).$sep.abs($g-44).$sep.abs($b-59);

$rgb1_2=abs($r-20).$sep.abs($g-20).$sep.abs($b-20);
$rgb2_2=abs($r-33-20).$sep.abs($g-36-20).$sep.abs($b-38-20);
$rgb3_2=abs($r-26-20).$sep.abs($g-31-20).$sep.abs($b-51-20);
$rgb4_2=abs($r-44-20).$sep.abs($g-44-20).$sep.abs($b-59-20);

echo "	#TableSortable #sortable1 .element {		
		 /* BACKGROUND GRADIENTS */
		 background: rgb($rgb1);
		 background: -moz-linear-gradient(top, 
											rgb($rgb1), 
											rgb($rgb2) 50%, 
											rgb($rgb3) 51%, 
											rgb($rgb4));
		 background: -webkit-gradient(linear, left top, left bottom, 
							color-stop(0, 	rgb($rgb1)), 
							color-stop(.5, 	rgb($rgb2)), 
							color-stop(.5, 	rgb($rgb3)), 
							to(				rgb($rgb4)));
		
		border: 1px solid rgb(255,255,255);
		border-top: 1px solid rgb(255,255,255);
		/**/
	}
	
	/* WHILE HOVERED */
	#TableSortable #sortable1 .element:hover {
		background: rgb($rgb1_2);
	 	background: -moz-linear-gradient(top, 
										rgb($rgb1_2), 
										rgb($rgb2_2) 50%, 
										rgb($rgb3_2) 51%, 
										rgb($rgb4_2));
		background: -webkit-gradient(linear, left top, left bottom, 
						color-stop(0, 	rgb($rgb1_2)), 
						color-stop(.5, 	rgb($rgb2_2)), 
						color-stop(.5, 	rgb($rgb3_2)), 
						to(				rgb($rgb4_2)));
			
	}";
?>


Успехов вам!
Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.