Skip to content

Instantly share code, notes, and snippets.

@sapegin
Created January 20, 2011 08:06
Show Gist options
  • Save sapegin/787562 to your computer and use it in GitHub Desktop.
Save sapegin/787562 to your computer and use it in GitHub Desktop.
jQuery Selections ɑ
/**
@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