Introduction
They said I had to use the API and that Salesforce doesn’t provide a way to natively do it in Apex. I said no.
Welcome back my people. Once more I’ve encountered a situation which required quite a bit of elaborate effort to figure out.
Note: There is an optimized adjustment to some of the final code at the end of this article in my post Acquiring Dependent Picklists in Apex Contd.
The Background:
According to the API Docs for the DescribeSObjectResult, a PicklistEntry object has a validFor property which is not available in Apex, but is available through the API. To me, this implied that if one were to serialize a PicklistEntry to JSON, this validFor property should become available. If such a scenario held true, then a deserialization of that content into a custom type which provided a property for validFor should allow us to access it in Apex. That’s part one of the problem hypothetically solved.
The second problem is that Apex doesn’t make it simple to manipulate a byte array or a base64 string. However, most of the time, we never really need to manipulate a byte array. A proper understanding of decimal should suffice. For this reason, I reference the MSDN library for a base64 mapping table [ http://msdn.microsoft.com/en-us/library/cc422512.aspx ] which will act as a reference for what I want to do.
The Issue:
You have a controlling picklist C, with N values and a dependent picklist D with M values. Let n represent an index in N, and m represent an index in M, such that 0<=n<N and 0<=m<M.
For a value D[m] we need to know if it is associated with n. According the Salesforce API Docs, validFor is defined as:
A set of bits where each bit indicates a controlling value for which this PicklistEntry is valid.
The docs had some good sample code at the end which showed how to get dependent values for controlling ones. However, we need to concern ourselves only with picklists for this problem.
The Scene:
For example, a value of “Blue” in D may have a validFor string of “AgAA”. One must remember that the string is encoded in base64 (2^6). This implies that “AgAA” uses 6 bits for each character.
The Explanation/Algorithm:
Since we don’t have access to the bits, what we can do, is get the corresponding value for each character from a base64 map of string to integers. We can then shift each integer to it’s respective segment by defining the shift amount as “number of bits per segment” multiplied by “number of segments to shift”. Therefore, if we reference “AgAA”, the first element get’s shifted to the left, to begin the binary string, by 3 segments. The second element gets shifted by 2 segments, the third by 1, and the fourth by none. By themselves, they would look like
000000 000000 000000 000000,000000 000000 000000 100000,000000 000000 000000 000000,000000 000000 000000 000000
After shifting, they look like
000000 100000 000000 000000
Once we’ve attained individual values, we sum them together to get full decimal representation of the string. Now all we have to do is check the bit which corresponds to the index of the controlling value n. We must remember that the position we seek is in a 0 indexed (upper bound is 7) bit in a set of bytes (8 bits). So the target bit is defined as 7 – (n % 8). With this information, we will provide a means to “test the bit” for a given base64 encoded validFor string. We will call this the “testBit” method.
Once we have a means to test the bit, we will get the values of the Controlling picklist and the dependent picklist. For each dependent entry (D[m]), we will test it against a controlling picklist entry (C[n]) to see if it’s valid for that entry.
The Solution:
Let us create a class called Bitset. The purpose of this class is to provide a way to manipulate base64 values. The property AlphaNumCharCodes is just there for ASCII values (not really used, but I might as well have them since computing them alongside the base 64 doesn’t take away any time).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
public class Bitset{ public Map<String,Integer> AlphaNumCharCodes {get;set;} public Map<String, Integer> Base64CharCodes { get; set; } public Bitset(){ LoadCharCodes(); } //Method loads the char codes private void LoadCharCodes(){ AlphaNumCharCodes = new Map<String,Integer>{ 'A'=>65,'B'=>66,'C'=>67,'D'=>68,'E'=>69,'F'=>70,'G'=>71,'H'=>72,'I'=>73,'J'=>74, 'K'=>75,'L'=>76,'M'=>77,'N'=>78,'O'=>79,'P'=>80,'Q'=>81,'R'=>82,'S'=>83,'T'=>84, 'U'=>85,'V'=> 86,'W'=>87,'X'=>88,'Y'=>89,'Z'=>90 }; Base64CharCodes = new Map<String, Integer>(); //lower case Set<String> pUpperCase = AlphaNumCharCodes.keySet(); for(String pKey : pUpperCase){ //the difference between upper case and lower case is 32 AlphaNumCharCodes.put(pKey.toLowerCase(),AlphaNumCharCodes.get(pKey)+32); //Base 64 alpha starts from 0 (The ascii charcodes started from 65) Base64CharCodes.put(pKey,AlphaNumCharCodes.get(pKey) - 65); Base64CharCodes.put(pKey.toLowerCase(),AlphaNumCharCodes.get(pKey) - (65) + 26); } //numerics for (Integer i=0; i<=9; i++){ AlphaNumCharCodes.put(string.valueOf(i),i+48); //base 64 numeric starts from 52 Base64CharCodes.put(string.valueOf(i), i + 52); } } } |
Next we implement the testBit method I spoke about earlier. This method determines the bytes being used as the length of validFor (e.g. AgAA) multiplied by the number of bits per character divided by 8.
After that, we calculate the target bit as I stated earlier as 7 – (n % 8) (in salesforce, 7 – Math.Mod(n, 8)). Next, we calculate the target octet (now that we are seeing things in bytes) that has in the bit we want as
(bytesBeingUsed -1 ) – (n >> bytesBeingUsed). How do i know this? According to the Salesforce API Docs. Test bit is defined as:
1 |
return (data[n >> 3] & (0x80 >> n % 8)) != 0; |
A bit ambiguous, but that’s what I translated it out to be. That tells me that Salesforce depends on that bit combination to determine the dependency.
From here on out, I begin to shift the bits and calculate the full decimal value as I explained earlier up.
The final part is where the definition of validFor comes back into play. Once we find the bit to compare, we apply a bit mask to zero out all other bits, then we shift to the target bit and check for a value of true (1) or false (0).
The final definition of the Bitset class comes out to be
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
public class Bitset{ public Map<String,Integer> AlphaNumCharCodes {get;set;} public Map<String, Integer> Base64CharCodes { get; set; } public Bitset(){ LoadCharCodes(); } //Method loads the char codes private void LoadCharCodes(){ AlphaNumCharCodes = new Map<String,Integer>{ 'A'=>65,'B'=>66,'C'=>67,'D'=>68,'E'=>69,'F'=>70,'G'=>71,'H'=>72,'I'=>73,'J'=>74, 'K'=>75,'L'=>76,'M'=>77,'N'=>78,'O'=>79,'P'=>80,'Q'=>81,'R'=>82,'S'=>83,'T'=>84, 'U'=>85,'V'=> 86,'W'=>87,'X'=>88,'Y'=>89,'Z'=>90 }; Base64CharCodes = new Map<String, Integer>(); //lower case Set<String> pUpperCase = AlphaNumCharCodes.keySet(); for(String pKey : pUpperCase){ //the difference between upper case and lower case is 32 AlphaNumCharCodes.put(pKey.toLowerCase(),AlphaNumCharCodes.get(pKey)+32); //Base 64 alpha starts from 0 (The ascii charcodes started from 65) Base64CharCodes.put(pKey,AlphaNumCharCodes.get(pKey) - 65); Base64CharCodes.put(pKey.toLowerCase(),AlphaNumCharCodes.get(pKey) - (65) + 26); } //numerics for (Integer i=0; i<=9; i++){ AlphaNumCharCodes.put(string.valueOf(i),i+48); //base 64 numeric starts from 52 Base64CharCodes.put(string.valueOf(i), i + 52); } } public Boolean testBit(String pValidFor,Integer n){ //the list of bytes List<Integer> pBytes = new List<Integer>(); //multiply by 6 since base 64 uses 6 bits Integer bytesBeingUsed = (pValidFor.length() * 6)/8; //will be used to hold the full decimal value Integer pFullValue = 0; //must be more than 1 byte if (bytesBeingUsed <= 1) return false; //calculate the target bit for comparison Integer bit = 7 - (Math.mod(n,8)); //calculate the octet that has in the target bit Integer targetOctet = (bytesBeingUsed - 1) - (n >> bytesBeingUsed); //the number of bits to shift by until we find the bit to compare for true or false Integer shiftBits = (targetOctet * 8) + bit; //get the base64bytes for(Integer i=0;i<pValidFor.length();i++){ //get current character value pBytes.Add((Base64CharCodes.get((pValidFor.Substring(i, i+1))))); } //calculate the full decimal value for (Integer i = 0; i < pBytes.size(); i++) { Integer pShiftAmount = (pBytes.size()-(i+1))*6;//used to shift by a factor 6 bits to get the value pFullValue = pFullValue + (pBytes[i] << (pShiftAmount)); } //& is to set the same set of bits for testing //shift to the bit which will dictate true or false Integer tBitVal = ((Integer)(Math.Pow(2, shiftBits)) & pFullValue) >> shiftBits; return tBitVal == 1; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
/* * @Summary: Entity to represent a json version of a picklist entry * so that the validFor property becomes exposed */ public class TPicklistEntry{ public string active {get;set;} public string defaultValue {get;set;} public string label {get;set;} public string value {get;set;} public string validFor {get;set;} public TPicklistEntry(){ } } |
The Meat:
Now we finally apply the features we just added. There are a few things that you need to know first:
- How to prepare for an arbitrary string to SObjectType association: Schema.getGlobalDescribe() gives us a map of String to SObjectTypes.
- How to prepare for an arbitrary field to SObjectField association: For an entity of type Schema.SObjectType, we have access to <MyEntity>.getDescribe().fields.getMap();
- Both the classes, Bitset and TPicklistEntry, and the static method below GetDependentOptions, are defined in a class I called TStringUtils. This is not a required step, but it does help in understanding the code listed below.
My code is documented on a line by line level for readability purposes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
public static Map<String,List<String>> GetDependentOptions(String pObjName, String pControllingFieldName, String pDependentFieldName){ Map<String,List<String>> objResults = new Map<String,List<String>>(); //get the string to sobject global map Map<String,Schema.SObjectType> objGlobalMap = Schema.getGlobalDescribe(); if (!objGlobalMap.containsKey(pObjName)) return objResults; //get the type being dealt with Schema.SObjectType pType = objGlobalMap.get(pObjName); Map<String, Schema.SObjectField> objFieldMap = pType.getDescribe().fields.getMap(); //verify field names if (!objFieldMap.containsKey(pControllingFieldName) || !objFieldMap.containsKey(pDependentFieldName)) return objResults; //get the control values List<Schema.PicklistEntry> ctrl_ple = objFieldMap.get(pControllingFieldName).getDescribe().getPicklistValues(); //get the dependent values List<Schema.PicklistEntry> dep_ple = objFieldMap.get(pDependentFieldName).getDescribe().getPicklistValues(); //iterate through the values and get the ones valid for the controlling field name TStringUtils.Bitset objBitSet = new TStringUtils.Bitset(); //set up the results for(Integer pControllingIndex=0; pControllingIndex<ctrl_ple.size(); pControllingIndex++){ //get the pointer to the entry Schema.PicklistEntry ctrl_entry = ctrl_ple[pControllingIndex]; //get the label String pControllingLabel = ctrl_entry.getLabel(); //create the entry with the label objResults.put(pControllingLabel,new List<String>()); } //cater for null and empty objResults.put('',new List<String>()); objResults.put(null,new List<String>()); //check the dependent values for(Integer pDependentIndex=0; pDependentIndex<dep_ple.size(); pDependentIndex++){ //get the pointer to the dependent index Schema.PicklistEntry dep_entry = dep_ple[pDependentIndex]; //get the valid for String pEntryStructure = JSON.serialize(dep_entry); TStringUtils.TPicklistEntry objDepPLE = (TStringUtils.TPicklistEntry)JSON.deserialize(pEntryStructure, TStringUtils.TPicklistEntry.class); //if valid for is empty, skip if (objDepPLE.validFor==null || objDepPLE.validFor==''){ continue; } //iterate through the controlling values for(Integer pControllingIndex=0; pControllingIndex<ctrl_ple.size(); pControllingIndex++){ if (objBitSet.testBit(objDepPLE.validFor,pControllingIndex)){ //get the label String pControllingLabel = ctrl_ple[pControllingIndex].getLabel(); objResults.get(pControllingLabel).add(objDepPLE.label); } } } return objResults; } |
At the end of the day, there is a line between explaining what you did, and just plain out going to far. I think I crossed that line today. Oh well.
Note: There is an optimized adjustment to some of the final code in my post Acquiring Dependent Picklists in Apex Contd.
90 Comments on “Salesforce: Acquiring Dependent Picklists in Apex”
Dude, DUDE!!!! This is awesome. The only suggestion I have is to maybe change the verbiage here, “are defined in a static class I called TStringUtils”, and remove the static word…..Just nitpicking. YOUR CODE ROCKS!!! Thank you so much!
Thanks @Tommy. Always helps to have someone else see those little “unnecessary extras” in one’s post.
Mate, you the man!! Luckily I didn’t spend much time before I saw yours. Your code works, your comments and description make it looks simple. Thanks a lot!
Glad to know that it doesn’t just work on my machine green (always got to wonder about that)
Great job! Thank you very much. It helped me a lot. 🙂
Thanks very much. it helps me a lot.The code and comments both are very clearly. Thanks~
Guys, I added some optimizations you may be interested in. The link is at the bottom of the post, feel free to check it out!
Pingback: Acquiring Dependent Picklists In Apex Contd. | Tales of an earthling
It’s hard to find your posts in google. I found it on 22 spot, you should build quality
backlinks , it will help you to get more visitors.
I know how to help you, just type in google – k2 seo tips
This is exactly what I’m looking for. Very interesting to know how dependency picklist works , thanks a ton!.
You should be in mensa – i have no idea what any of your code actually does but i know it works. Thank you so much! Saved my butt…
Lol, I don’t think I’m ready for mensa yet, but I do appreciate the kind words and I’m glad it helped. I know what “needing that functionality” feeds like.
I see you share interesting stuff here, you
can earn some extra money, your website has big potential, for the monetizing method, just search in google – K2 advices how to monetize a website
Awesome! Where is the 75 % code coverage? Thanks!
Hey, nice post! It helped me a lot in understanding the calculation of dependencies of picklists. In one point though I think you might be wrong:
Note: I didn’t say for each controlling entry, test the dependent ones. The reason for this is that, statistically speaking, it is more likely that you have more Dependent values (M) than controlling ones (N). As a result, if I loop through the bigger list and compare it against a smaller list, I end up with a average run time of O(M^N) instead of O(N^M). Mathematically speaking, for large values, M^N <= N^M.
It's not O(M^N) or O(N^M), but O(M*N) instead, so actually it doesn't matter which one is the outer and which one the inner loop. Or in other words, for infinitely large picklists, complexity would still be O(n^2)).
Cheers,
Matthias
Thank you for catching that Matthias and it has been corrected. I don’t know what I was thinking at that time. Much appreciated!
Hey thanks for giving a such a wonderful information
Can you tell me how to execute the following scenario.
I have created field dependencies on countries and state picklist as such when we change the country the respective states should appear on the state picklist. So how can we achieve this using apex code
Love the code – but need help – I am only returning a portion of the picklist values. Does anyone know why? Thank you in advance.
Awesome Code.But something is wrong with new class although that is more fast . But older class is giving me perfect results. Please fix it. It giving me values of wrong values.
Really nice code and very helpful for me. But I am getting “Apex CPU time limit exceeded Class” error. I am having 250 picklist value for controlling fields and I think that’s an issue with it. Can you please help me to solve. Your help will be appreciated.
Thanks so much for posting this!
There’s one other type of “picklist dependency” in Salesforce that this approach unfortunately does not solve, and for which in my experience you still need the API for (and which is actually a much bigger problem), and that’s Record Type picklist value dependencies. Each Record Type is essentially a “super controlling field” for all picklist fields on an object. The problem is that this metadata is available through the API via the describeLayout() call, which there is no equivalent for in Apex at all. Which is why the following Ideas still so desperately need to be implemented by Salesforce:
https://success.salesforce.com/ideaView?id=08730000000BpHAAA0
https://success.salesforce.com/ideaView?id=08730000000l5SnAAI
Once they expose some kind of describeLayout in Apex, we’ll be free to JSON-round-trip-it it and expose its private variables! 🙂
This was super helpful. I’ve been writing a GUI in python to manage records in SF via API calls and I was able to transcribe this into some working Python 3 modules.
I’m using a Python lib called simple_salesforce as the ‘auth’ object for my API calls, but here’s your code transcribed for Py3, if anyone is interested:
”
# SF_PicklistParser.py
import copy
class Bitset():
def __init__(self):
self.AlphaNumCharCodes = {}
self.Base64CharCodes = {}
self.LoadCharCodes()
def LoadCharCodes(self):
self.AlphaNumCharCodes = {
‘A’:65,’B’:66,’C’:67,’D’:68,’E’:69,’F’:70,’G’:71,’H’:72,’I’:73,’J’:74,
‘K’:75,’L’:76,’M’:77,’N’:78,’O’:79,’P’:80,’Q’:81,’R’:82,’S’:83,’T’:84,
‘U’:85,’V’: 86,’W’:87,’X’:88,’Y’:89,’Z’:90
}
self.Base64CharCodes = {}
#lower case
pUpperCase = copy.deepcopy(self.AlphaNumCharCodes)
for pKey in pUpperCase:
#the difference between upper case and lower case is 32
self.AlphaNumCharCodes.update({pKey.lower(): self.AlphaNumCharCodes.get(pKey)+32})
#Base 64 alpha starts from 0 (The ascii charcodes started from 65)
self.Base64CharCodes.update({pKey: self.AlphaNumCharCodes.get(pKey) – 65})
self.Base64CharCodes.update({pKey.lower(): self.AlphaNumCharCodes.get(pKey) – (65) + 26})
#numerics
for i in range(10):
self.AlphaNumCharCodes.update({str(i): i+48})
#base 64 numeric starts from 52
self.Base64CharCodes.update({str(i): i+52})
def testBit(self, pValidFor, n):
#the list of bytes
pBytes = []
#multiply by 6 since base 64 uses 6 bits
bytesBeingUsed = (len(pValidFor) * 6) // 8
#will be used to hold the full decimal value
pFullValue = 0
#must be more than 1 byte
if (bytesBeingUsed > bytesBeingUsed)
#the number of bits to shift by until we find the bit to compare for true or false
shiftBits = (targetOctet * 8) + bit
#get the base64bytes
i = 0
while i < len(pValidFor):
#get current character value
pBytes.append((self.Base64CharCodes.get((pValidFor[i:(i+1)]))))
i += 1
#calculate the full decimal value
i = 0
while i < len(pBytes):
#used to shift by a factor 6 bits to get the value
pShiftAmount = (len(pBytes)-(i+1)) * 6
pFullValue = pFullValue + (pBytes[i] <> shiftBits;
return tBitVal
”
”
# BitOper.py
from BitOper import Bitset
import json
def GetDependentOptions(pControllingFieldName, pDependentFieldName, recordId, auth):
objResults = {}
#get the string to sobject global map
objGlobalMap = auth.case.describe_layout(recordId)
#get the type being dealt with
objGlobalMap = auth.case.describe_layout(recordId)
# Set values to none for validation
ctrl_ple = None
dep_ple = None
for item in objGlobalMap[‘detailLayoutSections’]:
for child in item[‘layoutRows’]:
for grand in child[‘layoutItems’]:
for comp in grand[‘layoutComponents’]:
#get the control values
if str(pControllingFieldName).lower() == str(comp[‘value’]).lower():
ctrl_ple = comp[‘details’][‘picklistValues’]
#get the dependent values
if str(pDependentFieldName).lower() == str(comp[‘value’]).lower():
dep_ple = comp[‘details’][‘picklistValues’]
#print(“Controlling picklist values: \n” + str(ctrl_ple) + “\n****************”)
#print(“Dependent picklist values: \n” + str(dep_ple) + “\n****************”)
#verify field names
if (ctrl_ple is None) or (dep_ple is None):
return objResults
#iterate through the values and get the ones valid for the controlling field name
objBitSet = Bitset()
pControllingIndex = 0
#set up the results
while pControllingIndex < len(ctrl_ple):
#get the pointer to the entry
ctrl_entry = ctrl_ple[pControllingIndex]
#get the label
pControllingLabel = ctrl_entry['label']
#create the entry with the label
objResults.update({pControllingLabel: []})
pControllingIndex += 1
#check the dependent values
pDependentIndex = 0
while pDependentIndex < len(dep_ple):
#get the pointer to the dependent index
dep_entry = dep_ple[pDependentIndex];
#get the valid for
if dep_entry is None:
pDependentIndex += 1
continue
pEntryStructure = json.dumps(dep_entry)
objDepPLE = json.loads(pEntryStructure)
#if valid for is empty, skip
if (objDepPLE['validFor'] is None) or (objDepPLE['validFor'] == ''):
pDependentIndex += 1
continue
#iterate through the controlling values
pControllingIndex = 0
while pControllingIndex >> auth = Salesforce(username=creds, password=creds,security_token=creds)
>>> import SF_PicklistParser as pl_parser
>>> pl_parser.GetDependentOptions(‘Status’, ‘Sub_Status__c’, ‘01280000000cYyhAAE’, auth)
{‘New’: [‘Customer Requests Close’, ‘Inbound message received’, ‘Unassigned’], ‘Pending’: [‘Customer Requests Close’, ‘Inbound message received’, ‘Pro
blem Report’, ‘Waiting on Partner’], ‘Closed’: [‘By customer’, ‘Contact not on Account’, ‘Created in error’, ‘Duplicate’, ‘Inbound message received’,
‘Issue not Technical’, ‘No Entitlement’, ‘No response from customer’, ‘Resolved’, ‘Unsupported configuration’], ‘Open’: [‘Assigned’, ‘Close in 24 hour
s – No cust Resp’, ‘Close in 24 hours – Resolved’, ‘Close in 72 hours – No Cust Resp’, ‘Close in 72 hours – Resolved’, ‘Customer Requests Close’, ‘Cus
tomer To Confirm Fix’, ‘Escalation Requested’, ‘Inbound message received’, ‘No Response’, ‘On Hold Per Customer’, ‘Unassigned’, ‘Waiting on Customer’,
‘Waiting on Escalation Engineer’, ‘Waiting on OPS’, ‘Waiting on Partner’, ‘Wait on Engineering’, ‘Working’]}
”
Thanks again for your work on this. It was extremely helpful and got me through a challenging obstacle in my development!
Cheers!
It’s really a shame all of the formatting disappeared. Let me know if you’d like a copy and I can send it along.
Very helpful stuff, it saved my day..!
Great Job dude.
Hello,
Is it possible to provide some example how to use these classes?
Suppose I have controlling list P1 (A1, B1, C1) and dependable list P2 (A2, B2, C2).
How can I find what values of P2 belong to what values in P1 ?
Thanks!
Hi Nethaneel,
Thank you very much for your work on this. It was extremely helpful and saved my time 🙂
Thanks you so much!!!! it was very very helpful!
Great!
But I’m newbie and having difficulties how to pass parameters to these classes. could you explain please?
I have try to apply to the compound field of address, i.e. the “CountryCode” and “StateCode “. And it doesn’t work. I have use “user”/”Contact(MailingCountryCode and MailingStateCode)”/”Account(ShippingCountryCode and ShippingStateCode)” for testing but failed.
So for the compound address fields, how can we get the dependency values of state?
Hi,
I took your code and trying to run on one of my object and I am receiving NULL reference error in testBits method.
Line: pFullValue = pFullValue + (pBytes[i] << (pShiftAmount));
Can you please help me?
Thanks
Hari
Hi,
I did further debugging and found below:
My picklists are Product and Version (Product is controlling field for Version). I have Version value as 10.2.2 and converted to PicklistWrapper using below statement:
objJsonEntries = (List)JSON.deserialize(JSON.serialize(objEntries), List.class);
After converting:
PicklistEntryWrapper:[active=true, defaultValue=false, label=10.2.2, validFor=hKv4H8C/8iEQAsh8, value=10.2.2]
The “validFor” has “/” and the BitSet class is returning “null” for “/” at pBytes[i] in my previous comment.
Line: pFullValue = pFullValue + (pBytes[i] << (pShiftAmount));
Debug log for
14:32:45.051 (2051450466)|USER_DEBUG|[44]|DEBUG|********** pValidFor char: h *** base 64 code: 33
14:32:45.051 (2051580717)|USER_DEBUG|[44]|DEBUG|********** pValidFor char: K *** base 64 code: 10
14:32:45.051 (2051705340)|USER_DEBUG|[44]|DEBUG|********** pValidFor char: v *** base 64 code: 47
14:32:45.051 (2051829637)|USER_DEBUG|[44]|DEBUG|********** pValidFor char: 4 *** base 64 code: 56
14:32:45.051 (2051952643)|USER_DEBUG|[44]|DEBUG|********** pValidFor char: H *** base 64 code: 7
14:32:45.052 (2052075412)|USER_DEBUG|[44]|DEBUG|********** pValidFor char: 8 *** base 64 code: 60
14:32:45.052 (2052198921)|USER_DEBUG|[44]|DEBUG|********** pValidFor char: C *** base 64 code: 2
14:32:45.052 (2052320420)|USER_DEBUG|[44]|DEBUG|********** pValidFor char: / *** base 64 code: null
14:32:45.052 (2052442088)|USER_DEBUG|[44]|DEBUG|********** pValidFor char: 8 *** base 64 code: 60
14:32:45.052 (2052567420)|USER_DEBUG|[44]|DEBUG|********** pValidFor char: i *** base 64 code: 34
14:32:45.052 (2052692673)|USER_DEBUG|[44]|DEBUG|********** pValidFor char: E *** base 64 code: 4
14:32:45.052 (2052816002)|USER_DEBUG|[44]|DEBUG|********** pValidFor char: Q *** base 64 code: 16
14:32:45.052 (2052939342)|USER_DEBUG|[44]|DEBUG|********** pValidFor char: A *** base 64 code: 0
14:32:45.053 (2053061557)|USER_DEBUG|[44]|DEBUG|********** pValidFor char: s *** base 64 code: 44
14:32:45.053 (2053185562)|USER_DEBUG|[44]|DEBUG|********** pValidFor char: h *** base 64 code: 33
14:32:45.053 (2053308526)|USER_DEBUG|[44]|DEBUG|********** pValidFor char: 8 *** base 64 code: 60
Anyone has any idea why this null exception? I tried to skip if the value is "null", then I got wrong mappings for depedent picklist.
Thanks in advance.
Hari
I have the same issue as Hari.
Having “/” in “validFor ” is very common and this always make the dependent fields wrong.
Anyone has work around or enhancement?
Hi,
I have the same issue as Hari.
Having “/” in “validFor ” is very common and this always make the dependent fields wrong.
Anyone has work around or enhancement?
Add these lines at the end of the BitSet.LoadCharCodes
Base64CharCodes.put(‘+’, 62);
Base64CharCodes.put(‘/’, 63);
Hi,
I have the same issue as Hari.
Also, solutions doesn’t work correctly for dependent Picklist3: PickList1 -> PickList2 -> PickList3.
The problem with only seeing some of the dependent entries is in the alphaNumCharCodes where it is throwing an exception because the pValidFor starts with a char not in the Map.
The following snippet fixed the issue for me.
alphaNumCharCodes = new Map
{
‘!’=>33,’\”‘=>34,’#’=>35,’$’=>36,’%’=>37,’&’=>38,’\”=>39,'(‘=>40,’)’=>41,’*’=>42,’+’=>43,’,’=>44,’-‘=>45,
‘.’=>46,’/’=>47,’0’=>48,’1’=>49,’2’=>50,’3’=>51,’4’=>52,’5’=>53,’6’=>54,’7’=>55,
‘8’=>56,’9’=>57,’:’=>58,’;’=>59,’60,’=’=>61,’>’=>62,’?’=>63,’@’=>64,
‘A’=>65,’B’=>66,’C’=>67,’D’=>68,’E’=>69,’F’=>70,’G’=>71,’H’=>72,’I’=>73,’J’=>74,
‘K’=>75,’L’=>76,’M’=>77,’N’=>78,’O’=>79,’P’=>80,’Q’=>81,’R’=>82,’S’=>83,’T’=>84,
‘U’=>85,’V’=> 86,’W’=>87,’X’=>88,’Y’=>89,’Z’=>90
};
Hi, Bob! Thanks for the code, it works great. I was wondering, did you also write a test class for this? I can’t figure out how to check in a test class if the method returns the right values. I’m not entirely sure it’s even possible 🙂
Oops, you’re not Bob 🙂 sorry, not sure how I came to that conclusion.
Thank you VERY much. This was the starting point of solving a pain point for me. The only issue I had with it was that I was trying to deal with a VERY large dataset – the Countries and States picklists. There was so much data that the CPU limits were being reached. However, I only actually needed to know if a given country HAD dependent values, not what they actually were, so I thought I might be able to refactor the code for my purposes. I did, and then realized that my refactoring could also be rolled back into the original code, to allow it to run against a much larger dataset. Rather than looping over the parent records for each possible child, it parses the entire validFor string into an array of valid parent indexes, and so it builds the list much more quicky.
Here are my changes:
A new method in the “Bitset” class:
// Returns a list of all parent indexes for which this dependent entry is valid
public List validFor(String pValidFor) {
// Target list
List ret = new List();
// Start at offset 0
Integer target = 0;
// Process each character in order
for(Integer i=0; i < pValidFor.length();i++){
//get current character value
Integer cur = (Base64CharCodes.get((pValidFor.Substring(i, i+1))));
// 6 bits per character, starting at bit position 5
for (Integer j = 0; j < 6; j++) {
// Is the current bit set?
if ((cur & 32) == 32) {
// Yes, this index is valid
ret.add(target);
}
// Shift the next bit into position
cur <<= 1;
// Move to the next target index
target++;
}
}
return ret;
}
The "testBit" function is no longer used.
The refactored GetDependentOptions function:
// Build a map of parents and their valid children (faster version)
public static Map<String,List> GetDependentOptions(String pObjName, String pControllingFieldName, String pDependentFieldName){
Map<String,List> objResults = new Map<String,List>();
// This list will hold the children during the build, and will be copied to the final map at the end
List<List> workingList = new List<List>();
//get the string to sobject global map
Map objGlobalMap = Schema.getGlobalDescribe();
List counts = new List();
if (!objGlobalMap.containsKey(pObjName))
return objResults;
//get the type being dealt with
Schema.SObjectType pType = objGlobalMap.get(pObjName);
Map objFieldMap = pType.getDescribe().fields.getMap();
//verify field names
if (!objFieldMap.containsKey(pControllingFieldName) || !objFieldMap.containsKey(pDependentFieldName))
return objResults;
//get the control values
List ctrl_ple = objFieldMap.get(pControllingFieldName).getDescribe().getPicklistValues();
//get the dependent values
List dep_ple = objFieldMap.get(pDependentFieldName).getDescribe().getPicklistValues();
//iterate through the values and get the ones valid for the controlling field name
Bitset objBitSet = new Bitset();
//set up the results
for(Integer pControllingIndex=0; pControllingIndex<ctrl_ple.size(); pControllingIndex++){
//get the pointer to the entry
// Schema.PicklistEntry ctrl_entry = ctrl_ple[pControllingIndex];
//get the label
// String pControllingLabel = ctrl_entry.getLabel();
//create the entry with the label
// objResults.put(pControllingLabel,new List());
workingList.add(new List());
}
//check the dependent values
for(Integer pDependentIndex=0; pDependentIndex<dep_ple.size(); pDependentIndex++){
//get the pointer to the dependent index
Schema.PicklistEntry dep_entry = dep_ple[pDependentIndex];
//get the valid for
String pEntryStructure = JSON.serialize(dep_entry);
TPicklistEntry objDepPLE = (TPicklistEntry)JSON.deserialize(pEntryStructure, TPicklistEntry.class);
//if valid for is empty, skip
if (objDepPLE.validFor==null || objDepPLE.validFor==''){
continue;
}
List valids = objBitSet.validFor(objDepPLE.validFor);
for(Integer v : valids) {
// As a safety valve, make sure we didn’t get an index out of range
if (v < workingList.size()) {
workingList[v].add(objDepPLE.label);
}
}
}
// Now populate the map
objResults.put('',new List());
objResults.put(null,new List());
for(Integer pControllingIndex = 0; pControllingIndex < ctrl_ple.size(); pControllingIndex++) {
String pControllingLabel = ctrl_ple[pControllingIndex].getLabel();
objResults.put(pControllingLabel, workingList[pControllingIndex]);
}
return objResults;
}
With these changes, the code can easily handle the countries and states picklists with no problem.
@Neel and @Dwayne – awesome, awesome work. I had to break out my Computer Science 101 notes to decode this validFor property, and ye really helped. Thank you!
He man,
I need to tell you this in Spanish. Eres un jefazo. It worked perfectly…
Very good solution. Just a little comment, Base 64 char also includes the character / and + too. Salesforce uses those character, then I needed to improved the code:
//Method loads the char codes
private void loadCharCodes(){
AlphaNumCharCodes = new Map{
‘A’=>65,’B’=>66,’C’=>67,’D’=>68,’E’=>69,’F’=>70,’G’=>71,’H’=>72,’I’=>73,’J’=>74,
‘K’=>75,’L’=>76,’M’=>77,’N’=>78,’O’=>79,’P’=>80,’Q’=>81,’R’=>82,’S’=>83,’T’=>84,
‘U’=>85,’V’=> 86,’W’=>87,’X’=>88,’Y’=>89,’Z’=>90
};
Base64CharCodes = new Map();
//lower case
Set pUpperCase = AlphaNumCharCodes.keySet();
for(String pKey : pUpperCase){
//the difference between upper case and lower case is 32
AlphaNumCharCodes.put(pKey.toLowerCase(),AlphaNumCharCodes.get(pKey)+32);
//Base 64 alpha starts from 0 (The ascii charcodes started from 65)
Base64CharCodes.put(pKey,AlphaNumCharCodes.get(pKey) – 65);
Base64CharCodes.put(pKey.toLowerCase(),AlphaNumCharCodes.get(pKey) – (65) + 26);
}
//numerics
for (Integer i=0; i<=9; i++){
AlphaNumCharCodes.put(string.valueOf(i),i+48);
//base 64 numeric starts from 52
Base64CharCodes.put(string.valueOf(i), i + 52);
}
//Symbols
Base64CharCodes.put('+', 62);
Base64CharCodes.put('/', 63);
}
Just wanted to say thanks! <3
Hi Bob, thanks for the solution – especially liked the explanation of the approach.
However, I have found that this implementation does not spot dependency for some picklist items. After researching this, it looked like an issue with converting the validFor into a byte value – overflow occurred, and it didn’t calculate correctly.
So I have managed to re-implement the BitsetChecker class according to the SF documentation (please see my answer on Stack Overflow for details – http://salesforce.stackexchange.com/a/164491/29123).
The solution is based on breaking the validFor value into bits by using the EncodingUtil.convertToHex(), and then breaking the received hex string into 2 character chunks – those reperesent the dependency bits. After that, we can applying the check of controlling picklist entry’s index only to the specific bit.
I am facing issue while copying the code and my dependent picklist values are not taking correctly 🙁
vary good post .can any one share test classes for the same
Awesome.!!
I’m not sure if you monitoring this any more but I wondering if length of the validFor affects the bit Shifting in
anyway. All the examples that I have seen validFor has been 4 characters long but One pick list in my org is returning 8 characters. This pick list is not returning the correct values when it’s the controlling pick list. I believe that the size of validFor is the reason why the algorithm is failing.
Michael,
ValidFor will always be some multiple of 4 characters long. 4 characters can encode 24 bits of information. So as soon as there are more than 24 controlling field values, validFor will become 8 characters. A controlling field can have up to 300 values, which would result in validFor being a string 52 characters long.
I recently posted a new solution for the dependent picklist value problem. It is a single self-contained method of about 30 lines of Apex. My post includes test code as well for 100% coverage with assertions.
Please check out my blog here: https://glyntalkssalesforce.blogspot.com/2018/08/dependent-picklist-values-in-apex.html
It works for arbitrarily large datasets, and it works even for Checkbox controlling fields.
I hope it solves your problem!
Hello,
How can we retrieve the country and state picklist in different languages?
Hey,
Your code is very helpful for my requirement. First thanks.
Your code is working for one level only. Not working extra one level.
Below I have given my code. If you have any idea give me the solution.
Thanks in Advance.
/******Apex Class*******/
public class TStringUtils {
@AuraEnabled
public static List getDependentOptions() {
/******Object Name******/
string pObjName = ‘InventoryItem__c’;
/*******Controlling Field******/
1. string conFieldName = ‘Inventory_Category_Mirror__c’;
/*******Dependent for 1st controlling field and Controlling for 3rd depenent field*******/
2. string conDepFieldName = ‘Inventory_Sub_Category_Mirror__c’;
/*******Dependent for 2nd controlling field***********/
3. string depFieldName = ‘Inventory_Sub_Category_Qualifier_Mirror__c’;
List pickListWrappers = new List();
picklistWrapper plWrapper = new picklistWrapper();
//get the string to sobject global map
Map objGlobalMap = Schema.getGlobalDescribe();
if (!objGlobalMap.containsKey(pObjName))
return pickListWrappers;
//get the type being dealt with
Schema.SObjectType pType = objGlobalMap.get(pObjName);
Map objFieldMap = pType.getDescribe().fields.getMap();
//verify field names
if (!objFieldMap.containsKey(conFieldName) || !objFieldMap.containsKey(conDepFieldName))
return pickListWrappers;
//get the control values
List ctrl_ple = objFieldMap.get(conFieldName).getDescribe().getPicklistValues();
//get the dependent & control values
List dep_ctrl_ple =
objFieldMap.get(conDepFieldName).getDescribe().getPicklistValues();
//get the dependent values
List dep_ple = objFieldMap.get(depFieldName).getDescribe().getPicklistValues();
//iterate through the values and get the ones valid for the controlling field name
TStringUtils.Bitset objBitSet = new TStringUtils.Bitset();
//set up the results for 1
for(Integer pControllingIndex=0; pControllingIndex<ctrl_ple.size(); pControllingIndex++) {
//get the pointer to the entry
Schema.PicklistEntry ctrl_entry = ctrl_ple[pControllingIndex];
//get the label
String pControllingLabel = ctrl_entry.getValue();
//create the entry with the label
plWrapper.firstSetResults.put(pControllingLabel,new List());
}
//cater for null and empty
plWrapper.firstSetResults.put(”,new List());
plWrapper.firstSetResults.put(null,new List());
//check the dependent values
//For first set
for(Integer pDependentIndex=0; pDependentIndex<dep_ctrl_ple.size(); pDependentIndex++) {
//get the pointer to the dependent index
Schema.PicklistEntry dep_entry = dep_ctrl_ple[pDependentIndex];
//get the valid for
String pEntryStructure = JSON.serialize(dep_entry);
TStringUtils.TPicklistEntry objDepPLE = (TStringUtils.TPicklistEntry)JSON.deserialize(pEntryStructure, TStringUtils.TPicklistEntry.class);
//if valid for is empty, skip
if (objDepPLE.validFor==null || objDepPLE.validFor==''){
continue;
}
//iterate through the controlling values
for(Integer pControllingIndex=0; pControllingIndex<ctrl_ple.size(); pControllingIndex++){
if (objBitSet.testBit(objDepPLE.validFor,pControllingIndex)){
String pControllingLabel = ctrl_ple[pControllingIndex].getValue();
plWrapper.firstSetResults.get(pControllingLabel).add(objDepPLE.Value);
}
}
}
//setup up the results for 2
for(Integer pControllingIndex=0; pControllingIndex<dep_ctrl_ple.size(); pControllingIndex++) {
//get the pointer to the entry
Schema.PicklistEntry ctrl_entry1 = dep_ctrl_ple[pControllingIndex];
//get the label
String pControllingLabel1 = ctrl_entry1.getValue();
//create the entry with the label
plWrapper.secondSetResults.put(pControllingLabel1,new List());
}
//cater for null and empty
plWrapper.secondSetResults.put(”,new List());
plWrapper.secondSetResults.put(null,new List());
//For second set
for(Integer pDependentIndex=0; pDependentIndex<dep_ple.size(); pDependentIndex++) {
//get the pointer to the dependent index
Schema.PicklistEntry dep_entry1 = dep_ple[pDependentIndex];
//get the valid for
String pEntryStructure1 = JSON.serialize(dep_entry1);
TStringUtils.TPicklistEntry objDepPLE1 = (TStringUtils.TPicklistEntry)JSON.deserialize(pEntryStructure1, TStringUtils.TPicklistEntry.class);
//if valid for is empty, skip
if(objDepPLE1.validFor==null || objDepPLE1.validFor==''){
continue;
}
//iterate through the controlling values
for(Integer pControllingIndex=0; pControllingIndex<dep_ctrl_ple.size(); pControllingIndex++){
if (objBitSet.testBit(objDepPLE1.validFor,pControllingIndex)) {
String pControllingLabel1 = dep_ctrl_ple[pControllingIndex].getValue();
plWrapper.secondSetResults.get(pControllingLabel1).add(objDepPLE1.Value);
}
}
}
pickListWrappers.add(plWrapper);
return pickListWrappers;
}
public class TPicklistEntry {
public string active {get;set;}
public string defaultValue {get;set;}
public string label {get;set;}
public string value {get;set;}
public string validFor {get;set;}
public TPicklistEntry() {}
}
public class Bitset {
public Map AlphaNumCharCodes {get;set;}
public Map Base64CharCodes {get; set; }
public Bitset(){
LoadCharCodes();
}
//Method loads the char codes
private void LoadCharCodes() {
AlphaNumCharCodes = new Map{
‘A’=>65,’B’=>66,’C’=>67,’D’=>68,’E’=>69,’F’=>70,’G’=>71,’H’=>72,’I’=>73,’J’=>74,
‘K’=>75,’L’=>76,’M’=>77,’N’=>78,’O’=>79,’P’=>80,’Q’=>81,’R’=>82,’S’=>83,’T’=>84,
‘U’=>85,’V’=> 86,’W’=>87,’X’=>88,’Y’=>89,’Z’=>90
};
Base64CharCodes = new Map();
//lower case
Set pUpperCase = AlphaNumCharCodes.keySet();
for(String pKey : pUpperCase){
//the difference between upper case and lower case is 32
AlphaNumCharCodes.put(pKey.toLowerCase(),AlphaNumCharCodes.get(pKey)+32);
//Base 64 alpha starts from 0 (The ascii charcodes started from 65)
Base64CharCodes.put(pKey,AlphaNumCharCodes.get(pKey) – 65);
Base64CharCodes.put(pKey.toLowerCase(),AlphaNumCharCodes.get(pKey) – (65) + 26);
}
//numerics
for(Integer i=0; i<=9; i++){
AlphaNumCharCodes.put(string.valueOf(i),i+48);
//base 64 numeric starts from 52
Base64CharCodes.put(string.valueOf(i), i + 52);
}
}
public Boolean testBit(String pValidFor,Integer n) {
//the list of bytes
List pBytes = new List();
//multiply by 6 since base 64 uses 6 bits
Integer bytesBeingUsed = (pValidFor.length() * 6)/8;
//will be used to hold the full decimal value
Integer pFullValue = 0;
//must be more than 1 byte
if(bytesBeingUsed > bytesBeingUsed);
//the number of bits to shift by until we find the bit to compare for true or false
Integer shiftBits = (targetOctet * 8) + bit;
//get the base64bytes
for(Integer i=0;i<pValidFor.length();i++){
//get current character value
pBytes.Add((Base64CharCodes.get((pValidFor.Substring(i, i+1)))));
}
//calculate the full decimal value
for(Integer i = 0; i < pBytes.size(); i++) {
Integer pShiftAmount = (pBytes.size()-(i+1))*6;//used to shift by a factor 6 bits to get the value
pFullValue = pFullValue + (pBytes[i] <> shiftBits;
return tBitVal == 1;
}
}
public class pickListWrapper {
@AuraEnabled
public Map<String,List> firstSetResults = new Map<String,List>();
@AuraEnabled
public Map<String,List> secondSetResults = new Map<String,List>();
}
}
Hi,
This code is failing for me for very large data set. CPU limits are reached. Any workaround?
Liza, I recently posted a new solution for the dependent picklist value problem. It is a single self-contained method of about 30 lines of Apex. My post includes test code as well for 100% coverage with assertions.
Please check out my blog here: https://glyntalkssalesforce.blogspot.com/2018/08/dependent-picklist-values-in-apex.html
It should work for arbitrarily large datasets, and work even for Checkbox controlling fields.
I hope it solves your problem!
Hi all,
Some time never gives the correct values from the controlling field may be failing due to validfor size. please help me to fix this algorithm to work for every values.
Sarvesh, I recently posted a new solution for the dependent picklist value problem. It is a single self-contained method of about 30 lines of Apex. My post includes test code as well for 100% coverage with assertions.
Please check out my blog here: https://glyntalkssalesforce.blogspot.com/2018/08/dependent-picklist-values-in-apex.html
It should work for arbitrarily large datasets, and work even for Checkbox controlling fields.
I hope it solves your problem!
anyone has the updated code, it is not working for large data set Please provide with the component details
Rahul, I recently posted a new solution for the dependent picklist value problem. It is a single self-contained method of about 30 lines of Apex. My post includes test code as well for 100% coverage with assertions.
Please check out my blog here: https://glyntalkssalesforce.blogspot.com/2018/08/dependent-picklist-values-in-apex.html
It should work for arbitrarily large datasets, and work even for Checkbox controlling fields.
I hope it solves your problem!
Readers,
I recently posted a new solution for the dependent picklist value problem. It is a single self-contained method of about 30 lines of Apex. My post includes test code as well for 100% coverage with assertions.
Please check out my blog here: https://glyntalkssalesforce.blogspot.com/2018/08/dependent-picklist-values-in-apex.html
It works for arbitrarily large datasets, and works even for Checkbox controlling fields.
I hope it solves your problem!
Do you want to become more popular on Instagram? I’d love to help.
Michael
Before you repair your windows that are broken Find a Window repair near me (cristianlquwy.ja-blog.com) repair shop near me.
Before you hire someone to fix your windows make sure they are licensed and insured.
Different states have different requirements for licensing.
In the Consultant psychiatrist uk, there is an increasing need for private psychiatry, but how do they work?
This article discusses the advantages of private psychiatry
and explains how it can help families and individuals.
It is easier to access.
The first agency for European sexual dolls
is located in Barcelona, Spain. The company boasts four working
girls, and boasts that its customers can’t tell the difference between the real woman and a sex doll.
There may be issues you are unsure about payday loans bad credit direct
lender (Gavin) loan lenders’ terms.
The rates of interest and repayment terms will depend
on the lender’s individual terms, not on the platform for payday loans.
There are a variety of rules and rules that govern online gambling.
For example, in the US there is no way to establish Gambling – Voiceemergent.Com – websites
that are located in your country of residence.
However, it could be established elsewhere.
Asian Japanese sex dolls have made sex more real and enjoyable for men and women.
They’re a cheap and convenient alternative to real life relationships.
They are able to fulfill Sexual Dolls fantasies and be faithful lovers.
Asian Japanese sex dolls make the sex experience
more Realistic Sexdolls and enjoyable for both men and women. They’re a cheap and
convenient alternative to real-life partners. They are able to fulfill sexual fantasies and be reliable lovers.
Poker players have plenty of new options due to the internet.
The popularity of online poker has risen dramatically in recent years.
Poker Online (Bcabba.org) is a great alternative if you love
the game but don’t have time to visit a casino.
Online slots are a favorite pastime for millions of players.
This fun and exciting game can be a great way for you
to enjoy yourself or earn extra money. There are many games to play
and even games that are free.
my homepage gambling, centex-Indicators.org,
Online gambling is governed numerous rules and regulations.
In the US for instance, you can’t base gambling websites in the country where you reside however, you
can base it elsewhere.
Feel free to visit my site: sports – Gracechurchofdunedin.com
–
There are many reasons why you should consider sports betting.
From the possibility of earning a profit to the excitement and the chance to
root for your favorite team and the overall experience.
My web page best – fifisofdebary.com,
online; thevaap.com, poker offers
many advantages. For instance, there are no waiting lists and no second-hand smoke and you can play at any time you want.
Also, you can enjoy the variety of games.
The Internet has opened up a variety of opportunities for those who enjoy playing poker.
The popularity of Online (Imperialmandarinmarblehead.Com) poker has increased dramatically
in recent years.
It is legal to play poker online, but be sure that you’re over the
age of. The majority of online poker sites require players to be at least 18 years old or at least the legal drinking age in your jurisdiction.
My site gambler; Tenmaswitch.com,
There are many advantages of playing poker Online – Jamirosite.Com -.
For instance, there are no waiting lists, no second-hand smoke,
and you can play any time you want. Also, you can enjoy a wide
variety of games.
Online gambling has become very popular in the last decade.
In 1996, there were only fifteen websites. By 1997, there were over 200.
According to Frost & Sullivan, online gambling had generated $830 million in revenue in 1998.
my site gaming (Nkwomen.org)
There are many different rules and regulations
that govern online gambling. In the US for instance you aren’t able to base
an online gambling (harrybuffalospainesville.com) website in the country you reside in however, you are able to base it
elsewhere.
Playing slots online is an extremely popular pastime
for millions of players at online casinos. This thrilling and enjoyable form of gambling can be a great way
for you to have fun or earn additional money.
my blog betting (poin888.com)
The Internet has created many opportunities for those who love
to play. Poker online has seen a rapid rise in popularity
in the last few years. Online poker (judibca.com)
is a fantastic option if you enjoy the game but don’t have time to
go to a casino.
It is legal to play Online (magnoliassalonandspa.com) poker however, you
must ensure that you’re legally able to play.
The majority of poker websites require players to be at
least 18 years of age, or at the minimum, the legal
drinking age in the jurisdiction you reside in.
Betting online offers bettors as well as bookmakers
many advantages. online, mulgannon.Com, betting sites allow people to bet
on their favorite sports and can also make money.
They offer a variety of deposit options.
There are a variety of treatments for addiction to gambling.
Some of them involve one-on one counseling, medication, and
lifestyle changes. If you’re unable to stop yourself from engaging
in this behaviour it can become an addiction.
my blog Slots (Sepengetahuan.Com)
Poker online is legal. However it is important to ensure that you are legal age.
poker (cardiscovery.Com) sites online require
that players are at least 18 years old in order to play.
Sports fans in New York are allowed to place Bets
(mitrajudi.Net)
online from Jan. 8, 2022. Four main operators have received licenses to
operate mobile betting, which will permit them to provide their services in various ways.
Online gambling has become extremely
popular over the last decade. In 1996, there were only fifteen websites.
By 1997, there were over 200. In 1998 the Frost & Sullivan report stated that online gambling generated $830 million in revenue.
There are many reasons why you should consider sports betting.
From the possibility of earning money, to the excitement and the opportunity to
support your team of choice and the overall Experience (Eastperryfair.Com).
The Internet has opened up a variety of new opportunities for
those who love poker. The popularity of online poker has
grown dramatically in recent years. Online poker is a fantastic option if you like the game but don’t have time to visit casinos.
my web site Best; Mtiproed.Com,
New Yorkers are now able to place online bets on sports since January 8 2022.
Four major operators have been granted licenses to mobile betting.
They will be able to offer their services in variety ways.
Fans of sports in New York are allowed to place bets online since Jan. 8th 2022.
Four major operators have been granted licenses to mobile Betting; theaspectratio.net,.
They can provide their services in a variety of ways.
Pingback: Get lists of dependent picklist options in Apex
This is amazing! Thanks!