1 |
/** |
2 |
* jQuery PickList Widget |
3 |
* |
4 |
* Copyright (c) 2012-2013 Jonathon Freeman <jonathon@awnry.com> |
5 |
* Distributed under the terms of the MIT License. |
6 |
* |
7 |
* http://code.google.com/p/jquery-ui-picklist/ |
8 |
*/ |
9 |
(function($) |
10 |
{ |
11 |
$.widget("awnry.pickList", |
12 |
{ |
13 |
widgetEventPrefix: "pickList_", |
14 |
|
15 |
options: |
16 |
{ |
17 |
// Container classes |
18 |
mainClass: "pickList", |
19 |
listContainerClass: "pickList_listContainer", |
20 |
sourceListContainerClass: "pickList_sourceListContainer", |
21 |
controlsContainerClass: "pickList_controlsContainer", |
22 |
targetListContainerClass: "pickList_targetListContainer", |
23 |
listClass: "pickList_list", |
24 |
sourceListClass: "pickList_sourceList", |
25 |
targetListClass: "pickList_targetList", |
26 |
clearClass: "pickList_clear", |
27 |
|
28 |
// List item classes |
29 |
listItemClass: "pickList_listItem", |
30 |
richListItemClass: "pickList_richListItem", |
31 |
selectedListItemClass: "pickList_selectedListItem", |
32 |
|
33 |
// Control classes |
34 |
addAllClass: "pickList_addAll", |
35 |
addClass: "pickList_add", |
36 |
removeAllClass: "pickList_removeAll", |
37 |
removeClass: "pickList_remove", |
38 |
|
39 |
// Control labels |
40 |
addAllLabel: ">>", |
41 |
addLabel: ">", |
42 |
removeAllLabel: "<<", |
43 |
removeLabel: "<", |
44 |
|
45 |
// List labels |
46 |
listLabelClass: "pickList_listLabel", |
47 |
sourceListLabel: "Available", |
48 |
sourceListLabelClass: "pickList_sourceListLabel", |
49 |
targetListLabel: "Selected", |
50 |
targetListLabelClass: "pickList_targetListLabel", |
51 |
|
52 |
// Sorting |
53 |
sortItems: true, |
54 |
sortAttribute: "label", |
55 |
|
56 |
// Name of custom value attribute for list items |
57 |
listItemValueAttribute: "data-value", |
58 |
|
59 |
// Additional list items |
60 |
items: [] |
61 |
}, |
62 |
|
63 |
_create: function() |
64 |
{ |
65 |
var self = this; |
66 |
|
67 |
self._buildPickList(); |
68 |
self._refresh(); |
69 |
}, |
70 |
|
71 |
_buildPickList: function() |
72 |
{ |
73 |
var self = this; |
74 |
|
75 |
self._trigger("beforeBuild"); |
76 |
|
77 |
self.pickList = $("<div/>") |
78 |
.hide() |
79 |
.addClass(self.options.mainClass) |
80 |
.insertAfter(self.element) |
81 |
.append(self._buildSourceList()) |
82 |
.append(self._buildControls()) |
83 |
.append(self._buildTargetList()) |
84 |
.append( $("<div/>").addClass(self.options.clearClass) ); |
85 |
|
86 |
self._populateLists(); |
87 |
|
88 |
self.element.hide(); |
89 |
self.pickList.show(); |
90 |
|
91 |
self._trigger("afterBuild"); |
92 |
}, |
93 |
|
94 |
_buildSourceList: function() |
95 |
{ |
96 |
var self = this; |
97 |
|
98 |
var container = $("<div/>") |
99 |
.addClass(self.options.listContainerClass) |
100 |
.addClass(self.options.sourceListContainerClass) |
101 |
.css({ |
102 |
"-moz-user-select": "none", |
103 |
"-webkit-user-select": "none", |
104 |
"user-select": "none", |
105 |
"-ms-user-select": "none" |
106 |
}) |
107 |
.each(function() |
108 |
{ |
109 |
this.onselectstart = function() { return false; }; |
110 |
}); |
111 |
|
112 |
var label = $("<div/>") |
113 |
.text(self.options.sourceListLabel) |
114 |
.addClass(self.options.listLabelClass) |
115 |
.addClass(self.options.sourceListLabelClass); |
116 |
|
117 |
self.sourceList = $("<ul/>") |
118 |
.addClass(self.options.listClass) |
119 |
.addClass(self.options.sourceListClass) |
120 |
.delegate("li", "click", { pickList: self }, self._changeHandler); |
121 |
|
122 |
container |
123 |
.append(label) |
124 |
.append(self.sourceList); |
125 |
|
126 |
self.sourceList.delegate(".pickList_listItem", "dblclick", {pickList: self}, function(e) |
127 |
{ |
128 |
var self = e.data.pickList; |
129 |
self._addItems( self.sourceList.children(".ui-selected") ); |
130 |
}); |
131 |
|
132 |
return container; |
133 |
}, |
134 |
|
135 |
_buildTargetList: function() |
136 |
{ |
137 |
var self = this; |
138 |
|
139 |
var container = $("<div/>") |
140 |
.addClass(self.options.listContainerClass) |
141 |
.addClass(self.options.targetListContainerClass) |
142 |
.css({ |
143 |
"-moz-user-select": "none", |
144 |
"-webkit-user-select": "none", |
145 |
"user-select": "none", |
146 |
"-ms-user-select": "none" |
147 |
}) |
148 |
.each(function() |
149 |
{ |
150 |
this.onselectstart = function() { return false; }; |
151 |
}); |
152 |
|
153 |
var label = $("<div/>") |
154 |
.text(self.options.targetListLabel) |
155 |
.addClass(self.options.listLabelClass) |
156 |
.addClass(self.options.targetListLabelClass); |
157 |
|
158 |
self.targetList = $("<ul/>") |
159 |
.addClass(self.options.listClass) |
160 |
.addClass(self.options.targetListClass) |
161 |
.delegate("li", "click", { pickList: self }, self._changeHandler); |
162 |
|
163 |
container |
164 |
.append(label) |
165 |
.append(self.targetList); |
166 |
|
167 |
self.targetList.delegate(".pickList_listItem", "dblclick", {pickList: self}, function(e) |
168 |
{ |
169 |
var self = e.data.pickList; |
170 |
self._removeItems( self.targetList.children(".ui-selected") ); |
171 |
}); |
172 |
|
173 |
return container; |
174 |
}, |
175 |
|
176 |
_buildControls: function() |
177 |
{ |
178 |
var self = this; |
179 |
|
180 |
self.controls = $("<div/>").addClass(self.options.controlsContainerClass); |
181 |
|
182 |
self.addAllButton = $("<button type='button'/>").click({pickList: self}, self._addAllHandler).html(self.options.addAllLabel).addClass(self.options.addAllClass); |
183 |
self.addButton = $("<button type='button'/>").click({pickList: self}, self._addHandler).html(self.options.addLabel).addClass(self.options.addClass); |
184 |
self.removeButton = $("<button type='button'/>").click({pickList: self}, self._removeHandler).html(self.options.removeLabel).addClass(self.options.removeClass); |
185 |
self.removeAllButton = $("<button type='button'/>").click({pickList: self}, self._removeAllHandler).html(self.options.removeAllLabel).addClass(self.options.removeAllClass); |
186 |
|
187 |
self.controls |
188 |
.append(self.addAllButton) |
189 |
.append(self.addButton) |
190 |
.append(self.removeButton) |
191 |
.append(self.removeAllButton); |
192 |
|
193 |
return self.controls; |
194 |
}, |
195 |
|
196 |
_populateLists: function() |
197 |
{ |
198 |
var self = this; |
199 |
|
200 |
self._trigger("beforePopulate"); |
201 |
|
202 |
var sourceListItems = []; |
203 |
var targetListItems = []; |
204 |
var selectItems = self.element.children(); |
205 |
|
206 |
selectItems.not(":selected").each(function() |
207 |
{ |
208 |
sourceListItems.push( self._createDoppelganger(this) ); |
209 |
}); |
210 |
|
211 |
selectItems.filter(":selected").each(function() |
212 |
{ |
213 |
targetListItems.push( self._createDoppelganger(this) ); |
214 |
}); |
215 |
|
216 |
self.sourceList.append(sourceListItems.join("\n")); |
217 |
self.targetList.append(targetListItems.join("\n")); |
218 |
self.insertItems(self.options.items); |
219 |
|
220 |
self._trigger("afterPopulate"); |
221 |
}, |
222 |
|
223 |
_addItems: function(items) |
224 |
{ |
225 |
var self = this; |
226 |
|
227 |
self._trigger("beforeAdd"); |
228 |
|
229 |
self.targetList.append( self._removeSelections(items) ); |
230 |
|
231 |
var itemIds = []; |
232 |
items.each(function() |
233 |
{ |
234 |
itemIds.push( self._getItemValue(this) ); |
235 |
}); |
236 |
|
237 |
self.element.children().filter(function() |
238 |
{ |
239 |
return $.inArray(this.value, itemIds) != -1; |
240 |
}).attr("selected", "selected"); |
241 |
|
242 |
self._refresh(); |
243 |
|
244 |
self._trigger("afterAdd", null, { items: items }); |
245 |
self._trigger("onChange", null, { type: "add", items: items }); |
246 |
}, |
247 |
|
248 |
_removeItems: function(items) |
249 |
{ |
250 |
var self = this; |
251 |
|
252 |
self._trigger("beforeRemove"); |
253 |
|
254 |
self.sourceList.append( self._removeSelections(items) ); |
255 |
|
256 |
var itemIds = []; |
257 |
items.each(function() |
258 |
{ |
259 |
itemIds.push( self._getItemValue(this) ); |
260 |
}); |
261 |
|
262 |
self.element.children().filter(function() |
263 |
{ |
264 |
return $.inArray(this.value, itemIds) != -1; |
265 |
}).removeAttr("selected"); |
266 |
|
267 |
self._refresh(); |
268 |
|
269 |
self._trigger("afterRemove", null, { items: items }); |
270 |
self._trigger("onChange", null, { type: "remove", items: items }); |
271 |
}, |
272 |
|
273 |
_addAllHandler: function(e) |
274 |
{ |
275 |
var self = e.data.pickList; |
276 |
|
277 |
self._trigger("beforeAddAll"); |
278 |
|
279 |
var items = self.sourceList.children(); |
280 |
self.targetList.append( self._removeSelections(items) ); |
281 |
|
282 |
self.element.children().not(":selected").attr("selected", "selected"); |
283 |
|
284 |
self._refresh(); |
285 |
|
286 |
self._trigger("afterAddAll", null, { items: items }); |
287 |
self._trigger("onChange", null, { type: "addAll", items: items }); |
288 |
}, |
289 |
|
290 |
_addHandler: function(e) |
291 |
{ |
292 |
var self = e.data.pickList; |
293 |
self._addItems(self.sourceList.children(".ui-selected")); |
294 |
}, |
295 |
|
296 |
_removeHandler: function(e) |
297 |
{ |
298 |
var self = e.data.pickList; |
299 |
self._removeItems(self.targetList.children(".ui-selected")); |
300 |
}, |
301 |
|
302 |
_removeAllHandler: function(e) |
303 |
{ |
304 |
var self = e.data.pickList; |
305 |
|
306 |
self._trigger("beforeRemoveAll"); |
307 |
|
308 |
var items = self.targetList.children(); |
309 |
self.sourceList.append( self._removeSelections(items) ); |
310 |
|
311 |
self.element.children().filter(":selected").removeAttr("selected"); |
312 |
|
313 |
self._refresh(); |
314 |
|
315 |
self._trigger("afterRemoveAll", null, { items: items }); |
316 |
self._trigger("onChange", null, { type: "removeAll", items: items }); |
317 |
}, |
318 |
|
319 |
_refresh: function() |
320 |
{ |
321 |
var self = this; |
322 |
|
323 |
self._trigger("beforeRefresh"); |
324 |
|
325 |
self._refreshControls(); |
326 |
|
327 |
// Sort the selection lists. |
328 |
if(self.options.sortItems) |
329 |
{ |
330 |
self._sortItems(self.sourceList, self.options); |
331 |
self._sortItems(self.targetList, self.options); |
332 |
} |
333 |
|
334 |
self._trigger("afterRefresh"); |
335 |
}, |
336 |
|
337 |
_refreshControls: function() |
338 |
{ |
339 |
var self = this; |
340 |
|
341 |
self._trigger("beforeRefreshControls"); |
342 |
|
343 |
// Enable/disable the Add All button state. |
344 |
if(self.sourceList.children().length) |
345 |
{ |
346 |
self.addAllButton.removeAttr("disabled"); |
347 |
} |
348 |
else |
349 |
{ |
350 |
self.addAllButton.attr("disabled", "disabled"); |
351 |
} |
352 |
|
353 |
// Enable/disable the Remove All button state. |
354 |
if(self.targetList.children().length) |
355 |
{ |
356 |
self.removeAllButton.removeAttr("disabled"); |
357 |
} |
358 |
else |
359 |
{ |
360 |
self.removeAllButton.attr("disabled", "disabled"); |
361 |
} |
362 |
|
363 |
// Enable/disable the Add button state. |
364 |
if(self.sourceList.children(".ui-selected").length) |
365 |
{ |
366 |
self.addButton.removeAttr("disabled"); |
367 |
} |
368 |
else |
369 |
{ |
370 |
self.addButton.attr("disabled", "disabled"); |
371 |
} |
372 |
|
373 |
// Enable/disable the Remove button state. |
374 |
if(self.targetList.children(".ui-selected").length) |
375 |
{ |
376 |
self.removeButton.removeAttr("disabled"); |
377 |
} |
378 |
else |
379 |
{ |
380 |
self.removeButton.attr("disabled", "disabled"); |
381 |
} |
382 |
|
383 |
self._trigger("afterRefreshControls"); |
384 |
}, |
385 |
|
386 |
_sortItems: function(list, options) |
387 |
{ |
388 |
var items = new Array(); |
389 |
|
390 |
list.children().each(function() |
391 |
{ |
392 |
items.push( $(this) ); |
393 |
}); |
394 |
|
395 |
items.sort(function(a, b) |
396 |
{ |
397 |
if(a.attr(options.sortAttribute) > b.attr(options.sortAttribute)) |
398 |
{ |
399 |
return 1; |
400 |
} |
401 |
else if(a.attr(options.sortAttribute) == b.attr(options.sortAttribute)) |
402 |
{ |
403 |
return 0; |
404 |
} |
405 |
else |
406 |
{ |
407 |
return -1; |
408 |
} |
409 |
}); |
410 |
|
411 |
list.empty(); |
412 |
|
413 |
for(var i = 0; i < items.length; i++) |
414 |
{ |
415 |
list.append(items[i]); |
416 |
} |
417 |
}, |
418 |
|
419 |
_changeHandler: function(e) |
420 |
{ |
421 |
var self = e.data.pickList; |
422 |
|
423 |
if(e.ctrlKey) |
424 |
{ |
425 |
if(self._isSelected( $(this) )) |
426 |
{ |
427 |
self._removeSelection( $(this) ); |
428 |
} |
429 |
else |
430 |
{ |
431 |
self.lastSelectedItem = $(this); |
432 |
self._addSelection( $(this) ); |
433 |
} |
434 |
} |
435 |
else if(e.shiftKey) |
436 |
{ |
437 |
var current = self._getItemValue(this); |
438 |
var last = self._getItemValue(self.lastSelectedItem); |
439 |
|
440 |
if($(this).index() < $(self.lastSelectedItem).index()) |
441 |
{ |
442 |
var temp = current; |
443 |
current = last; |
444 |
last = temp; |
445 |
} |
446 |
|
447 |
var pastStart = false; |
448 |
var beforeEnd = true; |
449 |
|
450 |
self._clearSelections( $(this).parent() ); |
451 |
|
452 |
$(this).parent().children().each(function() |
453 |
{ |
454 |
if(self._getItemValue(this) == last) |
455 |
{ |
456 |
pastStart = true; |
457 |
} |
458 |
|
459 |
if(pastStart && beforeEnd) |
460 |
{ |
461 |
self._addSelection( $(this) ); |
462 |
} |
463 |
|
464 |
if(self._getItemValue(this) == current) |
465 |
{ |
466 |
beforeEnd = false; |
467 |
} |
468 |
|
469 |
}); |
470 |
} |
471 |
else |
472 |
{ |
473 |
self.lastSelectedItem = $(this); |
474 |
self._clearSelections( $(this).parent() ); |
475 |
self._addSelection( $(this) ); |
476 |
} |
477 |
|
478 |
self._refreshControls(); |
479 |
}, |
480 |
|
481 |
_isSelected: function(listItem) |
482 |
{ |
483 |
return listItem.hasClass("ui-selected"); |
484 |
}, |
485 |
|
486 |
_addSelection: function(listItem) |
487 |
{ |
488 |
var self = this; |
489 |
|
490 |
return listItem |
491 |
.addClass("ui-selected") |
492 |
.addClass("ui-state-highlight") |
493 |
.addClass(self.options.selectedListItemClass); |
494 |
}, |
495 |
|
496 |
_removeSelection: function(listItem) |
497 |
{ |
498 |
var self = this; |
499 |
|
500 |
return listItem |
501 |
.removeClass("ui-selected") |
502 |
.removeClass("ui-state-highlight") |
503 |
.removeClass(self.options.selectedListItemClass); |
504 |
}, |
505 |
|
506 |
_removeSelections: function(listItems) |
507 |
{ |
508 |
var self = this; |
509 |
|
510 |
listItems.each(function() |
511 |
{ |
512 |
$(this) |
513 |
.removeClass("ui-selected") |
514 |
.removeClass("ui-state-highlight") |
515 |
.removeClass(self.options.selectedListItemClass); |
516 |
}); |
517 |
|
518 |
return listItems; |
519 |
}, |
520 |
|
521 |
_clearSelections: function(list) |
522 |
{ |
523 |
var self = this; |
524 |
|
525 |
list.children().each(function() |
526 |
{ |
527 |
self._removeSelection( $(this) ); |
528 |
}); |
529 |
}, |
530 |
|
531 |
_setOption: function(key, value) |
532 |
{ |
533 |
switch(key) |
534 |
{ |
535 |
case "clear": |
536 |
{ |
537 |
break; |
538 |
} |
539 |
} |
540 |
|
541 |
$.Widget.prototype._setOption.apply(this, arguments); |
542 |
}, |
543 |
|
544 |
destroy: function() |
545 |
{ |
546 |
var self = this; |
547 |
|
548 |
self._trigger("onDestroy"); |
549 |
|
550 |
self.pickList.remove(); |
551 |
self.element.show(); |
552 |
|
553 |
$.Widget.prototype.destroy.call(self); |
554 |
}, |
555 |
|
556 |
insert: function(item) |
557 |
{ |
558 |
var self = this; |
559 |
|
560 |
var list = item.selected ? self.targetList : self.sourceList; |
561 |
var selectItem = self._createSelectItem(item); |
562 |
var listItem = self._createListItem(item); |
563 |
|
564 |
self.element.append(selectItem); |
565 |
list.append(listItem); |
566 |
|
567 |
self._trigger("onChange"); |
568 |
|
569 |
self._refresh(); |
570 |
}, |
571 |
|
572 |
insertItems: function(items) |
573 |
{ |
574 |
var self = this; |
575 |
|
576 |
var selectItems = []; |
577 |
var sourceItems = []; |
578 |
var targetItems = []; |
579 |
|
580 |
$(items).each(function() |
581 |
{ |
582 |
var selectItem = self._createSelectItem(this); |
583 |
var listItem = self._createListItem(this); |
584 |
|
585 |
selectItems.push(selectItem); |
586 |
|
587 |
if(this.selected) |
588 |
{ |
589 |
targetItems.push(listItem); |
590 |
} |
591 |
else |
592 |
{ |
593 |
sourceItems.push(listItem); |
594 |
} |
595 |
}); |
596 |
|
597 |
self.element.append(selectItems.join("\n")); |
598 |
self.sourceList.append(sourceItems.join("\n")); |
599 |
self.targetList.append(targetItems.join("\n")); |
600 |
|
601 |
self._trigger("onChange"); |
602 |
|
603 |
self._refresh(); |
604 |
}, |
605 |
|
606 |
_createSelectItem: function(item) |
607 |
{ |
608 |
var selected = item.selected ? " selected='selected'" : ""; |
609 |
return "<option value='" + item.value + "'" + selected + ">" + item.label + "</option>"; |
610 |
}, |
611 |
|
612 |
_createListItem: function(item) |
613 |
{ |
614 |
var self = this; |
615 |
|
616 |
if(item.element != undefined) |
617 |
{ |
618 |
var richItemHtml = item.element.clone().wrap("<div>").parent().html(); |
619 |
item.element.hide(); |
620 |
return "<li " + self.options.listItemValueAttribute + "='" + item.value + "' label='" + item.label + "' class='" + self.options.listItemClass + " " + self.options.richListItemClass + "'>" + richItemHtml + "</li>"; |
621 |
} |
622 |
|
623 |
return "<li " + self.options.listItemValueAttribute + "='" + item.value + "' label='" + item.label + "' class='" + self.options.listItemClass + "'>" + item.label + "</li>"; |
624 |
}, |
625 |
|
626 |
_createDoppelganger: function(item) |
627 |
{ |
628 |
var self = this; |
629 |
return "<li " + self.options.listItemValueAttribute + "='" + $(item).val() + "' label='" + $(item).text() + "' class='" + self.options.listItemClass + "'>" + $(item).text() + "</li>"; |
630 |
}, |
631 |
|
632 |
_getItemValue: function(item) |
633 |
{ |
634 |
var self = this; |
635 |
return $(item).attr(self.options.listItemValueAttribute); |
636 |
} |
637 |
}); |
638 |
}(jQuery)); |