Select to view content in your preferred language

Cascading Multi-selects: Only showing unique choices

1438
5
Jump to solution
08-27-2021 02:46 PM
AlfredBaldenweck
MVP Regular Contributor

Kind of a follow-up to this post

I have a list describing the contents of someone's closet. I'm taking inventory of this closet while I fill out this survey, but I really only care about what colors are represented in the assemblage. So I want to know what color shirts are in the closet, regardless of what style they are.

Example table (pivoted for readability)

Item TypeItem StyleColors possible in that Style
ShirtCollaredBlue (Navy), Blue (Pale), Green, Red
 HenleyBlue (Pale), Grey,  White
 T-ShirtGreen, Grey, Purple, Red
 Tank-topBlack, Blue (Navy), Grey, Green, Purple


If someone selects a Collared shirt  and Tank-top, the full set of choices will appear.

Item StyleColors possible
Collared, Tank-topBlue (Navy), Black, Blue (Pale), Blue (Navy), Green, Grey, Red, Green, Purple 

 (Survey123 puts the choices as A[0], B[0], A[1], B[1], etc...)

Since I only care about the color of his entire collection of shirts, how can I make a clean list of unique choices appear? 

Collared, Tank-topBlack, Blue (Navy), Blue (Pale), Green, Grey, Purple, Red


(If this were really what the survey was, I would cascade in reverse, starting with colors, then going to Styles, but that's not possible with my actual dataset.)

Thanks!

 

0 Kudos
1 Solution

Accepted Solutions
AlfredBaldenweck
MVP Regular Contributor

Okay, I have a working solution

The code to generate the if() statements is 

Spoiler
function choicenum(N) {
	var index_range = [];

	//Create an index range based on the count-selected in the question.
	for (var i = 1; i <= N; i++) {
		index_range.push(i);
    }
	// Create your filter statements
	var filterstr = []
	for (var y in index_range) {  
      	var midstr = []
      	midstr.push("if(count-selected(${Animal})=" + (Number(y)+1) + ", ");
      	
		// Create a sub-index for the second clause of the if()
		var subindex= []
      	for (var f = 0; f <= y; f++) {
			subindex.push(f);
   		
      		midstr.push("contains(Animal, selected-at(${Animal}, '" + f + "' )) or ")
        }
      	// Concatenates into a string, joined by a dummy character
  		var split_char= "?"
  		midstr = midstr.join(split_char);
  		// splits along the dummy character and stitches back together
  		midstr= midstr.split(split_char).join('');
  		// Removes the last "or" from the string
  		midstr = midstr.substring(0, midstr.length -4)
		
     	filterstr.push(midstr);
	}
  
	filterstr = filterstr + ", \"\"" +")".repeat(N)
	return filterstr
  
}

//Outcome for N=2
//if(count-selected(${Animal})=1, contains(Animal, selected-at(${Animal}, '0' )),
//if(count-selected(${Animal})=2, contains(Animal, selected-at(${Animal}, '0' )) 
//or contains(Animal, selected-at(${Animal}, '1' )), ""))

console.log(choicenum(2))

 It's not the ideal solution; originally I had created a script that worked dynamically. Unfortunately, S123 can't use formulas that are the result of a pulldata() or are the values in another question as filter expressions; it instead tries to filter based off the string of the expression.

 

View solution in original post

5 Replies
DougBrowning
MVP Esteemed Contributor

Have you looked at the allow duplicate choice option?  Not sure but it may filter for you.

https://community.esri.com/t5/arcgis-survey123-ideas/survey123connect-choices-duplicate-names-amp/id...

 

0 Kudos
AlfredBaldenweck
MVP Regular Contributor

Thanks for the response.

I tried setting it to "no" and leaving it blank, and both had the same result as leaving it as yes, which was the default.

I think that setting is supposed to be if you want your choice to show up under multiple filters, e.g. If the previous answer to the filter question was "Shirt", rather than having your choice show up after selecting multiple (e.g. "Shirt" and "Pants").

 

Another thing that might be adding a wrinkle to this is that Iʻm referencing an external csv for this question, so maybe that changes whether or not the setting applies?

0 Kudos
AlfredBaldenweck
MVP Regular Contributor

Update 9/7/2021

I'm having some limited success by placing the relevant items for each color into one cell, then using 

contains(Animal, selected-at(${Animal}, '0') or contains(Animal, selected-at(${Animal}, '1') or ...

Sorry, I had to change dummy data

The problem is that it won't kick in until the number of choices that are enumerated in this choice filter are selected. So if I go all the way up to selected index 3, nothing will filter until I have 4 choices selected.

I'm now trying to figure out how to make it not tied to the number of selections and could use some suggestions.

Thanks again!

Example photos:

Here is my choice list:

AlfredBaldenweck_3-1631059163384.png

With Index up to index 1, 
No Choices: 

AlfredBaldenweck_0-1631059036374.png

One Choice:

AlfredBaldenweck_1-1631059078952.png

Two Choices selected:

AlfredBaldenweck_2-1631059107749.png

 

 

0 Kudos
AlfredBaldenweck
MVP Regular Contributor

Update 9/8/2021

Good news! I have gotten it to work.

Bad news: It is extremely tedious to set up.

Using the data I shared yesterday, this code will work for all of the options. 

 

 

 

if(count-selected(${Animal})=1, contains(Animal, selected-at(${Animal}, '0')), 
if(count-selected(${Animal})=2, (contains(Animal, selected-at(${Animal}, '0')) or contains(Animal, selected-at(${Animal}, '1'))), 
if(count-selected(${Animal})=3, (contains(Animal, selected-at(${Animal}, '0')) or contains(Animal, selected-at(${Animal}, '1')) or contains(Animal, selected-at(${Animal}, '2'))), 
if(count-selected(${Animal})=4, (contains(Animal, selected-at(${Animal}, '0')) or contains(Animal, selected-at(${Animal}, '1')) or contains(Animal, selected-at(${Animal}, '2')) or contains(Animal, selected-at(${Animal}, '3'))), 
if(count-selected(${Animal})=5, (contains(Animal, selected-at(${Animal}, '0')) or contains(Animal, selected-at(${Animal}, '1')) or contains(Animal, selected-at(${Animal}, '2')) or contains(Animal, selected-at(${Animal}, '3')) or contains(Animal, selected-at(${Animal}, '4'))),"")))))

 

 

 

You can't actually put line breaks in the calculation field, or else it breaks the formula, so in real life it's even harder to read than this.

I'm hoping that there's a way to have a javascript script evaluate the number of selected choices and then build the filter statement from there. My real life data is upwards of 80 choices to deal with (even after paring it down with other filters first), so writing this out for that many possibilities is not ideal.

 

I have a rough idea of what do, but don't know how to code well enough to  make it happen.

  • Get the count of the selected in the question (X)
  • prepare statement S I just realized this isn't necessary
    • "if(count-selected(${Animal})=", X, ", "
  • create a range from 1 to X, inclusive
  • For  integer Y in that range, concatenate into one large string, U
    • "contains(Animal, selected-at(${Animal},'", (Y-1), "'))", " or""
  • Get rid of the last "or", replace it with 
  • "), "")"
  • Concatenate S, U  Return U

Specifically what I don't know how to do is creating a range, and then getting rid of the last "or". 

Thanks!

0 Kudos
AlfredBaldenweck
MVP Regular Contributor

Okay, I have a working solution

The code to generate the if() statements is 

Spoiler
function choicenum(N) {
	var index_range = [];

	//Create an index range based on the count-selected in the question.
	for (var i = 1; i <= N; i++) {
		index_range.push(i);
    }
	// Create your filter statements
	var filterstr = []
	for (var y in index_range) {  
      	var midstr = []
      	midstr.push("if(count-selected(${Animal})=" + (Number(y)+1) + ", ");
      	
		// Create a sub-index for the second clause of the if()
		var subindex= []
      	for (var f = 0; f <= y; f++) {
			subindex.push(f);
   		
      		midstr.push("contains(Animal, selected-at(${Animal}, '" + f + "' )) or ")
        }
      	// Concatenates into a string, joined by a dummy character
  		var split_char= "?"
  		midstr = midstr.join(split_char);
  		// splits along the dummy character and stitches back together
  		midstr= midstr.split(split_char).join('');
  		// Removes the last "or" from the string
  		midstr = midstr.substring(0, midstr.length -4)
		
     	filterstr.push(midstr);
	}
  
	filterstr = filterstr + ", \"\"" +")".repeat(N)
	return filterstr
  
}

//Outcome for N=2
//if(count-selected(${Animal})=1, contains(Animal, selected-at(${Animal}, '0' )),
//if(count-selected(${Animal})=2, contains(Animal, selected-at(${Animal}, '0' )) 
//or contains(Animal, selected-at(${Animal}, '1' )), ""))

console.log(choicenum(2))

 It's not the ideal solution; originally I had created a script that worked dynamically. Unfortunately, S123 can't use formulas that are the result of a pulldata() or are the values in another question as filter expressions; it instead tries to filter based off the string of the expression.