Created
January 20, 2011 08:06
-
-
Save sapegin/787562 to your computer and use it in GitHub Desktop.
jQuery Selections ɑ
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
@title: Selections | |
@version: 0.0.1 | |
@author: Artem Sapegin | |
@author: Andreas Lagerkvist | |
@date: 2011-01-20 | |
@license: http://creativecommons.org/licenses/by/3.0/ | |
@copyright: 2010 Artem Sapegin (sapegin.ru) | |
@copyright: 2008 Andreas Lagerkvist (andreaslagerkvist.com) | |
@does: Use this plug-in to allow your users to select certain elements by dragging a "select box". Works very similar to | |
how you can drag-n-select files and folders in most OS:es. | |
Based on jquery.dragToSelect by Andreas Lagerkvist (http://andreaslagerkvist.com/jquery/drag-to-select/ ) | |
@howto: $('#my-files').selections(selectables: 'li'); would make every li in the #my-files-element selectable by dragging. | |
The li:s will recieve a "selected"-class when they are within range of the select box when user drops. | |
Make sure a parent-element of the selectables has position: relative as well as overflow: auto or scroll. | |
@exampleHTML: | |
<ul> | |
<li><img src="http://exscale.se/__files/3d/lamp-and-mates/lamp-and-mates-01_small.jpg" alt="Lamp and Mates" /></li> | |
<li><img src="http://exscale.se/__files/3d/stugan-winter_small.jpg" alt="The Cottage - Winter time" /></li> | |
<li><img src="http://exscale.se/__files/3d/ps2_small.jpg" alt="PS2" /></li> | |
</ul> | |
@exampleJS: | |
$('#jquery-drag-to-select-example').selections({ | |
selectables: 'li', | |
onHide: function () { | |
alert($('#jquery-drag-to-select-example li.selected').length + ' selected'); | |
} | |
}); | |
@todo: | |
+ Убрать нахер проценты (в Проводнике этого нет). Выделять при любом пересечении | |
+ Запретить начинать растягивание при клике на элемент | |
- Выделять кликом по элементу | |
- Хранить список выделенных элементов | |
- Множественное выделение | |
+ Убрать active/disabled где не нужен | |
+ Кэшировать все возможные параметры | |
+ Сделать быструю проверку (подумать об этом) | |
* Не растягивать рамку за пределы родителя | |
- Клик по фону снимает выделение | |
*/ | |
(function($){ | |
$.fn.selections = function (conf) { | |
// Config defaults | |
var config = jQuery.extend({ | |
rectangleClass: 'jquery-drag-to-select', | |
activeClass: 'active', | |
disabledClass: 'disabled', | |
selectedClass: 'selected', | |
scrollThreshold: 10, | |
selectables: 'li', | |
autoScroll: false, | |
selectOnMove: false | |
}, conf || {}); | |
var items, itemWidth, itemHeight, itemsPerRow; | |
var itemsAffected, itemsDefaults, itemsCurrents; | |
var selectBoxLeft, selectBoxTop, selectBoxWidth, selectBoxHeight; | |
var maxScrollLeft, maxScrollTop; | |
var lastSelected; | |
var realParent = this; | |
var parent = realParent; | |
if (!parent.length) | |
return this; | |
do { | |
if (/auto|scroll|hidden/.test(parent.css('overflow'))) { | |
break; | |
} | |
parent = parent.parent(); | |
} while (parent[0].parentNode); | |
// Does user want to disable "selections" | |
if ('disable' == conf) { | |
parent.addClass(config.disabledClass); | |
return this; | |
} | |
else if ('enable' == conf) { | |
parent.removeClass(config.disabledClass); | |
return this; | |
} | |
var parentOffset = parent.offset(); | |
var parentDim = { // @todo обновлять при ресайзе | |
left: parentOffset.left, | |
top: parentOffset.top, | |
width: parent.width(), | |
height: parent.height() | |
}; | |
// Initial origin of select box | |
var selectBoxOrigin = { | |
left: 0, | |
top: 0 | |
}; | |
var selectBox = null; | |
/** | |
* Shows the select box | |
* @param e Event object | |
*/ | |
function showSelectBox(e) { | |
selectBoxOrigin.left = e.pageX - parentDim.left + parent[0].scrollLeft; | |
selectBoxOrigin.top = e.pageY - parentDim.top + parent[0].scrollTop; | |
if (!selectBox) { | |
selectBox = $('<div/>') | |
.addClass(config.rectangleClass) | |
.css('position', 'absolute'); | |
} | |
selectBox | |
.addClass(config.activeClass) | |
.css({ | |
left: selectBoxOrigin.left, | |
top: selectBoxOrigin.top, | |
width: 1, | |
height: 1 | |
}) | |
.appendTo(parent); | |
realParent.trigger('show.selections'); | |
} | |
/** | |
* Refreshes the select box dimensions and possibly position | |
* @param e Event object | |
*/ | |
function refreshSelectBox(e) { | |
var left = e.pageX - parentDim.left + parent[0].scrollLeft; | |
var top = e.pageY - parentDim.top + parent[0].scrollTop; | |
selectBoxLeft = left; | |
selectBoxTop = top; | |
selectBoxWidth = selectBoxOrigin.left - selectBoxLeft; | |
selectBoxHeight = selectBoxOrigin.top - selectBoxTop; | |
if (left > selectBoxOrigin.left) { | |
selectBoxLeft = selectBoxOrigin.left; | |
selectBoxWidth = left - selectBoxOrigin.left; | |
} | |
if (top > selectBoxOrigin.top) { | |
selectBoxTop = selectBoxOrigin.top; | |
selectBoxHeight = top - selectBoxOrigin.top; | |
} | |
selectBox.css({ | |
left: selectBoxLeft, | |
top: selectBoxTop, | |
width: selectBoxWidth, | |
height: selectBoxHeight | |
}); | |
realParent.trigger('refresh.selections'); | |
} | |
/** | |
* Hides the select box | |
*/ | |
function hideSelectBox() { | |
var event = $.Event('hide.selections'); | |
realParent.trigger(event); | |
if (!event.isDefaultPrevented()) { | |
selectBox.removeClass(config.activeClass); | |
} | |
} | |
/** | |
* Selects all elements in the select box's range | |
*/ | |
function selectElementsInRange() { | |
var fr = Math.ceil(selectBoxTop/itemHeight); | |
var fc = Math.ceil(selectBoxLeft/itemWidth); | |
var lr = Math.ceil((selectBoxTop+selectBoxHeight) / itemHeight); | |
var lc = Math.ceil((selectBoxLeft+selectBoxWidth) / itemWidth); | |
for (var idx in itemsAffected) { | |
itemsAffected[idx] = false; | |
} | |
for (var idx=((itemsPerRow*(fr-1)) + fc-1), last=((itemsPerRow*(lr-1)) + lc-1), col=fc; idx<=last; idx++) { | |
if (col++ > itemsPerRow) | |
col = 2; | |
if (col <= fc || col > lc+1) | |
continue; | |
if (itemsDefaults[idx]) { | |
itemsDefaults[idx] = itemsCurrents[idx] = items.eq(idx).hasClass(config.selectedClass); | |
} | |
if (!itemsCurrents[idx]) { | |
items.eq(idx).toggleClass(config.selectedClass); | |
itemsCurrents[idx] = !itemsCurrents[idx]; | |
} | |
itemsAffected[idx] = true; | |
} | |
for (var idx in itemsAffected) { | |
if (!itemsAffected[idx] && (itemsCurrents[idx] != itemsDefaults[idx])) { | |
items.eq(idx).toggleClass(config.selectedClass); | |
itemsCurrents[idx] = itemsDefaults[idx]; | |
} | |
} | |
} | |
/** | |
* Scrolls parent if needed | |
*/ | |
function scrollOnBounds(e) { | |
var threshold = config.scrollThreshold; | |
if ((e.pageY + threshold) > (parentDim.top + parentDim.height)) // Bottom | |
parent[0].scrollTop = Math.min(parent[0].scrollTop+threshold, maxScrollTop); | |
else if ((e.pageY - threshold) < parentDim.top) // Up | |
parent[0].scrollTop -= threshold; | |
else if ((e.pageX + threshold) > (parentDim.left + parentDim.width)) // Right | |
parent[0].scrollLeft = Math.min(parent[0].scrollLeft+threshold, maxScrollLeft); | |
else if ((e.pageX - threshold) < parentDim.left) // Left | |
parent[0].scrollLeft -= threshold; | |
} | |
function getItems() { | |
return realParent.find(config.selectables); | |
} | |
function getSelectedItems() { | |
return getItems().filter('.' + config.selectedClass); | |
} | |
function selectAll() { | |
getItems().addClass(config.selectedClass); | |
} | |
function deselect() { | |
getItems().removeClass(config.selectedClass); | |
} | |
// Do the right stuff then return this | |
if ($.fn.disableTextSelect) { | |
parent.disableTextSelect(); | |
} | |
parent.mousedown(function(e){ | |
if (parent.hasClass(config.disabledClass)) | |
return; | |
// Make sure user isn't clicking scrollbar (or disallow clicks far to the right actually) | |
if ((e.pageX + 20) > $(document.body).width()) | |
return; | |
// Disable drag rectangle from elements, only from space between elemens | |
if ($(e.target).parents(conf.selectables).length) | |
return; | |
if (!e.ctrlKey) | |
deselect(); | |
items = getItems(); | |
itemWidth = items.eq(0).outerWidth(true); | |
itemHeight = items.eq(0).outerHeight(true); | |
itemsPerRow = Math.floor(parentDim.width/itemWidth); | |
itemsAffected = {}; | |
itemsDefaults = {}; | |
itemsCurrents = {}; | |
maxScrollLeft = parent[0].scrollWidth-parent[0].clientWidth; | |
maxScrollTop = parent[0].scrollHeight-parent[0].clientHeight; | |
showSelectBox(e); | |
var boxAndDocument = $(document).add(selectBox); | |
boxAndDocument.bind('mousemove.selections', function(e){ | |
refreshSelectBox(e); | |
if (config.selectOnMove) | |
selectElementsInRange(); | |
if (config.autoScroll) | |
scrollOnBounds(e); | |
e.preventDefault(); | |
}); | |
boxAndDocument.bind('mouseup.selections', function(e){ | |
if (!config.selectOnMove) | |
selectElementsInRange(); | |
hideSelectBox(); | |
boxAndDocument.unbind('mousemove.selections'); | |
boxAndDocument.unbind('mouseup.selections'); | |
e.preventDefault(); | |
}); | |
e.preventDefault(); | |
}); | |
parent.delegate(config.selectables, 'click', function(e){ | |
if (e.shiftKey) { | |
var items = getItems(); | |
var begin = items.index(lastSelected); | |
var end = items.index($(this)); | |
items.slice(Math.min(begin, end), Math.max(begin, end)+1).addClass(config.selectedClass); | |
return; | |
} | |
lastSelected = $(this); | |
var operation; | |
if (e.ctrlKey) { | |
operation = 'toggleClass'; | |
} | |
else { | |
deselect(); | |
operation = 'addClass'; | |
} | |
lastSelected[operation](config.selectedClass); | |
}); | |
// Be nice | |
return this; | |
}; | |
})(jQuery); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment