+ Start a Discussion
SteveBowerSteveBower 

Dependent Picklists

I found myself needing to implement Dependent Picklists in a UI I'm working on.  However, as has been noted in other threads, dependent picklists aren't actually implemented in the Ajax Beta 3.3 Toolkit.  So, I went and got them working.  Here's what I implemented.  I welcome any comments, critiques, suggestions, etc.

Frankly, there have been a lot of bugs and code fixes submitted by people on these boards, I REALLY think it's time for a Beta 4.0, or, god forbid, a Production version of the Ajax toolkit.

I hope this helps some of you, thanks, Steve Bower.


(In general, I make changes by overriding code in sforceClient.js by inserting my own changed code after the "<script src=...sforceClient.js> tag. )

First, this block can be added to the sforceClient.js section where the other prototypes for PicklistEntry are defined.  Somewhere around line 2530.

Note: I know that the sample code for the Java version used a bitmask of 0x80 before shifting it.  That just didn't work for me, it was off by one.  So, it's possible I've done something wrong here, but this code is working fine. 

Code:
Sforce.PicklistEntry.prototype.getValidFor = function() { return this.validFor; }
Sforce.PicklistEntry.prototype.getLabel = function() { return this.label; }
Sforce.PicklistEntry.prototype.getValue = function() { return this.value; }
Sforce.PicklistEntry.prototype.isValidFor = function(n) {
 // ValidFor is a bitmap.  Each bit corresponds to an entry in the controlling 
 // picklist, so if the Controlling field has 0=colors 1=tastes, then this
 // validFor field will have two significant bits indicating if this PicklistEntry
 // is valid for each of the two categories.  Reading left to right, the first
 // bit indicates if this entry is valid for Colors, and the next bit for Tastes.
 //
 // First isolate which number in the array we want to inspect by dividing 
 // the index we're looking for by 8.  (n>>3 is a bitwise divide by 8).
 // Now we have the number, we need to figure out which single bit to mask off.
 // This would be the remainder after the divide by 8.  Build a mask for it.
 // Note: using 0x40 instead of 0x80 seems to work for me.  0x80 was off.
 return (((0x40 >> (n%8)) & (this.validFor[(n>>3)])) != 0);
}

 Now, as someone else noted, the "validFor" value in the picklistEntry's that are being returned in a Sforce.DescribeSObjectResult object appear to be missing.  That's because they are.  So, we need to add some code to the createPicklistValues function that is defined in the DescribeSObjectResult function definition.  I'd say "around" line 2316, however in actuality the entire createPicklistValues function is defined on that one line in one huge run-on line of code.   Either way, you want to add:

Code:
picklistEntry.validFor = [0,0,0,0];
// vFor is a four character string holding the bitset.
var vFor = Sforce.DOM.GetElementValue (node[i], "validFor");
if (vFor != "" && vFor != null ) {
        for (var j=0;j<vFor.length;j++) {
         // Convert the letters to a numeric array so bitwise 
  // operations are possible.
  var n = vFor.charCodeAt(j);
  picklistEntry.validFor[j] = n;
 }
}

into the function. So createPickListValues ends up looking like:

Code:
 createPicklistValues = function(node) {
  if (node.length == 0) {
   return new Array();
  } else {
   var ret = new Array();
   for (var i=0;i<node.length;i++) {
    var picklistEntry = new Sforce.PicklistEntry();
    picklistEntry.active = Sforce.DOM.GetBool(Sforce.DOM.GetElementValue (node[i], "active"));
    picklistEntry.defaultValue = Sforce.DOM.GetBool(Sforce.DOM.GetElementValue (node[i], "defaultValue"));
    picklistEntry.label = Sforce.DOM.GetElementValue (node[i], "label");
    picklistEntry.value = Sforce.DOM.GetElementValue (node[i], "value");
    picklistEntry.validFor = [0,0,0,0];
    // vFor is a four character string holding the bitset.
    var vFor = Sforce.DOM.GetElementValue (node[i], "validFor");
    if (vFor != "" && vFor != null ) {
     for (var j=0;j<vFor.length;j++) {
      // Convert the letters to a numeric array so bitwise 
      // operations are possible.
      var n = vFor.charCodeAt(j);
      picklistEntry.validFor[j] = n;
     }
    }
    ret[i] = picklistEntry;
   }
   return ret;
  }
 };


Ok, so now you have data in the fields and methods to access it.
All you need is a code example which shows how to use it. In the sample
provided for Java, they are building a matrix by going "across" one
picklistEntry at a time and determining it's status for each bit.

All I really wanted was some code to generate a new Options array
for an element depending on the index of the controlling field
which I would pass in. So, yes, I'm expensivly rebuilding my
Options lists whenever needed instead of building a cached array
of Select Option arrays and then referring to them, but this is
fine for this particular usage.

Sample:
function reloadDependentPicklistElement(element,bean,fld,depIndex) {

  /*
  element: the DOM element we are going to create a new Option list for.
  It should be a Select element, we aren't checking that.
  
                bean: the type of bean we are looking at:  "opportunity", "account", etc.
  
                fld: the field in the bean we are building a picklist from.  This should
  obviously be a picklist type field but we aren't checking that.
  
                depIndex: the index into the Controller Picklist that we are testing.
  (Normally this will come from a nearby DOM controllerElement.indexSelected 
  type of construct.)
  
  Returns an Options array loaded into the requested Element.
  */
  var j = bean.fieldMap.getItem(fld);
  var startopt = 0;
  element.options.length = 0; 
  for (var i=0; i < j.picklistValues.length; i++) {
   var plv = j.picklistValues[i];
   if (j.dependentPicklist == true) {
    if (plv.isValidFor(depIndex)) {
     element.options[startopt++] = new Option(plv.label,plv.value);
    }
   } else {
    element.options[startopt++] = new Option(plv.label,plv.value);
   }
   if (plv.defaultValue == true) {
    element.options[startopt-1].selected = true
    element.selectedIndex = startopt-1; // this one.
   }
  }
  element.options.length = startopt;
 }

 
I appreciate any feedback, comments, tips, improvements, etc. Thanks, Steve.

 

gsickalgsickal
Steve, does this still work for you in v8.0 of the api?  I tried this and while the values in validFor are present and not empty, some of the seem to be incorrect.  Do I still need to override the function in 8.0 or change your code some other way?
gsickalgsickal
Is there a problem with the validFor bitset for dependent picklists in the 8.0 api?  i am getting some values that are off and are causing my dependent picklist to display the wrong values... how can i override the validFor function in the 8.0 api to work using the method described above?  does this method work for picklists of a long length and for which there are more than 4 bits needed to describe the bitmask (i.e. 35 possible controller picklist values with 95 dependent picklist values)?
SuperfellSuperfell
If you have a reproducable case where the validFor bitset is wrong, please please log a case so we can fix it.

gsickalgsickal

Simon, I'm not positive it's a bug in ValidFor.  It may be something I'm doing wrong... I used Steve's technique above to build a dependent picklist but I didn't override validFor the way he did because I was getting some values back whereas earlier posts weren't getting any values.... should i be overridding validFor and if so, how do I do this in my scontrol using 8.0?   i searched connection.ps but wasn't able to find any definition for PicklistEntry or the validFor function... Where is this defined?  here is the code snippet to build my dependent picklist from the describe call.  Am I doing something wrong here?

Code:

function outputDependentPicklist(field,object,value,currRow) {
  var output = "";
  var dsoResult = sforce.connection.describeSObject(object);
  var fields = dsoResult.fields;
    
  for (i=0; i<fields.length; i++){
    var fieldFromDescribe = fields[i];
    if (fieldFromDescribe.name == field) {
      i = fields.length;
      var plValues = fieldFromDescribe.picklistValues;
      output += "<option value=\"\">--None--</option>";

      var isDependentPicklist = new Boolean(fieldFromDescribe.dependentPicklist);
      if (isDependentPicklist == true) {
        var cId = currRow + "-" + fieldFromDescribe.controllerName;
        var cElem = document.getElementById(cId);
        var cIdx = cElem.selectedIndex;
        var cVal = cElem.options[cIdx].value;

        for (j=0; j<plValues.length; j++) {
          var plv = plValues[j];
          var plLabel = plv.label;
          var plValue = plv.value;
          var plValidFor = plv.validFor;
              
          if (plValidFor != "" && plValidFor != null) {
             var plvf = new Array(plValidFor.length);
             for (var k=0; k<plValidFor.length; k++) {
               //convert letters to numeric array for bitwise operations
               var n = plValidFor.charCodeAt(k);
               plv.validFor[k] = n;
               plvf[k] = n;
            }

            if (isValidFor(plvf,cIdx)) {
              output += "<option value=\"" + plValue + "\"";
              if (plValue == value) {
                output += " selected";
              }
              output += " title=\"" + plLabel + "\"";
              output += ">" + plLabel + "</option>";
            }
          }
        }
      }
    }
  }
  return output;
}

function isValidFor(plv,n) {
  return (plv[(n>>3)] & (0x40 >> (n % 8))) != 0;
}


 

Message Edited by gsickal on 03-08-2007 01:05 PM

SteveBowerSteveBower
I'm sorry, I haven't had to try this against 8.0 yet so I don't know if it still works.  If I'm recalling correctly, and I might not be, on one of the other threads discussing Picklist entries someone thought my implementation of isValidFor was incorrect, and off by a bit.  But, I don't remember the details, and, as I noted, this worked for me at the time. I guess it's time to whip out the debugger!  :-)  Best of luck, Steve.

gsickalgsickal

Steve, maybe you could might be so kind as to take a quick look at this.  I have spent countless hours trying to debug the problem without success... You can easily duplicate the problem i this way:

(1) create a controller picklist and add values C1,C2,C3,C4

(2) create a dependent picklist and add values D1A, D1B, D1C, D2A, D2B, D2C, D3A, D3B, D3C, D4A, D4B, D4C.  Assign D1x to C1, D2x to C2, D3x to C3, and D4x to C4.

(3) show the picklists using your code and everything works okay except for the C1 which picks up the 3 values for D1A,D1B,D1C in addition to the ones it should get for C4.  It appears this is because the validFor value I get back is 103 but should be 97... Do you concur?  Is this a validFor error or an error in ,your original implementation?

Also Simon, I created a case for this yesterday, the number is Case 01150344:API call error, perhaps if y7ou could you might take a quick look?  Thank yoou very much

Message Edited by gsickal on 03-09-2007 09:29 AM

zakzak
You're missing the step of base64 decoding the validFor string, this generated the right results for me.

// assume dep is the Field object for the dependent picklist field and
// ctrl is the Field object for the controlling field.

var b64 = new sforce.Base64Binary("");
for (var i =0; i <dep.picklistValues.length; i++) {
    var ple = dep.picklistValues[i];
    var ln = ple.label + " ";
    var vf = b64.decode(ple.validFor);
    for (var j = 0; j < ctrl.picklistValues.length; j++) {
          var bits = vf.charCodeAt(j >> 3);
          if ((bits & (0x80 >> j)) != 0)
            ln += "x ";
          else
            ln += "  ";
    }
    output.innerHTML += ln + "<br>";
}


Cheers
Simon

Message Edited by zak on 03-11-2007 01:45 PM

gsickalgsickal
Perfect !  That works great.  I didn't realize you had to base64 decode the validFor bits as I thought the Ajax toolkit would have taken care of that automatically... But thanks very much for pointing this out!
Vijay RautVijay Raut

Hi all,

I am also getting same problem, where i  am not getting the values of validFor property of the picklist values. Its returning me empty.

So hw should i get those things so that i will able to use above code snippest for the Dependant picklist values. Which will provide me the UI design by SControl for controlling and controlled field on the page.

Thanks in Advance.

gsickalgsickal
Basically what Simon told me is that if you are using the Ajax toolkit you need to base64 decode the bitset in order for it to be valid.  Here is the code snippet for how I did this using his advice.  The functioncreateDependentPicklistMap takes a table name and the dependent picklist name and creates a vectored map which can be drawn on the screen.  It also illustrates how to decode the bitset and use the bitwise shifting logic described in the api docs to make sense of the validFor bits:
 
Code:
//cached picklist map
var gPicklistMap;

//controller picklist
var gcpName = "";
var gcpValues = new Array();

//dependent picklist
var gdpName = "";
var gdpValues = new Array();

function createDependentPicklistMap(table,field) {
    var dso = sforce.connection.describeSObject(table);
    var b64 = new sforce.Base64Binary("");
    var debugMap = 0;
    
    //get dependent picklist values and controller name
    for (var i=0; i<dso.fields.length; i++){
        var fld = dso.fields[i];
        if (fld.name == field) {
         gcpName = fld.controllerName;
         gdpName = fld.name;
         gdpValues = fld.picklistValues;
         i = dso.fields.length;
        }
    }
    
    //get controller picklist values
    if (debugMap) document.write("<font face=\"Courier New\">&nbsp;&nbsp;&nbsp;");
    for (var i=0; i<dso.fields.length; i++){
        var fld = dso.fields[i];
        if (fld.name == gcpName) {
         gcpValues = fld.picklistValues;
         i = dso.fields.length;
        }
    }
    
 if (debugMap) {
     for (var i=0; i<gcpValues.length; i++){
         document.write(i+1);
     }
     document.write("<br>");
 }
    gPicklistMap = new Array(gdpValues.length);

 //build validfor bitset map
 for (var dpi=0; dpi<gdpValues.length; dpi++) {
  var dplv = gdpValues[dpi];
  var dpLabel = dplv.label;
  var dpValue = dplv.value;
  
  var vf = b64.decode(dplv.validFor);
  var vfbits = dpi + 1;
  if (vfbits < 10) {
   vfbits += "&nbsp;";
  }
  vfbits += "&nbsp;";

  gPicklistMap[dpi] = new Array(gcpValues.length);

  for (var cpi=0; cpi<gcpValues.length; cpi++) {
   var bits = vf.charCodeAt(cpi >> 3);
   var bit = (bits & (0x80 >> (cpi%8))) != 0;
   
   var vfbit = (bit == true) — 1 : 0;
   vfbits += vfbit;
   
   gPicklistMap[dpi][cpi] = vfbit;
  }
  
        if (debugMap) document.write(vfbits + "<br>");
 }
 
 //alert(gPicklistMap);
 
    if (debugMap) {
     document.write("</font><br><br>");
     document.write("gPicklistMap["+gdpValues.length+"]["+gcpValues.length+"]=<br>");
  document.write(gPicklistMap + "<br>");
    }
}

 
hamayoun65hamayoun65
Hi Zak

Thnaks a lot, I don't know how I would have figured that out with you!  One slight correction, in bold below.

   for (var j = 0; j < ctrl.picklistValues.length; j++) {
          var bits = vf.charCodeAt(j >> 3);
          if ((bits & (0x80 >> (j%8))) != 0)
            ln += "x ";
          else
            ln += "  ";
    }