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