Using pulldata() to create the choice filter expression

438
3
Jump to solution
09-10-2021 11:29 AM
AlfredBaldenweck
Occasional Contributor III

I'm trying to create a dynamic choice filter, whose number of statements changes based on the number of choices selected in the previous question.

To do this, I've written a script in Javascript that builds the correct expression. I've tested the script, and it works as intended, however I can't seem to get the output to be used as the choice filter.  I have tried both in the choice filter, and having it calculate in another question and referencing that question.

Does anyone have any idea of how to do this?

Thanks!

In case someone wants to see the code:

 

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) {
		filterstr.push("contains(Animal, selected-at(${Animal}"  + ", '" + (y) + "')) or ");
	}
  
	// Concatenates into a string, joined by a dummy character
  	var split_char= "?"
  	var filterstr = filterstr.join(split_char);
  
	// splits along the dummy character and stitches back together
  	filterstr= filterstr.split(split_char).join('');
  
	// Removes the last "or" from the string
  	filterstr = filterstr.substring(0, filterstr.length -4)
  	return filterstr
	
}

 

 And the output for choicenum(3) will return as 

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

0 Kudos
1 Solution

Accepted Solutions
AlfredBaldenweck
Occasional Contributor III

Thanks for the response. 

This is a follow-up to this post Re: Cascading Multi-selects: Only showing unique c... - Esri Community

I can't post my survey here, but my real questions are looking at how historic land divisions overlay current tax parcels. In order to filter correctly, I did a spatial join on them, with an end result of 144k tax parcels that get filtered down by what choices are selected elsewhere. That being said, depending on the choices selected earlier in the survey, there can still be as many as 200 available to select at once.

I built this script to get around having to write out 200 increasingly more complex if statements. These if()s are  the only way I could get the duplicates created by the spatial join to only show up once, but the problem is that it's tied to how many choices are selected.

I guess the alternative to just have javascript create the choicelist manually, but that's going to be a lot harder to write than this script was.  I ended up modifying the script I wrote to generate the nasty if() statements so I don't have to. I'm just going to plug in 250 and then paste the output into the filter column. See below for the code.

It's definitely not as elegant as what I originally wanted to do, but the output functions as intended, so I can't complain too much.

 

I think this would be a good functionality to have in S123, though: being able to take formulas written as values in another question, if not a script output, and use them as filter rules, instead of just filter objects. 
Like you say, the choice-filter reads the formula as a string and searches for the string.

 

The code to mass-generate the if() statement. Plug it into any javascript playground and it'll get all those if() statements.

 

 

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))

 

 

View solution in original post

0 Kudos
3 Replies
DeonLengton
Esri Contributor

Hi Alfred

Do you mind attaching your Excel design file of your survey? It would help a lot to see how your choices are structured to see exactly what you're trying to achieve.

0 Kudos
DeonLengton
Esri Contributor

Hi Alfred

I had another look at what I think you are trying to achieve. I stand corrected but i dont think you can create an "expression" inside JavaScript and then have it execute as an expression in Survey123. I think it just returns the string/text that your function calculates.

There is a less elegant way to achieve what you're trying to do in Survey123 and it will highly depend on the number of choices that you have in your survey.

I've created a survey with a select-multiple question (animals) and then a second list that grows according to the items selected in the multiple select.

DeonLengton_0-1631517955643.png

I've seen before that null values can mess around with calculations so i first did some calculations to see which choices were selected in the multiple select:

DeonLengton_1-1631518104950.png

Basically if an option is not selected I just defaulted the calculation to the "notselected" string.

Next I did the filter calculation based on this expression:

contains(Animals,${c1}) or contains(Animals,${c2}) or contains(Animals,${c3}) or contains(Animals,${c4}) or contains(Animals,${c5}) or contains(Animals,${c6})

I hope this helps, although I could see that you were looking for a much more elegant solution!

I have attached my XLSX design file as well.

0 Kudos
AlfredBaldenweck
Occasional Contributor III

Thanks for the response. 

This is a follow-up to this post Re: Cascading Multi-selects: Only showing unique c... - Esri Community

I can't post my survey here, but my real questions are looking at how historic land divisions overlay current tax parcels. In order to filter correctly, I did a spatial join on them, with an end result of 144k tax parcels that get filtered down by what choices are selected elsewhere. That being said, depending on the choices selected earlier in the survey, there can still be as many as 200 available to select at once.

I built this script to get around having to write out 200 increasingly more complex if statements. These if()s are  the only way I could get the duplicates created by the spatial join to only show up once, but the problem is that it's tied to how many choices are selected.

I guess the alternative to just have javascript create the choicelist manually, but that's going to be a lot harder to write than this script was.  I ended up modifying the script I wrote to generate the nasty if() statements so I don't have to. I'm just going to plug in 250 and then paste the output into the filter column. See below for the code.

It's definitely not as elegant as what I originally wanted to do, but the output functions as intended, so I can't complain too much.

 

I think this would be a good functionality to have in S123, though: being able to take formulas written as values in another question, if not a script output, and use them as filter rules, instead of just filter objects. 
Like you say, the choice-filter reads the formula as a string and searches for the string.

 

The code to mass-generate the if() statement. Plug it into any javascript playground and it'll get all those if() statements.

 

 

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))

 

 

0 Kudos