├── 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 | 
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]