// ===================================================================
// Author: Matt Kruse <matt@mattkruse.com>
// WWW: http://www.mattkruse.com/
//
// NOTICE: You may use this code for any purpose, commercial or
// private, without any further permission from the author. You may
// remove this notice from your final code if you wish, however it is
// appreciated by the author if at least my web site address is kept.
//
// You may *NOT* re-distribute this code in any way except through its
// use. That means, you can include it in your product, or your web
// site, or any other form where the code is actually being used. You
// may not put the plain javascript up on your site for download or
// include it in your javascript libraries for download. 
// If you wish to share this code with others, please just point them
// to the URL instead.
// Please DO NOT link directly to my .js files from your site. Copy
// the files to your server and use them there. Thank you.
// ===================================================================

// HISTORY
// ------------------------------------------------------------------
// February 29, 2004: Fixed bug caused by LAST update, when control
//    checkbox is the first on checked. I hate when that happens.
// January 28, 2004: Fixed bug that occurred when checkbox was CHECKED
//    by default.
// December 16, 2002: Created
/* 
Original coding done by David Rogers - http://www.MrDaveR.com/

DESCRIPTION: This library lets you quickly and easily make multiple checkboxes
behave as a group by limiting the total number of boxes that can be checked
and/or have a master control checkbox. 

COMPATABILITY: Should work on all Javascript-compliant browsers.

USAGE:
// Create a new CheckBoxGroup object
var myOptions = new CheckBoxGroup();

// Tell the object which checkboxes exist in your group. You may make multiple
// calls to this function, and/or pass multiple arguments. You may specify
// field names exactly, or use a wildcard at the beginning or end of the 
// name.
myOptions.addToGroup("cb1","cb2","optionset*");
myOptions.addToGroup("*checkbox");

// Optionally set a "control" box which will effect all other boxes.
myOptions.setControlBox("masterCheckboxName");

// Specify how your control box will interact with the group. Options are
// either "some" or "all" ("all" is default).
// all: Checking the box will select all the other boxes. Unchecking it will
//      uncheck all the group checkboxes. Selecting all the checkboxes in
//      the group will automatically check the control box.
// some: Checking any checkbox in the group will check the control box. The
//       control box may not be unchecked if any option in the group is 
//       still checked. If no boxes in the group are checked, you may still
//       check the control box.
myOptions.setMasterBehavior("some");

// Optionally set the maximum number of boxes in the group which are allowed
// to be checked. You may pass a second argument which is an alert message to
// be displayed to the user if they exceed this limit.
myOptions.setMaxAllowed(3);
myOptions.setMaxAllowed(3,"You may only select 3 choices!");

// IMPORTANT: After defining the group and behavior, you must insert an action
// into EVERY checkbox's onClick event-handler. Just specify the name of the
// group object that the checkbox belongs to, and pass it 'this' as the argument.
<INPUT TYPE="checkbox" NAME="cb1" onClick="myOptions.check(this)">

// That's it! Your checkboxes will now behave as a group!

*/ 
function CheckBoxGroup() {
        this.controlBox=null;
        this.controlBoxChecked=null;
        this.maxAllowed=null;
        this.maxAllowedMessage=null;
        this.masterBehavior=null;
        this.formRef=null;
        this.checkboxWildcardNames=new Array();
        this.checkboxNames=new Array();
        this.totalBoxes=0;
        this.totalSelected=0;
        // Public methods
        this.setControlBox=CBG_setControlBox;
        this.setMaxAllowed=CBG_setMaxAllowed;
        this.setMasterBehavior=CBG_setMasterBehavior;   // all, some
        this.addToGroup=CBG_addToGroup;
        // Private methods
        this.expandWildcards=CBG_expandWildcards;
        this.addWildcardCheckboxes=CBG_addWildcardCheckboxes;
        this.addArrayCheckboxes=CBG_addArrayCheckboxes;
        this.addSingleCheckbox=CBG_addSingleCheckbox;
        this.check=CBG_check;
        }

// Set the master control checkbox name
function CBG_setControlBox(name) { this.controlBox=name; }

// Set the maximum number of checked boxes in the set, and optionally
// the message to popup when the max is reached.
function CBG_setMaxAllowed(num,msg) {
        this.maxAllowed=num;
        if (msg!=null&&msg!="") { this.maxAllowedMessage=msg; }
        }

// Set the behavior for the checkbox group master checkbox
//      All: all boxes must be checked for the master to be checked
//      Some: one or more of the boxes can be checked for the master to be checked
function CBG_setMasterBehavior(b) { this.masterBehavior = b.toLowerCase(); }

// Add checkbox wildcards to the checkboxes array
function CBG_addToGroup() {
        if (arguments.length>0) {
                for (var i=0;i<arguments.length;i++) {
                        this.checkboxWildcardNames[this.checkboxWildcardNames.length]=arguments[i];
                        }
                }
        }

// Expand the wildcard checkbox names given in the addToGroup method
function CBG_expandWildcards() {
        if (this.formRef==null) {alert("ERROR: No form element has been passed.  Cannot extract form name!"); return false; }
        for (var i=0; i<this.checkboxWildcardNames.length;i++) {
                var n = this.checkboxWildcardNames[i];
                var el = this.formRef[n];
                if (n.indexOf("*")!=-1) { this.addWildcardCheckboxes(n); }
                else if(CBG_nameIsArray(el)) { this.addArrayCheckboxes(n); }
                else { this.addSingleCheckbox(el); }
                }
        }


// Add checkboxes to the group which match a pattern
function CBG_addWildcardCheckboxes(name) {
        var i=name.indexOf("*");
        if ((i==0) || (i==name.length-1)) {
                searchString= (i)?name.substring(0,name.length-1):name.substring(1,name.length);
                for (var j=0;j<this.formRef.length;j++) {
                        currentElement = this.formRef.elements[j];
                        currentElementName=currentElement.name;
                        var partialName = (i)?currentElementName.substring(0,searchString.length) : currentElementName.substring(currentElementName.length-searchString.length,currentElementName.length);
                        if (partialName==searchString) {
                                if(CBG_nameIsArray(currentElement)) this.addArrayCheckboxes(currentElement);
                                else this.addSingleCheckbox(currentElement);
                                }
                        }
                }
        }

// Add checkboxes to the group which all have the same name
function CBG_addArrayCheckboxes(name) {
        if((CBG_nameIsArray(this.formRef[name])) && (this.formRef[name].length>0)) {
                for (var i=0; i<this.formRef[name].length; i++) { this.addSingleCheckbox(this.formRef[name][i]); }
                }
        }

function CBG_addSingleCheckbox(obj) {
        if (obj != this.formRef[this.controlBox]) {
                this.checkboxNames[this.checkboxNames.length]=obj;
                this.totalBoxes++;
                if (obj.checked) {
                        this.totalSelected++;
                        }
                }
        }

// Runs whenever a checkbox in the group is clicked
function CBG_check(obj) {
        var checked=obj.checked;
        if (this.formRef==null) {
                this.formRef=obj.form;
                this.expandWildcards();
                if (this.controlBox==null || obj.name!=this.controlBox) {
                        this.totalSelected += (checked)?-1:1;
                        }
                }
        if (this.controlBox!=null&&obj.name==this.controlBox) {
                if (this.masterBehavior=="all") {
                        for (i=0;i<this.checkboxNames.length;i++) {
                                this.checkboxNames[i].checked=checked;
                                }
                        this.totalSelected=(checked)?this.checkboxNames.length:0;
                        }
                else {
                        if (!checked) {
                                obj.checked = (this.totalSelected>0)?true:false;
                                obj.blur();
                                }
                        }
                }
        else {
                if (this.masterBehavior=="all") {
                        if (!checked) {
                                this.formRef[this.controlBox].checked=false;
                                this.totalSelected--;
                                }
                        else { this.totalSelected++; }
                        if (this.controlBox!=null) {
                                this.formRef[this.controlBox].checked=(this.totalSelected==this.totalBoxes)?true:false;
                                }
                        }
                else {
                        if (!obj.checked) { this.totalSelected--; }     
                        else { this.totalSelected++; }
                        if (this.controlBox!=null) {
                                this.formRef[this.controlBox].checked=(this.totalSelected>0)?true:false;
                                }
                        if (this.maxAllowed!=null) {
                                if (this.totalSelected>this.maxAllowed) {
                                        obj.checked=false;
                                        this.totalSelected--;
                                        if (this.maxAllowedMessage!=null) { alert(this.maxAllowedMessage); }
                                        return false;
                                        }
                                }
                        }
                }
        }

function CBG_nameIsArray(obj) {
        return ((typeof obj.type!="string")&&(obj.length>0)&&(obj[0]!=null)&&(obj[0].type=="checkbox"));
        }
