some bug fixes (#4818)
[openemr.git] / interface / super / rules / www / js / cdr-multiselect / ui.multiselect.js
blob5159834aedb93e4611d0ce51aefcf73cfec33fac
1 /*
2  * jQuery UI Multiselect
3  *
4  * Authors:
5  *  Michael Aufreiter (quasipartikel.at)
6  *  Yanick Rochon (yanick.rochon[at]gmail[dot]com)
7  *
8  * Dual licensed under the MIT (MIT-LICENSE.txt)
9  * and GPL (GPL-LICENSE.txt) licenses.
10  *
11  * http://www.quasipartikel.at/multiselect/
12  *
13  *
14  * Depends:
15  *      ui.core.js
16  *      ui.sortable.js
17  *
18  * Optional:
19  * localization (http://plugins.jquery.com/project/localisation)
20  * scrollTo (http://plugins.jquery.com/project/ScrollTo)
21  *
22  * Todo:
23  *  Make batch actions faster
24  *  Implement dynamic insertion through remote calls
25  */
28 (function($) {
30 $.widget("ui.multiselect", {
31   options: {
32                 sortable: true,
33                 searchable: true,
34                 doubleClickable: true,
35                 animated: 'fast',
36                 show: 'slideDown',
37                 hide: 'slideUp',
38                 dividerLocation: 0.6,
39                 nodeComparator: function(node1,node2) {
40                         var text1 = node1.text(),
41                             text2 = node2.text();
42                         return text1 == text2 ? 0 : (text1 < text2 ? -1 : 1);
43                 }
44         },
45         _create: function() {
46                 this.element.hide();
47                 this.id = this.element.attr("id");
48                 this.container = $('<div class="ui-multiselect ui-helper-clearfix ui-widget"></div>').insertAfter(this.element);
49                 this.count = 0; // number of currently selected options
50                 this.selectedContainer = $('<div class="selected"></div>').appendTo(this.container);
51                 this.availableContainer = $('<div class="available"></div>').appendTo(this.container);
52                 this.selectedActions = $('<div class="actions ui-widget-header ui-helper-clearfix"><span class="count">0 '+$.ui.multiselect.locale.itemsCount+'</span><a href="#" class="remove-all">'+$.ui.multiselect.locale.removeAll+'</a></div>').appendTo(this.selectedContainer);
53                 this.availableActions = $('<div class="actions ui-widget-header ui-helper-clearfix"><input type="text" class="search empty ui-widget-content ui-corner-all"/><a href="#" class="add-all">'+$.ui.multiselect.locale.addAll+'</a></div>').appendTo(this.availableContainer);
54                 this.selectedList = $('<ul class="selected connected-list"><li class="ui-helper-hidden-accessible"></li></ul>').bind('selectstart', function(){return false;}).appendTo(this.selectedContainer);
55                 this.availableList = $('<ul class="available connected-list"><li class="ui-helper-hidden-accessible"></li></ul>').bind('selectstart', function(){return false;}).appendTo(this.availableContainer);
57                 var that = this;
59                 // set dimensions
60                 this.container.width(this.element.width()+1);
61                 this.selectedContainer.width(Math.floor(this.element.width()*this.options.dividerLocation));
62                 this.availableContainer.width(Math.floor(this.element.width()*(1-this.options.dividerLocation)));
64                 // fix list height to match <option> depending on their individual header's heights
65                 this.selectedList.height(Math.max(this.element.height()-this.selectedActions.height(),1));
66                 this.availableList.height(Math.max(this.element.height()-this.availableActions.height(),1));
68                 if ( !this.options.animated ) {
69                         this.options.show = 'show';
70                         this.options.hide = 'hide';
71                 }
73                 // init lists
74                 this._populateLists(this.element.find('option'));
76                 // make selection sortable
77                 if (this.options.sortable) {
78                         this.selectedList.sortable({
79                                 placeholder: 'ui-state-highlight',
80                                 axis: 'y',
81                                 update: function(event, ui) {
82                                         // apply the new sort order to the original selectbox
83                                         that.selectedList.find('li').each(function() {
84                                                 if ($(this).data('optionLink'))
85                                                         $(this).data('optionLink').remove().appendTo(that.element);
86                                         });
87                                 },
88                                 receive: function(event, ui) {
89                                         ui.item.data('optionLink').attr('selected', true);
90                                         // increment count
91                                         that.count += 1;
92                                         that._updateCount();
93                                         // workaround, because there's no way to reference
94                                         // the new element, see http://dev.jqueryui.com/ticket/4303
95                                         that.selectedList.children('.ui-draggable').each(function() {
96                                                 $(this).removeClass('ui-draggable');
97                                                 $(this).data('optionLink', ui.item.data('optionLink'));
98                                                 $(this).data('idx', ui.item.data('idx'));
99                                                 that._applyItemState($(this), true);
100                                         });
102                                         // workaround according to http://dev.jqueryui.com/ticket/4088
103                                         setTimeout(function() { ui.item.remove(); }, 1);
104                                 }
105                         });
106                 }
108                 // set up livesearch
109                 if (this.options.searchable) {
110                         this._registerSearchEvents(this.availableContainer.find('input.search'));
111                 } else {
112                         $('.search').hide();
113                 }
115                 // batch actions
116                 this.container.find(".remove-all").click(function() {
117                         that._populateLists(that.element.find('option').removeAttr('selected'));
118                         return false;
119                 });
121                 this.container.find(".add-all").click(function() {
122                         var options = that.element.find('option').not(":selected");
123                         if (that.availableList.children('li:hidden').length > 1) {
124                                 that.availableList.children('li').each(function(i) {
125                                         if ($(this).is(":visible")) $(options[i-1]).attr('selected', 'selected');
126                                 });
127                         } else {
128                                 options.attr('selected', 'selected');
129                         }
130                         that._populateLists(that.element.find('option'));
131                         return false;
132                 });
133         },
134         destroy: function() {
135                 this.element.show();
136                 this.container.remove();
138                 $.Widget.prototype.destroy.apply(this, arguments);
139         },
140         _populateLists: function(options) {
141                 this.selectedList.children('.ui-element').remove();
142                 this.availableList.children('.ui-element').remove();
143                 this.count = 0;
145                 var that = this;
146                 var items = $(options.map(function(i) {
147               var item = that._getOptionNode(this).appendTo(this.selected ? that.selectedList : that.availableList).show();
149                         if (this.selected) that.count += 1;
150                         that._applyItemState(item, this.selected);
151                         item.data('idx', i);
152                         return item[0];
153     }));
155                 // update count
156                 this._updateCount();
157                 that._filter.apply(this.availableContainer.find('input.search'), [that.availableList]);
158   },
159         _updateCount: function() {
160                 this.selectedContainer.find('span.count').text(this.count+" "+$.ui.multiselect.locale.itemsCount);
161         },
162         _getOptionNode: function(option) {
163                 option = $(option);
164                 var node = $('<li class="ui-state-default ui-element" title="' + jsAttr(option.text()) + '"><span class="ui-icon"/>' + jsText(option.text()) + '<a href="#" class="action"><span class="ui-corner-all ui-icon"/></a></li>').hide();
165                 node.data('optionLink', option);
166                 return node;
167         },
168         // clones an item with associated data
169         // didn't find a smarter away around this
170         _cloneWithData: function(clonee) {
171                 var clone = clonee.clone(false,false);
172                 clone.data('optionLink', clonee.data('optionLink'));
173                 clone.data('idx', clonee.data('idx'));
174                 return clone;
175         },
176         _setSelected: function(item, selected) {
177                 item.data('optionLink').attr('selected', selected);
179                 if (selected) {
180                         var selectedItem = this._cloneWithData(item);
181                         item[this.options.hide](this.options.animated, function() { $(this).remove(); });
182                         selectedItem.appendTo(this.selectedList).hide()[this.options.show](this.options.animated);
184                         this._applyItemState(selectedItem, true);
185                         return selectedItem;
186                 } else {
188                         // look for successor based on initial option index
189                         var items = this.availableList.find('li'), comparator = this.options.nodeComparator;
190                         var succ = null, i = item.data('idx'), direction = comparator(item, $(items[i]));
192                         // TODO: test needed for dynamic list populating
193                         if ( direction ) {
194                                 while (i>=0 && i<items.length) {
195                                         direction > 0 ? i++ : i--;
196                                         if ( direction != comparator(item, $(items[i])) ) {
197                                                 // going up, go back one item down, otherwise leave as is
198                                                 succ = items[direction > 0 ? i : i+1];
199                                                 break;
200                                         }
201                                 }
202                         } else {
203                                 succ = items[i];
204                         }
206                         var availableItem = this._cloneWithData(item);
207                         succ ? availableItem.insertBefore($(succ)) : availableItem.appendTo(this.availableList);
208                         item[this.options.hide](this.options.animated, function() { $(this).remove(); });
209                         availableItem.hide()[this.options.show](this.options.animated);
211                         this._applyItemState(availableItem, false);
212                         return availableItem;
213                 }
214         },
215         _applyItemState: function(item, selected) {
216                 if (selected) {
217                         if (this.options.sortable)
218                                 item.children('span').addClass('ui-icon-arrowthick-2-n-s').removeClass('ui-helper-hidden').addClass('ui-icon');
219                         else
220                                 item.children('span').removeClass('ui-icon-arrowthick-2-n-s').addClass('ui-helper-hidden').removeClass('ui-icon');
221                         item.find('a.action span').addClass('ui-icon-minus').removeClass('ui-icon-plus');
222                         this._registerRemoveEvents(item.find('a.action'));
224                 } else {
225                         item.children('span').removeClass('ui-icon-arrowthick-2-n-s').addClass('ui-helper-hidden').removeClass('ui-icon');
226                         item.find('a.action span').addClass('ui-icon-plus').removeClass('ui-icon-minus');
227                         this._registerAddEvents(item.find('a.action'));
228                 }
230                 this._registerDoubleClickEvents(item);
231                 this._registerHoverEvents(item);
232         },
233         // taken from John Resig's liveUpdate script
234         _filter: function(list) {
235                 var input = $(this);
236                 var rows = list.children('li'),
237                         cache = rows.map(function(){
239                                 return $(this).text().toLowerCase();
240                         });
242                 var term = $.trim(input.val().toLowerCase()), scores = [];
244                 if (!term) {
245                         rows.show();
246                 } else {
247                         rows.hide();
249                         cache.each(function(i) {
250                                 if (this.indexOf(term)>-1) { scores.push(i); }
251                         });
253                         $.each(scores, function() {
254                                 $(rows[this]).show();
255                         });
256                 }
257         },
258         _registerDoubleClickEvents: function(elements) {
259                 if (!this.options.doubleClickable) return;
260                 elements.dblclick(function() {
261                         elements.find('a.action').click();
262                 });
263         },
264         _registerHoverEvents: function(elements) {
265                 elements.removeClass('ui-state-hover');
266                 elements.mouseover(function() {
267                         $(this).addClass('ui-state-hover');
268                 });
269                 elements.mouseout(function() {
270                         $(this).removeClass('ui-state-hover');
271                 });
272         },
273         _registerAddEvents: function(elements) {
274                 var that = this;
275                 elements.click(function() {
276                         var item = that._setSelected($(this).parent(), true);
277                         that.count += 1;
278                         that._updateCount();
279                         return false;
280                 });
282                 // make draggable
283                 if (this.options.sortable) {
284                 elements.each(function() {
285                         $(this).parent().draggable({
286               connectToSortable: that.selectedList,
287                                 helper: function() {
288                                         var selectedItem = that._cloneWithData($(this)).width($(this).width() - 50);
289                                         selectedItem.width($(this).width());
290                                         return selectedItem;
291                                 },
292                                 appendTo: that.container,
293                                 containment: that.container,
294                                 revert: 'invalid'
295             });
296                 });
297                 }
298         },
299         _registerRemoveEvents: function(elements) {
300                 var that = this;
301                 elements.click(function() {
302                         that._setSelected($(this).parent(), false);
303                         that.count -= 1;
304                         that._updateCount();
305                         return false;
306                 });
307         },
308         _registerSearchEvents: function(input) {
309                 var that = this;
311                 input.focus(function() {
312                         $(this).addClass('ui-state-active');
313                 })
314                 .blur(function() {
315                         $(this).removeClass('ui-state-active');
316                 })
317                 .keypress(function(e) {
318                         if (e.keyCode == 13)
319                                 return false;
320                 })
321                 .keyup(function() {
322                         that._filter.apply(this, [that.availableList]);
323                 });
324         }
327 $.extend($.ui.multiselect, {
328         locale: {
329                 addAll:'Add all',
330                 removeAll:'Remove all',
331                 itemsCount:'items selected'
332         }
336 })(jQuery);