├── LICENSE ├── README.md └── src ├── jquery.nu-selectable.js └── jquery.nu-selectable.min.js /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Alex Suyun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nuSelectable 2 | Lightweight alternative to jQuery Selectable. Inspired by Google Drive file select. 3 | 4 | # Code example 5 | ``` javascript 6 | $(function() { 7 | $('#item-container').nuSelectable({ 8 | items: '.item', 9 | selectionClass: 'nu-selection-box', 10 | selectedClass: 'nu-selected', 11 | autoRefresh: true 12 | }); 13 | }); 14 | 15 | ``` 16 | 17 | # Screenshots 18 | 19 | ![Screenshot](https://cloud.githubusercontent.com/assets/13611918/10266308/27381d2e-6a27-11e5-9216-92344b558cb3.png) 20 | 21 | # License 22 | The MIT License (MIT) 23 | 24 | Copyright (c) 2015 Alex Suyun 25 | 26 | Permission is hereby granted, free of charge, to any person obtaining a copy 27 | of this software and associated documentation files (the "Software"), to deal 28 | in the Software without restriction, including without limitation the rights 29 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 30 | copies of the Software, and to permit persons to whom the Software is 31 | furnished to do so, subject to the following conditions: 32 | 33 | The above copyright notice and this permission notice shall be included in all 34 | copies or substantial portions of the Software. 35 | 36 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 37 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 38 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 39 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 40 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 41 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 42 | SOFTWARE. 43 | -------------------------------------------------------------------------------- /src/jquery.nu-selectable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * nuSelectable - jQuery Plugin 3 | * Copyright (c) 2015, Alex Suyun 4 | * Copyrights licensed under The MIT License (MIT) 5 | */ 6 | ; 7 | (function($, window, document, undefined) { 8 | 9 | 'use strict'; 10 | 11 | var plugin = 'nuSelectable'; 12 | 13 | var defaults = { 14 | onSelect: function() {}, 15 | onUnSelect: function() {}, 16 | onClear: function() {} 17 | }; 18 | 19 | var nuSelectable = function(container, options) { 20 | this.container = $(container); 21 | this.options = $.extend({}, defaults, options); 22 | this.selection = $('
') 23 | .addClass(this.options.selectionClass); 24 | this.items = $(this.options.items); 25 | this.init(); 26 | }; 27 | 28 | nuSelectable.prototype.init = function() { 29 | if (!this.options.autoRefresh) { 30 | this.itemData = this._cacheItemData(); 31 | } 32 | this.selecting = false; 33 | this._normalizeContainer(); 34 | this._bindEvents(); 35 | return true; 36 | }; 37 | 38 | nuSelectable.prototype._normalizeContainer = function() { 39 | this.container.css({ 40 | '-webkit-touch-callout': 'none', 41 | '-webkit-user-select': 'none', 42 | '-khtml-user-select': 'none', 43 | '-moz-user-select': 'none', 44 | '-ms-user-select': 'none', 45 | 'user-select': 'none' 46 | }); 47 | }; 48 | 49 | nuSelectable.prototype._cacheItemData = function() { 50 | var itemData = [], 51 | itemsLength = this.items.length; 52 | 53 | for (var i = 0, item; item = $(this.items[i]), i < 54 | itemsLength; i++) { 55 | itemData.push({ 56 | element: item, 57 | selected: item.hasClass(this.options.selectedClass), 58 | selecting: false, 59 | position: item[0].getBoundingClientRect() 60 | }); 61 | } 62 | return itemData; 63 | }; 64 | 65 | nuSelectable.prototype._collisionDetector = function() { 66 | var selector = this.selection[0].getBoundingClientRect(), 67 | dataLength = this.itemData.length; 68 | // Using native for loop vs $.each for performance (no overhead) 69 | for (var i = dataLength - 1, item; item = this.itemData[i], i >= 70 | 0; i--) { 71 | var collided = !(selector.right < item.position.left || 72 | selector.left > item.position.right || 73 | selector.bottom < item.position.top || 74 | selector.top > item.position.bottom); 75 | 76 | if (collided) { 77 | if (item.selected) { 78 | item.element.removeClass(this.options.selectedClass); 79 | item.selected = false; 80 | } 81 | if (!item.selected) { 82 | item.element.addClass(this.options.selectedClass); 83 | item.selected = true; 84 | this.options.onSelect(item.element); 85 | } 86 | } 87 | else { 88 | if (this.selecting) { 89 | item.element.removeClass(this.options.selectedClass); 90 | this.options.onUnSelect(item.element); 91 | } 92 | } 93 | 94 | } 95 | }; 96 | 97 | nuSelectable.prototype._createSelection = function(x, y) { 98 | this.selection.css({ 99 | 'position': 'absolute', 100 | 'top': y + 'px', 101 | 'left': x + 'px', 102 | 'width': '0', 103 | 'height': '0', 104 | 'z-index': '999', 105 | 'overflow': 'hidden' 106 | }) 107 | .appendTo(this.container); 108 | }; 109 | 110 | nuSelectable.prototype._drawSelection = function(width, height, x, 111 | y) { 112 | this.selection.css({ 113 | 'width': width, 114 | 'height': height, 115 | 'top': y, 116 | 'left': x 117 | }); 118 | }; 119 | 120 | nuSelectable.prototype.clear = function() { 121 | this.items.removeClass(this.options.selectedClass); 122 | this.options.onClear(); 123 | }; 124 | 125 | nuSelectable.prototype._mouseDown = function(event) { 126 | event.preventDefault(); 127 | event.stopPropagation(); 128 | if (this.options.disable) { 129 | return false; 130 | } 131 | if (event.which !== 1) { 132 | return false; 133 | } 134 | if (this.options.autoRefresh) { 135 | this.itemData = this._cacheItemData(); 136 | } 137 | if (event.metaKey || event.ctrlKey) { 138 | this.selecting = false; 139 | } 140 | else { 141 | this.selecting = true; 142 | } 143 | this.pos = [event.pageX, event.pageY]; 144 | this._createSelection(event.pageX, event.pageY); 145 | 146 | }; 147 | 148 | nuSelectable.prototype._mouseMove = function(event) { 149 | event.preventDefault(); 150 | event.stopPropagation(); 151 | // Save some bytes 152 | var pos = this.pos; 153 | if (!pos) { 154 | return false; 155 | } 156 | var newpos = [event.pageX, event.pageY], 157 | width = Math.abs(newpos[0] - pos[0]), 158 | height = Math.abs(newpos[1] - pos[1]), 159 | top, left; 160 | 161 | top = (newpos[0] < pos[0]) ? (pos[0] - width) : pos[0]; 162 | left = (newpos[1] < pos[1]) ? (pos[1] - height) : pos[1]; 163 | this._drawSelection(width, height, top, left); 164 | this._collisionDetector(); 165 | 166 | }; 167 | 168 | nuSelectable.prototype._mouseUp = function(event) { 169 | event.preventDefault(); 170 | event.stopPropagation(); 171 | if (!this.pos) { 172 | return false; 173 | } 174 | this.selecting = false; 175 | this.selection.remove(); 176 | if (event.pageX === this.pos[0] && event.pageY === this.pos[1]) { 177 | this.clear(); 178 | } 179 | }; 180 | 181 | nuSelectable.prototype._bindEvents = function() { 182 | this.container.on('mousedown', $.proxy(this._mouseDown, this)); 183 | this.container.on('mousemove', $.proxy(this._mouseMove, this)); 184 | // Binding to document is 'safer' than the container for mouse up 185 | $(document) 186 | .on('mouseup', $.proxy(this._mouseUp, this)); 187 | }; 188 | 189 | $.fn[plugin] = function(options) { 190 | var args = Array.prototype.slice.call(arguments, 1); 191 | 192 | return this.each(function() { 193 | var item = $(this), 194 | instance = item.data(plugin); 195 | if (!instance) { 196 | item.data(plugin, new nuSelectable(this, options)); 197 | } 198 | else { 199 | 200 | if (typeof options === 'string' && options[0] !== '_' && 201 | options !== 'init') { 202 | instance[options].apply(instance, args); 203 | } 204 | } 205 | 206 | }); 207 | }; 208 | 209 | })(jQuery, window, document); 210 | -------------------------------------------------------------------------------- /src/jquery.nu-selectable.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * nuSelectable - jQuery Plugin 3 | * Copyright (c) 2015, Alex Suyun 4 | * Copyrights licensed under The MIT License (MIT) 5 | */ 6 | !function(a,b,c,d){"use strict";var e="nuSelectable",g=function(b,c){this.container=a(b),this.options=a.extend({},c,c),this.selection=a("
").addClass(this.options.selectionClass),this.items=a(this.options.items),this.init()};g.prototype.init=function(){return this.options.autoRefresh||(this.itemData=this._cacheItemData()),this.selecting=!1,this._normalizeContainer(),this._bindEvents(),!0},g.prototype._normalizeContainer=function(){this.container.css({"-webkit-touch-callout":"none","-webkit-user-select":"none","-khtml-user-select":"none","-moz-user-select":"none","-ms-user-select":"none","user-select":"none"})},g.prototype._cacheItemData=function(){for(var e,b=[],c=this.items.length,d=0;e=a(this.items[d]),c>d;d++)b.push({element:e,selected:e.hasClass(this.options.selectedClass),selecting:!1,position:e[0].getBoundingClientRect()});return b},g.prototype._collisionDetector=function(){for(var d,a=this.selection[0].getBoundingClientRect(),b=this.itemData.length,c=b-1;d=this.itemData[c],c>=0;c--){var e=!(a.rightd.position.right||a.bottomd.position.bottom);e?(d.selected&&(d.element.removeClass(this.options.selectedClass),d.selected=!1),d.selected||(d.element.addClass(this.options.selectedClass),d.selected=!0,this.options.onSelect(d.element))):this.selecting&&(d.element.removeClass(this.options.selectedClass),this.options.onUnSelect(d.element))}},g.prototype._createSelection=function(a,b){this.selection.css({position:"absolute",top:b+"px",left:a+"px",width:"0",height:"0","z-index":"999",overflow:"hidden"}).appendTo(this.container)},g.prototype._drawSelection=function(a,b,c,d){this.selection.css({width:a,height:b,top:d,left:c})},g.prototype.clear=function(){this.items.removeClass(this.options.selectedClass),this.options.onClear()},g.prototype._mouseDown=function(a){return a.preventDefault(),a.stopPropagation(),this.options.disable?!1:1!==a.which?!1:(this.options.autoRefresh&&(this.itemData=this._cacheItemData()),a.metaKey||a.ctrlKey?this.selecting=!1:this.selecting=!0,this.pos=[a.pageX,a.pageY],void this._createSelection(a.pageX,a.pageY))},g.prototype._mouseMove=function(a){a.preventDefault(),a.stopPropagation();var b=this.pos;if(!b)return!1;var f,g,c=[a.pageX,a.pageY],d=Math.abs(c[0]-b[0]),e=Math.abs(c[1]-b[1]);f=c[0]