Skip to content

Commit

Permalink
Merge pull request #4 from ThibaultJanBeyer/feature/multiselect
Browse files Browse the repository at this point in the history
Feature/multiselect
  • Loading branch information
Thibault Jan Beyer committed Sep 5, 2017
2 parents 6609470 + 7d1f334 commit d03c130
Show file tree
Hide file tree
Showing 8 changed files with 219 additions and 61 deletions.
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ var ds = dragSelect({
selector: document.getElementById('rectangle'), // draggable element. By default one will be created.
area: document.getElementById('area'), // area in which you can drag. If not provided it will be the whole document.
customStyles: false, // If set to true, no styles (except for position absolute) will be applied by default.
multiSelectKeys: ['ctrlKey', 'shiftKey', 'metaKey'], // special keys that allow multiselection.
onElementSelect: function(element) {}, // fired every time an element is selected. (element) = just selected node
onElementUnselect: function(element) {}, // fired every time an element is de-selected. (element) = just de-selected node.
callback: function(elements) {} // fired once the user releases the mouse. (elements) = selected nodes.
Expand All @@ -84,15 +85,19 @@ ds.getSelection(); // returns all currently selected nodes
ds.addSelectables(document.getElementsByClassName('selectable-node')); // adds elements that can be selected. Intelligent algorythm never adds elements twice.
ds.stop(); // will teardown/stop the whole functionality
ds.start(); // reset the functionality after a teardown
```
```

You can also use the "shift", "ctrl" or "command" key to make multiple independent selections.


## Properties:
| property | type | usage |
|--- |--- |--- |
|selectables |DOM elements (nodes) |OPTIONAL. The elements that can be selected |
|selector |single DOM element (node) |OPTIONAL. The square that will draw the selection. Autocreated by default |
|area |single DOM element (node) |OPTIONAL. The square in which you are able to select |
|customStyles |boolean |OPTIONAL. If true, no styles will be automatically applied (except position: absolute). Default: false |
|customStyles |boolean |OPTIONAL. If true, no styles will be automatically applied (except position: absolute). Default: `false` |
|multiSelectKeys |array |OPTIONAL. These key will allow the user add more elements to the selection instead of clearing the selection. The only possible values are keys that are provided via the event object. So far: <kbd>ctrlKey</kbd>, <kbd>shiftKey</kbd>, <kbd>metaKey</kbd> and <kbd>altKey</kbd>. Provide an empty array `[]` if you want to turn off the funcionality. Default: `['ctrlKey', 'shiftKey', 'metaKey']` |
|onElementSelect |function |OPTIONAL. Fired every time an element is selected. This callback gets a property which is the selected node |
|onElementUnselect |function |OPTIONAL. Fired every time an element is de-selected. This callback gets a property which is the de-selected node |
|callback |function |OPTIONAL. Callback function that gets fired when the selection is released. This callback gets a property which is an array that holds all selected nodes |
Expand All @@ -112,6 +117,7 @@ When the function is saved into a variable `var foo = dragSelect()` you have acc
| name | trigger |
|--- |--- |
|.ds-selected |On elements that are selected
|.ds-hover |On elements that are currently hovered
|.ds-selector |On the selector element

# Have Fun!
Expand Down
113 changes: 94 additions & 19 deletions dist/dragSelect.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/*
@TODO: rewrite it in a OOP manner so that people can extend/mixin the dragselect
@TODO: test in older browsers
__ _____ __ __
____/ /________ _____ _/ ___/___ / /__ _____/ /_
Expand All @@ -26,6 +27,7 @@ Key-Features
** @selector node the square that will draw the selection
** @area node area in which you can drag. If not provided it will be the whole document
** @customStyles boolean if set to true, no styles (except for position absolute) will be applied by default
** @multiSelectKeys array These key will allow the user add more elements to the selection instead of clearing the selection. The only possible values are keys that are provided via the event object. So far: <kbd>ctrlKey</kbd>, <kbd>shiftKey</kbd>, <kbd>metaKey</kbd> and <kbd>altKey</kbd>. Provide an empty array `[]` if you want to turn off the funcionality. Default: `['ctrlKey', 'shiftKey', 'metaKey']` |
** @onElementSelect function this is optional, it is fired every time an element is selected. This callback gets a property which is the just selected node
** @onElementUnselect function this is optional, it is fired every time an element is de-selected. This callback gets a property which is the just de-selected node
** @callback function a callback function that gets fired when the element is dropped. This callback gets a property which is an array that holds all selected nodes
Expand Down Expand Up @@ -76,21 +78,26 @@ var dragSelect = function(options) {

var selector,
selectables,
multiSelectKeys,
multiSelectKeyPressed,
selectCallback,
unselectCallback,
callback,
initialCursorPos,
area,
selected,
customStyles,
initialScroll;

function _setup() {
selectables = toArray(options.selectables) || [];
multiSelectKeys = options.multiSelectKeys || ['ctrlKey', 'shiftKey', 'metaKey'];
selectCallback = options.onElementSelect || function() {};
unselectCallback = options.onElementUnselect || function() {};
callback = options.callback || function() {};
area = options.area || document;

customStyles = options.customStyles || false;

selector = options.selector || _createSelection();
addClass(selector, 'ds-selector');

Expand All @@ -101,7 +108,7 @@ var dragSelect = function(options) {
var selector = document.createElement('div');

selector.style.position = 'absolute';
if(!options.customStyles) {
if(!customStyles) {
selector.style.background = 'rgba(0, 0, 255, 0.2)';
selector.style.border = '1px solid rgba(0, 0, 255, 0.5)';
selector.style.display = 'none';
Expand All @@ -124,12 +131,19 @@ var dragSelect = function(options) {
// Startups
//////////////////////////////////////////////////////////////////////////////////////

function _startUp(event) {
function _startUp( event ) {
selector.style.display = 'block';

// check if some multiselection modifier key is pressed
multiSelectKeyPressed = false;
for (var index = 0; index < multiSelectKeys.length; index++) {
var mKey = multiSelectKeys[index];
if(event[mKey]) { multiSelectKeyPressed = true; }
}

// move element on location
_getStartingPositions(event);
checkIfInsideSelection();
checkIfInsideSelection(true);

area.removeEventListener('mousedown', _startUp);
area.addEventListener('mousemove', _handleMove);
Expand Down Expand Up @@ -216,7 +230,6 @@ var dragSelect = function(options) {
*/
var selectorPos = {};

// console.log('yala', cursorPosNew.y, initialCursorPos.y, scrollAmount.y, initialScroll.y);
// right
if(cursorPosNew.x > initialCursorPos.x - scrollAmount.x) { // 1.
selectorPos.x = initialCursorPos.x + initialScroll.x; // 2.
Expand Down Expand Up @@ -244,30 +257,56 @@ var dragSelect = function(options) {
// Colision detection
//////////////////////////////////////////////////////////////////////////////////////

function checkIfInsideSelection() {
/* startup handles first clicks. Here is user is clicking directly onto
* some element at start, (contrary to later hovers) we can assume that he
* really wants to select/deselect that item. So we force it through. */
function checkIfInsideSelection( startup ) {
for(var i = 0, il = selectables.length; i < il; i++) {
var selectable = selectables[i];
var posInSelectedArray = selected.indexOf(selectable);

if( isElementTouching(selectable, selector) ) {
_handleSelection( selectable, startup );
} else {
_handleUnselection( selectable, startup );
}

if( posInSelectedArray < 0 ) {
selected.push(selectable);
addClass(selectable, 'selected');
selectCallback(selectable);
}
}
}

} else {
function _handleSelection( item, startup ) {
if( hasClass( item, 'ds-hover' ) && !startup ) { return false; }
var posInSelectedArray = selected.indexOf( item );

if( posInSelectedArray > -1 ) {
selected.splice(posInSelectedArray, 1);
removeClass(selectable, 'selected');
unselectCallback(selectable);
}
if( posInSelectedArray < 0 ) {
_select( item );
} else if( posInSelectedArray > -1 && multiSelectKeyPressed ) {
_unselect( item );
}

}
addClass( item, 'ds-hover' );
}

function _handleUnselection( item, startup ) {
if( !hasClass( item, 'ds-hover' ) && !startup ) { return false; }
var posInSelectedArray = selected.indexOf( item );

if( posInSelectedArray > -1 && !multiSelectKeyPressed ) {
_unselect( item );
}

removeClass( item, 'ds-hover' );
}

function _select( item ) {
selected.push(item);
addClass(item, 'ds-selected');
selectCallback(item);
}

function _unselect( item ) {
selected.splice(selected.indexOf(item), 1);
removeClass(item, 'ds-selected');
unselectCallback(item);
}

//- Is Element touching Selection? (and vice-versa)
Expand Down Expand Up @@ -442,6 +481,19 @@ var dragSelect = function(options) {
element.className = cn;
}

/**
* Checks if an element has a class
* sadly legacy phones/browsers don’t support .classlist so we use this workaround
* @param {*} element
* @param {*} classname
* @return {node} element
*/
function hasClass( element, classname ) {
var cn = element.className;
if( cn.indexOf( classname ) > -1 ) { return true; }
else { return false; }
}

/**
* Transforms an Object to an array
* this is mainly used to transform Nodelists
Expand Down Expand Up @@ -558,6 +610,24 @@ var dragSelect = function(options) {
}


// Polyfills
//////////////////////////////////////////////////////////////////////////////////////

// indexOf polyfill for <IE9 all credits to https://stackoverflow.com/a/9768663/3712591
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function (elt /*, from*/) {
var len = this.length >>> 0;
var from = Number(arguments[1]) || 0;
from = (from < 0) ? Math.ceil(from) : Math.floor(from);
if (from < 0) { from += len; }

for (; from < len; from++) {
if (from in this && this[from] === elt) { return from; }
}
return -1;
};
}

// Return
//////////////////////////////////////////////////////////////////////////////////////

Expand All @@ -570,6 +640,10 @@ var dragSelect = function(options) {
_handleMove: _handleMove,
getPosition: getPosition,
checkIfInsideSelection: checkIfInsideSelection,
_handleSelection: _handleSelection,
_handleUnselection: _handleUnselection,
_select: _select,
_unselect: _unselect,
isElementTouching: isElementTouching,
autoScroll: autoScroll,
isCursorNearEdge: isCursorNearEdge,
Expand All @@ -578,6 +652,7 @@ var dragSelect = function(options) {
getSelection: getSelection,
addSelectables: addSelectables,
removeSelectables: removeSelectables,
hasClass: hasClass,
addClass: addClass,
removeClass: removeClass,
toArray: toArray,
Expand Down
2 changes: 1 addition & 1 deletion dist/ds.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit d03c130

Please sign in to comment.