Javascript-форум (https://javascript.ru/forum/)
-   Ваши сайты и скрипты (https://javascript.ru/forum/project/)
-   -   Класс выделения элементов. Нужна критика. (https://javascript.ru/forum/project/44073-klass-vydeleniya-ehlementov-nuzhna-kritika.html)

Zuenf 05.01.2014 03:42

Класс выделения элементов. Нужна критика.
 
Учусь на практике.
Класс получает все выделяемые html-элементы(с классом selectClass) между координатами mousedown и mouseup. Существует возможность нажатия ctrl и соответственно добавления элементов в уже существующее выделение.

Хочется узнать, что сделано криво, что не красиво, а что вообще не правильно.
Если есть какие-то функции, существующие, чтобы облегчить какие-либо ситуации в моем примере, буду рад узнать о них.
//Класс Select
function Select( selectBox, selectClass, visualSelectionClass){
	var selectBox = selectBox || document; //Контейнер в котором возможно выделение
	var selectClass = selectClass || '.selectable'; //Клас элемента который можно выделить
	var visualSelectionClass = visualSelectionClass || '.selection'; //Класс для блока визуального выделения
	var coord = {}; //Координаты выделенной области
	var ctrl = false; //Состояние клавиши CTRL
	
	var selectedElements = []; //Выделенные элементы
	
	//Координаты мыши относительно элемента
	function mouseCoordRelativeElement(event, elem){
		return {'X':event.pageX - $(elem).offset().left, 'Y':event.pageY - $(elem).offset().top};
	}
	
	//----------------------Нажатие CTRL------------------------
	function isCtrl(e){
		if( e.keyCode == 17 ){
			ctrl = true;
			$(document).unbind( 'keydown', isCtrl );
			$(document).bind( 'keyup', offCtrl );
		}
	}
	
	function offCtrl(e){
		if( e.keyCode == 17 ){
			ctrl = false;
			$(document).bind( 'keydown', isCtrl );
			$(document).unbind( 'keyup', offCtrl );
		}
	}
	//----------------------------------------------------------
	
	//--------------Выделение и добавление в массив-------------
	function startSelect(e){
		var t = e.target
		var c = mouseCoordRelativeElement(e, selectBox);
		//Клик был не на элементе(на пустом пространстве) -> начинаем выделение;
		if ( !$(t).hasClass( selectClass.substring(1) ) ){
			coord.x1 = c.X;
			coord.y1 = c.Y;
			//Начало показа визуального выделения;
			startVisualSelection();
		}else{//Клик был на элементе;
			//Если он не входит в массив выбранных элементов
          if(selectedElements.indexOf(t) == -1){
            if ( !ctrl ) selectedElements = [];
			//Запиcываем его в массив выделенных элементов;
			selectedElements.push(t);
          }
		}
		return false;
	}
	
	function endSelect(e){
      	if(coord.x1 === undefined) return;
		var c = mouseCoordRelativeElement(e, selectBox);
		coord.x2 = c.X;
		coord.y2 = c.Y;
		//По идее координаты начала выделения < координат конца
		//Cоответственно если координаты mouseup < координаты начала выделения(mousedown), то координаты начала должны стать координатами конца 
		if( c.X < coord.x1 ){
			coord.x2 = coord.x1;
			coord.x1 = c.X;
		}
		if( c.Y < coord.y1 ){
			coord.y2 = coord.y1;
			coord.y1 = c.Y;
		}
		//Проверка вхождения выделяемых элементов в область выделения и запись их в массив выделенных элементов
		selectElements();
		//Остановка визуального выделения;
		stopVisualSelection();
        coord = {};
	}
	
	function selectElements(){
		if( !ctrl ) selectedElements = [];
		//Идем по всем выделяемым элементам
		$(selectClass).each(function(){
			var y = $(this).position().top + $(this).outerHeight()/2;
			var x = $(this).position().left + $(this).outerWidth()/2;
			//Если центральная точка элемента находится внутри выделения и его еще нет в масииве то...
			if( x>coord.x1 && y>coord.y1 && x<coord.x2 && y<coord.y2 && selectedElements.indexOf(this) == -1 ){
				//...добавляем в массив
				selectedElements.push(this);
			}
		});
	}
	//---------------------------------------------------
	
	//----------------Визуальная часть-------------------
	function initVisualSelection(){
		$(selectBox).append('<div class="'+visualSelectionClass.substring(1)+'"></div>');
		$(visualSelectionClass).css({'position':'absolute', 'display':'none'});
	}
	
	function startVisualSelection(){
		$(visualSelectionClass).css({'display':'block', 'top':coord.y1, 'left':coord.x1});
		$(selectBox).bind( 'mousemove', viewVisualSelection );
	}
	
	function viewVisualSelection(e){
		var c = mouseCoordRelativeElement(e, selectBox);
		var height = c.Y - coord.y1;
		var width = c.X - coord.x1;
		var top = coord.y1;
		var left = coord.x1;
		if( height < 0 ){
			top = c.Y;
			height *= -1;
		}
		if( width < 0 ){
			left = c.X;
			width *= -1;
		}
		$(visualSelectionClass).css({'top':top, 'left':left,'height':height, 'width':width});
	}
	
	function stopVisualSelection(){
		$(visualSelectionClass).css({'display':'none','height':'0', 'width':'0'});
		$(selectBox).unbind( 'mousemove', viewVisualSelection );
	}
	//------------------------------------------
	
	this.init = function(){
		$(document).bind( 'keydown', isCtrl );
		$(selectBox).bind( 'mousedown', startSelect );
		$(selectBox).bind( 'mouseup', endSelect );
		//Инициализация визуального выделения;
		initVisualSelection();
	}
	
	//Возвращает массив выбраных елементов;
	this.getSelectedElements = function(){
		return selectedElements;
	}
}

Zuenf 05.01.2014 13:47

Пример возможного использования - http://learn.javascript.ru/play/VT7sZb
Пока делал нашел и исправил несколько косяков.

BallsShaped 05.01.2014 15:07

Мне кажется такая структура кода странной. Структура убогая и неудобная, но распространённая. Как по мне, так "так верстают только мудаки".:) Есть такая проблема: на любую задачу есть готовое решение, которое немного не подходит. Вот мне нужно выделение, и я думаю: "Надо взять готовое решение!" Нахожу, внедряю и тут мне говорят: "Нужно, чтобы внизу был счётчик выбранных элементов."
- Ну, - думаю я. - Отлично! Это ж класс. Создам дочерний класс с нужным мне функционалом...
Хренцы-бубенцы! Наследованием проблему не решить.

Тогда многие дают при инициализации передать типа обработчики на "внутренние события" (стандартный для jQ-плагинов паттерн с объектом options). Но такой вариант мне тоже кажется туповатым.

Мне кажется, что в решениях, где вся суть в манипуляциях с DOM-элементами, необходимо чтобы на каждую манипуляцию происходило соответствующее событие. Тогда, чтобы добавить счётчик, нужно сделать что-то вроде того:
$(".some-container").on({
    select: function () {
        $(".counter", this).html($(".counter", this).html() + 1);
    },
    unselect: function () {
        $(".counter", this).html($(".counter", this).html() - 1);
    }
});

Это псевдо-быдлокод, чтобы уловить идею.:)

Zuenf 05.01.2014 16:39

Про наследование почитаю, не задумывался об этом.

А про собственные события с отдельными названиеми, пробегала мысля(видел как-то давно в исходниках jQuery), может подскажешь где почитать про их создание или как там они делаются?

P.S. Спасибо за "наставничество", плюсую :) .

BallsShaped 05.01.2014 17:30

Zuenf, могу предложить только документацию или гугл.:) С событиями тоже есть проблема: они - глобальные. Так что делать в своём коде событие с именем select - не лучшая мысль, ибо есть уже событие с таким именем. Решить можно с помощью какого-нибудь префикса. Например, у тебя будут имена событий foo_select, foo_startselection и т.д.

Zuenf 05.01.2014 17:40

BallsShaped, ок, спасибо! Будем делать.

Zuenf 29.01.2014 16:35

Сделал в виде события - https://www.dropbox.com/s/masf1jhjpe...nts%20Event.js
Пример - http://fiddle.jshell.net/h8G3g/5/

И опять же, если вам не трудно, посмотрите, что сделано не правильно/не логично.
Буду рад услышать, все, что приходит на ум по коду и его оформлению.

Возможности
Бинд событий:
selelemstart - происходит только при первом mousemove после нажатия.
selelem - происходит каждый раз после нажатия, когда водишь мышью.
В объекте события передаются элементы которые были задеты выделением - lastSelectedElements
selelemend - происходит при mouseup.
В объекте события передаются элементы которые были задеты выделением - lastSelectedElements и выделенные элементы selectedElements;

При бинде можно передать дополнительные параметры событию:
selectableClass - класс элемента который можно будет выделить, по умолчанию = 'selectable'.
visualSelectionClass - класс блока визуального выделения, по умолчанию = 'selection'.

//Создаем обработчики для #block, где выделяемые элементы будут иметь класс .element
$('#block').bind('selelem', {selectableClass: 'element'}, selelemHandler);
$('#block').bind('selelemend', selelemendHandler);

function selelemHandler(e){
console.log('---задеты выделением---');
console.log(e.lastSelectedElements);
}

function selelemendHandler(e){
console.log('---задеты выделением---');
console.log(e.lastSelectedElements);
console.log('---выделенные---');
console.log(e.selectedElements);
}

Управление выделенными элементами:
$(elem).controlSelectedElemets() - возвращает объект контроля над выделением данного элемента.

Объект контроля содержит 3 метода:
.add(array) - добавляет к массиву выделенных элементов
.clear() - очищает массив выделенных элементов
.get() - возвращает массив выделенных элементов
$('#button').click(function(){
//создаем объект контроля над выделением конкретного блока
var blockSelection = $('#block').controlSelectedElemets();
//выведет массив выделенных объектов
console.log(blockSelection.get());
});

рони 29.01.2014 17:45

Zuenf,
странно как-то считает....

Zuenf 29.01.2014 17:52

рони, странно, у меня все нормально:blink:
Буду искать баг.

Zuenf 29.01.2014 17:59

Нашел, исправил, обновил пример.


Часовой пояс GMT +3, время: 06:26.