1/**
2 *  $Id: selectbox.js 147 2006-12-20 22:53:19Z wingedfox $
3 *  $HeadURL: https://svn.debugger.ru/repos/jslibs/BrowserExtensions/tags/BrowserExtensions.003/dom/selectbox.js $
4 *
5 *  Extends the selectbox interface
6 *
7 *  @author Ilya Lebedev <ilya@lebedev.net>
8 *  @modified $Date: 2006-12-21 01:53:19 +0300 (Чтв, 21 Дек 2006) $
9 *  @version $Rev: 147 $
10 *  @title Selectbox
11 *  @license LGPL 2.1 or later
12 */
13// ===================================================================
14//
15// Author: Matt Kruse <matt@mattkruse.com>
16// Author: Ilya Lebedev <ilya@lebedev.net>
17// http://debugger.ru
18//
19// NOTICE: You may use this code for any purpose, commercial or
20// private, without any further permission from the author. You may
21// remove this notice from your final code if you wish, however it is
22// appreciated by the author if at least my web site address is kept.
23//
24// You may *NOT* re-distribute this code in any way except through its
25// use. That means, you can include it in your product, or your web
26// site, or any other form where the code is actually being used. You
27// may not put the plain javascript up on your site for download or
28// include it in your javascript libraries for download.
29// If you wish to share this code with others, please just point them
30// to the URL instead.
31// Please DO NOT link directly to my .js files from your site. Copy
32// the files to your server and use them there. Thank you.
33// ===================================================================
34
35
36// HISTORY
37// ------------------------------------------------------------------
38// October 2, 2006: Rewritten in the OOP style and added number of useful features
39// June 12, 2003: Modified up and down functions to support more than
40//                one selected option
41/*
42COMPATIBILITY: These are fairly basic functions - they should work on
43all browsers that support Javascript.
44*/
45
46/**
47 *  Provides control component for the select box
48 *
49 *  @class
50 *  @param {String} id select box to attach control to
51 *  @scope public
52 */
53Selectbox = function (id /*: String*/) {
54  var self = this;
55  /**
56   *  Selectbox node
57   *
58   *  @type HTMLSelectElement
59   *  @scope private
60   */
61  var node;
62  /**
63   *  List of all the options in the list
64   *  Format of the array elements
65   *  { 'value'    : option value,
66   *    'text'     : option text,
67   *    'defaultSelected' : default selection flag,
68   *    'selected'  : selection flag,
69   *    'visible'   : visible flag,
70   *    'fixed'     : fixes position in the list
71   *  }
72   *
73   *  @type Array
74   *  @scope private
75   */
76  var options = [];
77  /**
78   * Trackers does keep a track of the options
79   *
80   * @type Object
81   * @scope private
82   */
83  var trackers = {
84      'all' : [[]],
85      'added' : [[]],
86      'deleted' : [[]]
87  }
88  /**
89   *  @constructor
90   */
91  var __construct = function () /* :void */{
92    if ((node = document.getElementById(id)) && node.tagName.toLowerCase() != 'select')
93      throw new Error('Node with supplied id="'+id+'" is not the <select> box.')
94    /*
95    *  if node is not exists, create it.
96    */
97    if (!node) {
98        node = document.createElement('select');
99        node.id = 'selectbox'+(new Date).valueOf();
100    } else {
101        self.addOptionsList(node.options, false, false);
102    }
103    node.onblur = __storeOptionsState;
104  }
105  //---------------------------------------------------------------------------
106  // Private methods
107  //---------------------------------------------------------------------------
108  /**
109   * Special 'toString' method, allowing to have a free array sorting
110   *
111   * @return {String}
112   * @scope protected
113   */
114  var __toString = function() {
115      return String(this.text);
116  }
117  /**
118   *  Check all visible options and set the state for them
119   *
120   *  @param {RegExp, String, Array} reg expression to match option text against
121   *  @param {String} type state to be changed, currently 'selected' and 'visible' supported
122   *  @param {Boolean} state true means 'set selected'
123   *  @param {Boolean} pre true means 'preserve already selected options state'
124   *  @param {String} match match needle from: 'start', 'end', 'exact', 'any'. Default is 'start'. Has no meaning, when regexp is supplied.
125   *  @return {Number} number of changed options
126   */
127  var __setOptionsState = function ( reg   /* :RegExp, String, Array */
128                                    ,type  /* :String */
129                                    ,state /* :Boolean */
130                                    ,pre   /* :Boolean */
131                                    ,match /* :String */ ) /* :Number */ {
132    var res = 0;
133    /*
134    *  don't touch options with empty match
135    */
136    if (!isRegExp(reg)) {
137        reg = RegExp.escape(reg);
138        if (!isString(match)) match = 'start';
139        switch (match.toLowerCase()) {
140            case "any" :
141                reg = new RegExp(reg,"i");
142                break;
143            case "end" :
144                reg = new RegExp(reg+"$","i");
145                break;
146            case "exact" :
147                reg = new RegExp("^"+reg+"$","i");
148                break;
149            case "start" :
150            case "default" :
151                reg = new RegExp("^"+reg,"i");
152                break;
153        }
154    }
155    for (var i=0, oL=options.length; i<oL; i++) {
156      /*
157      *  skip already options with matching state, if allowed
158      */
159      if (pre && options[i][type] == state) continue;
160      /*
161      *  flag that some options are processed successfully
162      */
163      if ( reg.test(options[i].text) ) {
164        res += (options[i][type] != state);
165        options[i][type] = state;
166      } else {
167        if ( options[i][type] == state && !pre) {
168          res += (options[i][type] == state);
169          options[i][type] = ! state;
170        }
171      }
172    }
173    /*
174    *  if something has changed, do the update
175    */
176    if (res) self.updateOptions();
177    return res;
178  }
179  /**
180   *  Stores actual selection state, made by user
181   *
182   *  @scope protected
183   */
184  var __storeOptionsState = function () /* :void */{
185      for (var i=0, oL=this.options.length; i<oL; i++) {
186          if (!isUndefined(this.options[i].__idx) && options[this.options[i].__idx]) {
187              options[this.options[i].__idx].selected = this.options[i].selected;
188              options[this.options[i].__idx].defaultSelected = this.options[i].defaultSelected;
189          }
190      }
191  }
192  /**
193   *  Stores option name in the tracker
194   *
195   *  @see #trackers
196   *  @param {String} track name of the tracker
197   *  @param {String} text option text
198   *  @param {String} val option value
199   *  @return {Boolean}
200   *  @scope private
201   */
202  var __saveOptionsTrack = function (track /* :String */, text /* :String */, val /* :String */) /* :Boolean */{
203      if (!trackers[track]) return false;
204      var tmp = trackers[track][trackers[track].length-1];
205      tmp[tmp.length] = {'text' :text, 'value' :val};
206      return true;
207  }
208  /**
209   *  Fixes current tracking state in the past
210   *
211   *  @scope private
212   */
213  var __fixOptionsTrack = function () {
214      for (var i in trackers) {
215          if (!trackers.hasOwnProperty(i) ||
216              0 == trackers[i][trackers[i].length-1].length) continue;
217          trackers[i][trackers[i].length] = [];
218      }
219
220  }
221  //---------------------------------------------------------------------------
222  // Public methods
223  //---------------------------------------------------------------------------
224  /**
225   *  Return the number of options in the list
226   *
227   *  @return {Number}
228   *  @scope public
229   */
230  this.hasOptions = function () /* :Number */{
231    return options.length;
232  }
233  /**
234   *  Redraw options list
235   *
236   *  @scope public
237   */
238  this.updateOptions = function () /* :void */ {
239      var ndx = 0;
240      for (var i=0, oL=options.length; i<oL; i++) {
241          __saveOptionsTrack('all', options[i].text, options[i].value)
242          if (options[i].visible) {
243              if (!node.options[ndx])
244                  node.options[ndx] = new Option();
245              /*
246              *  firefox behavior - it does reset selectbox,
247              *  when even single label has been touched
248              *  so, try to keep its scroll on the place
249              */
250              if (node.options[ndx].text != options[i].text)
251                  node.options[ndx].text        = options[i].text;
252
253              node.options[ndx].value           = options[i].value;
254              node.options[ndx].defaultSelected = options[i].defaultSelected;
255              node.options[ndx].selected        = options[i].selected;
256              node.options[ndx].__idx           = i;
257              ndx++;
258          }
259      }
260      for (var i=node.options.length; i>=ndx ; i-- ) node.options[i] = null;
261      __fixOptionsTrack();
262  }
263  //-------------------------------------------
264  //  SELECTION OPERATION
265  //-------------------------------------------
266  /**
267   *  Selects all available options
268   *
269   *  It's useful, when do the transfer from one select box to another and before submitting a form
270   *
271   *  @scope public
272   */
273  this.selectAllOptions = function () /* :void */ {
274    for (var i=0; i<options.length; i++) {
275       if (options[i].visible) {
276         options[i].selected = true;
277       }
278    }
279    this.updateOptions();
280  }
281  /**
282   *  Selects all matching options
283   *
284   *  @param {String} needle search phrase
285   *  @param {String} match match needle from: 'start', 'end', 'exact', 'any'. Default is 'start'. Has no meaning, when regexp is supplied.
286   *  @return {Number} number of selected options
287   *  @scope public
288   */
289  this.selectMatchingOptions = function (needle /* :String */, match /* :String */) /* :Number */ {
290    return __setOptionsState (needle, 'selected', true, true, match);
291  }
292  /**
293   *  Selects all matching options and reset selection for others
294   *
295   *  @param {String} needle search phrase
296   *  @param {String} match match needle from: 'start', 'end', 'exact', 'any'. Default is 'start'. Has no meaning, when regexp is supplied.
297   *  @return {Number} number of selected options
298   *  @scope public
299   */
300  this.selectOnlyMatchingOptions = function (needle /* :String */, match /* :String */) /* :Number */ {
301    return __setOptionsState (needle, 'selected', true, false, match);
302  }
303  /**
304   *  Removes selection from all available options
305   *
306   *  @scope public
307   */
308  this.unselectAllOptions = function () /* :void */ {
309    for (var i=0, nL=node.options.length; i<nL; i++) {
310         node.options[i].selected = false;
311         options[node.options[i].__idx].selected = false;
312    }
313    node.selectedIndex = -1;
314  }
315  /**
316   *  Selects all matching options
317   *
318   *  @param {String} needle search phrase
319   *  @param {String} match match needle from: 'start', 'end', 'exact', 'any'. Default is 'start'. Has no meaning, when regexp is supplied.
320   *  @return {Number} number of unselected options
321   *  @scope public
322   */
323  this.unselectMatchingOptions = function (needle /* :String */, match /* :String */) /* :Number */ {
324    return __setOptionsState (needle, 'selected', false, true, match);
325  }
326  /**
327   *  Removes selection from all matching options and set for others
328   *
329   *  @param {String} needle search phrase
330   *  @param {String} match match needle from: 'start', 'end', 'exact', 'any'. Default is 'start'. Has no meaning, when regexp is supplied.
331   *  @return {Number} number of selected options
332   *  @scope public
333   */
334  this.unselectOnlyMatchingOptions = function (needle /* :String */, match /* :String */) /* :Number */ {
335    return __setOptionsState (needle, 'selected', false, false, match);
336  }
337  //-------------------------------------------
338  //  FIXATION OPERATION
339  //-------------------------------------------
340  /**
341   *  Fix positions of all matching options
342   *
343   *  @param {String} needle search phrase
344   *  @param {String} match match needle from: 'start', 'end', 'exact', 'any'. Default is 'start'. Has no meaning, when regexp is supplied.
345   *  @return {Number} number of fixed options
346   *  @scope public
347   */
348  this.fixMatchingOptions = function (needle /* :String */, match /* :String */) /* :Number */ {
349    return __setOptionsState (needle, 'fixed', true, true, match);
350  }
351  /**
352   *  Fix positions of all matching options and reset selection for others
353   *
354   *  @param {String} needle search phrase
355   *  @param {String} match match needle from: 'start', 'end', 'exact', 'any'. Default is 'start'. Has no meaning, when regexp is supplied.
356   *  @return {Number} number of fixed options
357   *  @scope public
358   */
359  this.fixOnlyMatchingOptions = function (needle /* :String */, match /* :String */) /* :Number */ {
360    return __setOptionsState (needle, 'fixed', true, false, match);
361  }
362  /**
363   *  Unfixes positions of all matching options
364   *
365   *  @param {String} needle search phrase
366   *  @param {String} match match needle from: 'start', 'end', 'exact', 'any'. Default is 'start'. Has no meaning, when regexp is supplied.
367   *  @return {Number} number of unfixed options
368   *  @scope public
369   */
370  this.unfixMatchingOptions = function (needle /* :String */, match /* :String */) /* :Number */ {
371    return __setOptionsState (needle, 'fixed', false, true, match);
372  }
373  /**
374   *  Unfixes positions of all matching options and set for others
375   *
376   *  @param {String} needle search phrase
377   *  @param {String} match match needle from: 'start', 'end', 'exact', 'any'. Default is 'start'. Has no meaning, when regexp is supplied.
378   *  @return {Number} number of unfixed options
379   *  @scope public
380   */
381  this.unfixOnlyMatchingOptions = function (needle /* :String */, match /* :String */) /* :Number */ {
382    return __setOptionsState (needle, 'fixed', false, false, match);
383  }
384  //-------------------------------------------
385  //  VISIBILITY OPERATION
386  //-------------------------------------------
387  /**
388   *  Shows all matching options
389   *
390   *  @param {String} needle search phrase
391   *  @param {String} match match needle from: 'start', 'end', 'exact', 'any'. Default is 'start'. Has no meaning, when regexp is supplied.
392   *  @return {Number} number of shown options
393   *  @scope public
394   */
395  this.showMatchingOptions = function (needle /* :String */, match /* :String */) /* :Number */ {
396    return __setOptionsState (needle, 'visible', true, true, match);
397  }
398  /**
399   *  Shows all matching options and hide others
400   *
401   *  @param {String} needle search phrase
402   *  @param {String} match match needle from: 'start', 'end', 'exact', 'any'. Default is 'start'. Has no meaning, when regexp is supplied.
403   *  @return {Number} number of shown options
404   *  @scope public
405   */
406  this.showOnlyMatchingOptions = function (needle /* :String */, match /* :String */) /* :Number */ {
407    return __setOptionsState (needle, 'visible', true, false, match);
408  }
409  /**
410   *  Hides all matching options
411   *
412   *  @param {String} needle search phrase
413   *  @param {String} match match needle from: 'start', 'end', 'exact', 'any'. Default is 'start'. Has no meaning, when regexp is supplied.
414   *  @return {Number} number of hidden options
415   *  @scope public
416   */
417  this.hideMatchingOptions = function (needle /* :String */, match /* :String */) /* :Number */ {
418    return __setOptionsState (needle, 'visible', false, true, match);
419  }
420  /**
421   *  Hides all matching options and show others
422   *
423   *  @param {String} needle search phrase
424   *  @param {String} match match needle from: 'start', 'end', 'exact', 'any'. Default is 'start'. Has no meaning, when regexp is supplied.
425   *  @return {Number} number of hidden options
426   *  @scope public
427   */
428  this.hideOnlyMatchingOptions = function (needle /* :String */, match /* :String */) /* :Number */ {
429    return __setOptionsState (needle, 'visible', false, false, match);
430  }
431  //-------------------------------------------
432  //  MISC FUNCTIONS
433  //-------------------------------------------
434  /**
435   *  Sorts the options
436   *
437   *  @scope public
438   */
439  this.sort = function ()/* :void */{
440    options = options.sort();
441    this.updateOptions();
442  }
443  //-------------------------------------------
444  //  GETTERS
445  //-------------------------------------------
446  /**
447   *  Returns current selectbox value
448   *
449   *  @return {String}
450   *  @scope public
451   */
452  this.getValue = function () {
453      return node.value;
454  }
455  /**
456   *  Return visible option or its property, if exists
457   *
458   *  @param {Number} id option id
459   *  @param {Object} p optional property name
460   *  @scope public
461   */
462  this.getOption = function(id /* :Number */, p /* :String */) /* :String,Array */ {
463      if (!isNumeric(id) || id<0 || !node.options[id]) return "";
464      return isString(p)?node.options[id][p]:[text.value,node.options[id].value];
465  }
466  /**
467   *  Returns a list of the selected options
468   *
469   *  @return {Array}
470   *  @scope public
471   */
472  this.getSelectedOptions = function () /* :Array */ {
473      var res = [];
474      for (var i=0, oL=node.options.length; i<oL; i++) {
475          if (node.options[i].selected) {
476              res[res.length] = [node.options[i].text, node.options[i].value];
477          }
478      }
479      return res;
480  }
481  /**
482   *  Returns the number of options in the box
483   *
484   *  @return {Number}
485   *  @scope public
486   */
487  this.getOptionsLength = function () /* :Number */ {
488      return node.options.length;
489  }
490  /**
491   *  Returns the selected index
492   *
493   *  @return {Number}
494   *  @scope public
495   */
496  this.getSelectedIndex = function () /* :Number */ {
497      return node.selectedIndex;
498  }
499  /**
500   *  Returns ID of the element, it is bound to
501   *
502   *  @return {String}
503   *  @scope public
504   */
505  this.getId = function () /* :String */ {
506      return id;
507  }
508  /**
509   *  Returns the element, it is bound to
510   *
511   *  @return {HTMLSelectElement}
512   *  @scope public
513   */
514  this.getEl = function () /* :HTMLSelectElement */ {
515      return node;
516  }
517  /**
518   *  Returns arary of options title text
519   *
520   *  @param {Array} ids optional list of ids to get names of
521   *  @return {Array} list of options names
522   *  @scope public
523   */
524  this.getOptionsNames = function (ids /* :Array */) /* :Array */ {
525      if (ids) ids = new RegExp("^("+RegExp.escape(ids)+")$","i");
526      var tmp = [];
527      for (var i=0, oL=options.length; i<oL; i++) {
528          if (isUndefined(ids) || (isRegExp(ids) && ids.test(options[i].value))) {
529              tmp[tmp.length] = options[i].text;
530          }
531      }
532      return tmp;
533  }
534  /**
535   * Returns requested options track
536   *
537   * @param {String} type one of 'all', 'added', 'deleted'
538   * @param {String} part optoinal 'text' or 'value'
539   * @param {String} j optional list joiner
540   * @param {Number} rev optional revision number
541   * @return {String} joined options track
542   */
543  this.getOptionsTrack = function ( type /* :String */
544                                   ,part /* :String */
545                                   ,j /* :String */
546                                   ,rev /* :Number */) /* :String */{
547    if (!trackers[type]) return [];
548    if (!rev || !trackers[type][rev]) rev = trackers[type].length-2;
549    if (!trackers[type][rev]) rev = trackers[type].length-1;
550    if (!trackers[type][rev]) rev = 0;
551    var tmp = [];
552    for (var i=0, tL=trackers[type][rev].length; i<tL; i++) {
553        tmp[tmp.length] = part?trackers[type][rev][i][part]:trackers[type][rev][i];
554    }
555    return j?tmp.join(j):tmp;
556  }
557  //-------------------------------------------
558  //  OPTIONS MANIPULATION
559  //-------------------------------------------
560  /**
561   *  Removes all matching options, preserving the selection
562   *
563   *  @param {String} needle search phrase
564   *  @param {String} match match needle from: 'start', 'end', 'exact', 'any'. Default is 'start'. Has no meaning, when regexp is supplied.
565   *  @scope public
566   */
567  this.removeMatchingOptions = function (needle /* :String */, match /* :String */) {
568      var tmp = self.getSelectedOptions().flatten([0]);
569      self.selectOnlyMatchingOptions(needle, match);
570      self.removeSelectedOptions();
571      if (tmp.length>0) self.selectOnlyMatchingOptions(tmp, 'exact');
572  }
573  /**
574   *  Removes all selected options from the list
575   *
576   *  @return {Array} list of deleted options
577   *  @scope public
578   */
579  this.removeSelectedOptions = function () /* :Array */{
580    var res = [], tmp;
581    for (var i=(options.length-1); i>=0; i--) {
582      if (options[i].selected && (tmp = self.removeOption (i, false)))
583          res[res.length] = tmp;
584    }
585    if (res) this.updateOptions();
586    node.selectedIndex = -1;
587    return res;
588  }
589  /**
590   *  Removes all the options
591   *
592   *  @return {Array} list of deleted options
593   *  @scope public
594   */
595  this.removeAllOptions = function () /* :Array */{
596    var res = [], tmp;
597    for (var i=(node.options.length-1); i>=0; i--) {
598      if (tmp = self.removeOption (i, false))
599          res[res.length] = tmp;
600    }
601    node.selectedIndex = -1;
602    if (res) self.updateOptions();
603    return res;
604  }
605  /**
606   *  Removes the option with specified index
607   *
608   *  @param {Number} idx option index
609   *  @param {Boolean} update after removal
610   *  @return {Boolean} removal state
611   *  @scope public
612   */
613  this.removeOption = function ( idx /* :Number */
614                                ,update /* :Boolean */) /* :Boolean */{
615    if (!options[idx]) return false;
616    /*
617    * update the track
618    */
619    __saveOptionsTrack ('deleted', options[idx].text, options[idx].value);
620    /*
621    * and delete
622    */
623    var res = options[idx];
624    options.splice(idx,1);
625    if (false !== update) this.updateOptions();
626    return res;
627  }
628  /**
629   *  Adds options to the list
630   *
631   *  @param {String} text option title
632   *  @param {String} value option value
633   *  @param {Boolean} selected selection state
634   *  @param {Boolean} defaultSelected selection state
635   *  @param {Boolean} sort
636   *  @param {Boolean} update if 'false' is not explicitly specified, option is added immediately
637   *  @return {Boolean} addition state
638   */
639  this.addOption = function ( text /* :String */
640                             ,value /* :String */
641                             ,defaultSelected /* :Boolean */
642                             ,selected /* :Boolean */
643                             ,sort /* :Boolean */
644                             ,update /* :Boolean */) /* :Boolean */ {
645    if (!isScalar(text) || !isScalar(value)) return false;
646    if (isUndefined(update)) update = true;
647    options[options.length] = { 'text'  : text
648                               ,'value' : value
649                               ,'defaultSelected' : defaultSelected
650                               ,'selected' : selected
651                               ,'visible'  : true
652                               ,'fixed'    : false
653                              };
654    if (update) node.options[node.options.length] = new Option ( text ,value ,defaultSelected ,selected);
655    /*
656    *  overload the 'toString' method
657    */
658    options[options.length-1].toString = __toString;
659    /*
660    * update the track
661    */
662    __saveOptionsTrack ('added', options[options.length-1].text, options[options.length-1].value);
663    if (sort) this.sort();
664    return true;
665  }
666  /**
667   *  Adds the array of the options to the list
668   *
669   *  @param {Array} list array of the options
670   *  @param {Boolean} sort
671   *  @param {Boolean} update if 'false' is not explicitly specified, option is added immediately
672   *  @return {Boolean}
673   *  @scope public
674   */
675  this.addOptionsList = function ( list /* :Array */
676                                  ,sort /* :Boolean */
677                                  ,update /* :Boolean */) /* :Boolean */ {
678    if (isUndefined(list) || !isNumeric(list.length) || list.length<1) return false;
679    if (isUndefined(update)) update = true;
680
681    for (var i=0, lL=list.length; i<lL; i++) {
682        var li = list[i];
683        if (li.text) self.addOption (li.text, li.value?li.value:li.text
684                                    ,Boolean(li.selected), Boolean(li.defaultSelected)
685                                    ,false, update);
686        else if (isString(li)) self.addOption (li, li
687                                              ,false, false
688                                              ,false, update);
689        else if (li[0]) self.addOption (li[0], li[1]?li[1]:li[0]
690                                       ,Boolean(li[2]), Boolean(li[3])
691                                       ,false, update);
692    }
693    if (sort) this.sort();
694    else this.updateOptions();
695    return true;
696  }
697  //-------------------------------------------
698  //  OPTIONS TRANSFER
699  //-------------------------------------------
700  /**
701   *  Copies selected options to another select box
702   *
703   *  @param {SelectBox} to SelectBox to copy options to
704   *  @param {Boolean} autosort enables autosort on update
705   *  @param {RegExp} regex string to keep matching options
706   *  @return {Boolean} move state
707   */
708  this.copySelectedOptions = function ( to /* :Selectbox*/
709                                       ,regex /* :RegExp */
710                                       ,autosort /* :Boolean */) /* :Boolean */{
711    if (!to || !to.addOption) return false;
712    /*
713    * Unselect matching options, if required
714    */
715    if (regex) {
716      self.unselectMatchingOptions(regex);
717    }
718    /*
719    * Copy them over
720    */
721    for (var i=0, oL=options.length; i<oL; i++) {
722      if (options[i].selected && options[i].visible) {
723        to.addOption (options[i].text, options[i].value, options[i].selected, options[i].defaultSelected);
724      }
725    }
726    if (autosort) {
727      to.sort();
728    } else {
729      to.updateOptions();
730    }
731    return true;
732  }
733  /**
734   *  Copies all options to another SelectBox
735   *
736   *  @param {SelectBox} to SelectBox to copy options to
737   *  @param {Boolean} autosort enables autosort on update
738   *  @param {RegExp} regex string to keep matching options
739   *  @return {Boolean} move state
740   */
741  this.copyAllOptions = function ( to /* :Selectbox*/
742                                  ,regex /* :RegExp */
743                                  ,autosort /* :Boolean */) /* :Boolean */{
744    if (!to || !to.addOption) return false;
745    self.selectAllOptions();
746    self.copySelectedOptions(to,regex,autosort);
747  }
748  /**
749   *  Moves selected options to another select box
750   *
751   *  This function moves options between select boxes. Works best with
752   *  multi-select boxes to create the common Windows control effect.
753   *  Passes all selected values from the first object to the second
754   *  object and re-sorts each box.
755   *  If a third argument of 'false' is passed, then the lists are not
756   *  sorted after the move.
757   *  If a fourth string argument is passed, this will function as a
758   *  Regular Expression to match against the TEXT or the options. If
759   *  the text of an option matches the pattern, it will NOT be moved.
760   *  It will be treated as an unmoveable option.
761   *  You can also put this into the <SELECT> object as follows:
762   *    onDblClick="moveSelectedOptions(this,this.form.target)
763   *  This way, when the user double-clicks on a value in one box, it
764   *  will be transferred to the other (in browsers that support the
765   *  onDblClick() event handler).
766   *
767   *  @param {SelectBox} to SelectBox to copy options to
768   *  @param {Boolean} autosort enables autosort on update
769   *  @param {RegExp} regex string to keep matching options
770   *  @return {Boolean} move state
771   */
772  this.moveSelectedOptions = function ( to /* :Selectbox*/
773                                       ,regex /* :RegExp */
774                                       ,autosort /* :Boolean */) /* :Boolean */{
775    if (!to || !to.addOption) return false;
776    /*
777    * Unselect matching options, if required
778    */
779    if (regex) {
780      self.unselectMatchingOptions(regex);
781    }
782    /*
783    * Reset selection on the target box
784    */
785    to.unselectAllOptions();
786
787    /*
788    * Move them over
789    */
790    var opts = self.removeSelectedOptions(false);
791
792    for (var i=opts.length-1; i>=0; i--) {
793        to.addOption (opts[i].text, opts[i].value, opts[i].defaultSelected, opts[i].selected);
794    }
795    if (autosort) {
796      to.sort();
797      self.sort();
798    } else {
799      to.updateOptions();
800      self.updateOptions();
801    }
802    return true;
803  }
804  /**
805   *  Move all options to another SelectBox
806   *
807   *  @param {SelectBox} to SelectBox to copy options to
808   *  @param {Boolean} autosort enables autosort on update
809   *  @param {RegExp} regex string to keep matching options
810   *  @return {Boolean} move state
811   */
812  this.moveAllOptions = function ( to /* :Selectbox*/
813                                  ,regex /* :RegExp */
814                                  ,autosort /* :Boolean */) /* :Boolean */{
815    if (!to || !to.addOption) return false;
816    self.selectAllOptions();
817    self.moveSelectedOptions(to,regex,autosort);
818  }
819  //-------------------------------------------
820  //  OPTIONS MOVEMENT
821  //-------------------------------------------
822  /**
823   *  Swap 2 options
824   *
825   *  @param {Number} idx1
826   *  @param {Number} idx2
827   *  @return {Boolean} swap state
828   *  @scope public
829   */
830  this.swapOptions = function ( idx1 /* :Number */
831                               ,idx2 /* :Number */) /* :Boolean */ {
832    if (!options[idx1] || options[idx2]) return false;
833    var tmp = options[idx1];
834    options[idx1] = options[idx2];
835    options[idx2] = tmp;
836    this.updateOptions();
837  }
838  /**
839   *  Moves specified option up
840   *
841   *  @return {Boolean} operation state
842   *  @scope public
843   */
844   this.moveSelectedOptionsUp = function () /* :Boolean */{
845     var res = false;
846     for (i=0, oL=options.length; i<oL; i++) {
847       if (options[i].selected && options[i].visible) {
848         if (i != 0 && !options[i-1].selected && !options[i-1].fixed) {
849           this.swapOptions(i,i-1);
850           obj.options[i-1].selected = true;
851         }
852       }
853     }
854   }
855  /**
856   *  Moves specified option up
857   *
858   *  @return {Boolean} operation state
859   *  @scope public
860   */
861   this.moveSelectedOptionsUp = function () /* :Boolean */{
862     var res = false;
863     for (oL=i=options.length-1; i>=0; i--) {
864       if (options[i].selected && options[i].visible) {
865         if (i != oL && !options[i+1].selected && !options[i+1].fixed) {
866           this.swapOptions(i,i+1);
867           obj.options[i+1].selected = true;
868         }
869       }
870     }
871   }
872  //-------------------------------------------
873  //  SELECTION MOVEMENT
874  //-------------------------------------------
875  /**
876   *  Selects the option by it's id
877   *
878   *  @param {Number} id option id
879   *  @param {Boolean} force
880   *  @return {Boolean}
881   *  @scope public
882   */
883  this.selectOption = function (id /* :Number */, force) {
884      if (!isNumber(id) || (!force && (id<0 || !node.options[Math.abs(id)]))) return false;
885      self.unselectAllOptions();
886      if (id >= node.options.length) {
887          id = node.options.length-1;
888      } else if (id < 0) {
889          id = 0;
890      }
891      node.options[id].selected = true;
892      options[node.options[id].__idx].selected = true;
893      return true;
894  }
895  /**
896   *  Moves selection 1 position higher on the list, allows to cycle movement
897   *  Don't use __updateOptions here, because it affects the visual performance
898   *
899   *  @param {Boolean} cyclic allows to move selection from the beginning to the end of the list
900   *  @scope public
901   */
902  this.selectPrev = function (cycle /* :Boolean */) /* :void */{
903     var res = false
904        ,selected = false;
905     for (i=node.options.length-1; i>=0; i--) {
906       if (selected || node.options[i].selected) {
907           var tmp = node.options[i].selected;
908           node.options[i].selected = selected;
909           options[node.options[i].__idx].selected = selected;
910           selected = tmp;
911       }
912     }
913     if (selected) {
914         if (cycle) {
915             for (i=node.options.length-1; i>=0; i--) {
916                 node.options[0].selected = false;
917                 node.options[i].selected = true;
918                 options[node.options[0].__idx].selected = false;
919                 options[node.options[i].__idx].selected = true;
920                 break;
921             }
922         } else {
923             node.options[0].selected = true;
924             options[node.options[0].__idx].selected = true;
925         }
926     }
927  }
928  /**
929   *  Moves selection 1 position lower on the list, allows to cycle movement
930   *  Don't use __updateOptions here, because it affects the visual performance
931   *
932   *  @param {Boolean} cyclic allows to move selection from the end to the beginning of the list
933   *  @scope public
934   */
935  this.selectNext = function (cycle /* :Boolean */) /* :void */ {
936     var res = false
937        ,selected = false;
938     for (i=0, oL=node.options.length; i<oL; i++) {
939       if (selected || node.options[i].selected) {
940           var tmp = node.options[i].selected;
941           node.options[i].selected = selected;
942           options[node.options[i].__idx].selected = selected;
943           selected = tmp;
944       }
945     }
946     if (selected) {
947         if (cycle) {
948             for (i=0, oL=node.options.length; i<oL; i++) {
949                 options[node.options[oL-1].__idx].selected = false;
950                 options[node.options[i].__idx].selected = true;
951                 node.options[oL-1].selected = false;
952                 node.options[i].selected = true;
953                 break;
954             }
955         } else {
956             node.options[oL-1].selected = true;
957             options[node.options[oL-1].__idx].selected = true;
958         }
959     }
960  }
961   /*
962   * let's call a constructor
963   */
964  __construct();
965}
966