Dynamic Multiple Choice Field - How to Programmatically Assign
Hi - the latest Ninox update has lots of interesting new features to play with, great to see it launch.
Dynamic lists is something we've all been clamouring for. It has its niggles (why does it have to link to another table? Why not a global variable that returns a list, for instance?). Anyway, the Single Choice Dynamic Field is straightforward enough. You can programmatically assign a value based on the ID of the record in the table it links to. If I have a table with records: [{id: 1, Code: "US", Country: "United States"}, {id: 2, Code: "CN", Country: "China"}] then I can write a script that can find the ID of the record based on either Code or Country. I can then declare a Single Choice Dynamic field in another table with that ID, e.g.
let chinaID := first(select Countries[Code="CN"]).id
let allChineseCompanies := select Companies[countryString="CN"];
for company in allChineseCompanies do
company.countryDynamicSingleChoice := number(chinaID);
end;
However, I run into problems when I try the same for Multi Choice Dynamic fields... I played around and found there is no way of declaring countries, either by running a for loop and iterating over a set of country codes, or by passing an array. Strangely, just to experiment, I used the console to read the output of a Multi Choice Dynamic Field that had a couple of countries selected and the response was a long STRING of numbers... very strange. Then, I began declaring the Multi Choice Dynamic Field a random STRING of numbers I plonked into my keyboard and was astonished that it would randomly select a number from the list.
Will Ninox make it possible to programmatically assign Multi Choice Dynamic Field options via NX Scripts? Please???
Thanks!
K
16 replies
-
Is it really random? It seems that the internal representation of a dynamic multiple choice field (as seen with debugValueInfo) is the hexadecimal encoding of a string of bits, each bit corresponding to a choice, with the position of the bit, starting fron the right, being the numeric value of the Id of the corresponding record in the table providing the choices - ouch, that's quite a sentence.
You can access this value with the function string() or the function raw(). You can build a compatible string and set the field by assigning that value to it.
This is also valid for regular multiple choice fields, except that the positions of the bits correspond to the assigned numeric values of the choices. Strangely, it seems that the chosen() function is not usable with dynamic multiple choice fields.
-
Yes, I shouldn't have said random. I was able to replicate the selections of a Dynamic Multi Choice of one record in another by feeding in that string. It's clear something that's coded, but I couldn't begin to decode it. Plus, I'm linking dynamically to a table of 200+ records (every iso2 country!). And I also noticed that chosen() doesn't work on the Dynamic fields.
Thanks for pointing out the hexadecimal encoding. It's new territory for me but I'll have a crack at it.
Hopefully Ninox allow pushing an Array of IDs into a Dynamic Multi Choice field at some point...
-
More detailed information about the coding.
1- divide the Ids in groups of four: 1-4, 5-8, 9-12, etc. Note that if some records have been deleted, you will have holes in the Ids actually present, but the missing Ids must not be removed from the encoding.
2- encode each group as as single hexadecimal character. For the first group, Id 1 has the weight 1, Id 2 has the weight 2, Id 3 has the weight 4 and Id 4 has the weight 8. Sum the weights, repeat for each group.
3- concatenate the encoding of the groups from left to right. Note that the internal encoding of the groups is MSB first, while the order of the groups is least significant group first. So the global string is not a straight encoding of the binary array.
-
This is super helpful. I can now draft hex strings and get the expected results to show. Now on to the work to write a script that will do that for me programmatically!!
-
This is a script I've got working for me at the moment... it's a bit "hack-y" so I can't say how robust it will be but it's proven excellent for my use case. I have 1000+ records of companies with a Text Field that contains iso2 country codes for where offices are based, e.g. "CN, DE, NL, US". I was able to use this function to programmatically convert those to the correct DYNAMIC multiple choices, which link to a table called Countries, where each record has an iso2 country code and the corresponding country name.
If you choose to use this code, note you will have to tweak TABLE NAMES and FIELD NAMES, which I've had to hard code, as Ninox doesn't allow these to be passed into functions themselves. I'm sure someone is capable of producing something neater...
"// Accepts a stringified array of iso2 country codes and generates a hex string for passing into a dynamic multi choice field";
function generateHexStringCountries(stringifiedArray : text) do
let allCountries := (select Countries);
let splitArray := split(stringifiedArray, ",");
let splitArray := for i in splitArray do trim(i) end;
let choiceRecordIDs := for i in splitArray do
let choice := allCountries[Code = i];
let recordID := choice.Id;
recordID
end;
let orderedRecordIDs := for c in choiceRecordIDs order by number(Id) do
c
end;
let dictArray := for recordID in orderedRecordIDs do
"// rounds up to determine which positional group it's in";
let hexgroup := ceil(number(recordID) / 4);
"// checks remainder to determine its position within group";
let posString := number(recordID) % 4;
let weighting := switch posString do
case 1: 1
case 2: 2
case 3: 4case 0: 8
end;
let choicePosWeightDict := {
pos: number(hexgroup),
weight: weighting
};
choicePosWeightDict
end;
"// with length of hexstring determined, make an array returning a weight for each position along the hexstring";
let hexLength := max(for i in dictArray do
i.pos
end);
let hexArray := for item in range(1, number(hexLength) + 1) do
let weightValue := 0;
"// iterates through each dict and returns the sum of all weights that match pos";
last(for dict in dictArray do
if item = dict.pos then
weightValue := weightValue + number(dict.weight)
end;
let weightEncoded := switch weightValue do
case 10: "a"
case 11: "b"
case 12: "c"
case 13: "d"
case 14: "e"
case 15: "f"
default: text(weightValue)
end;
text(weightEncoded)
end)
end;
"// turn that array to a string and return";
let hexString := join(hexArray, "");
hexString
end -
This doesn't help with setting the values of the Dynamic Multiple Choice field, but you can get the chosen Id's using the numbers() function. This is still not documented in the list of commands and functions. If you haven't been invited to the Webinar DE 2021 team, I suggest you request an invitation because they have put together a language reference database - 0001_Ninox-Reference.
I think the Dynamic Multiple Choice field still needs some polishing. With a Multiple Choice field you can set the values using an array...
MC := [1,2,3]
MC := range(1, N, 2)
- Sets the odd numbered choicesMC := range(2, N, 2)
- Sets the even numbered choicesThe Dynamic Multiple Choice field will not take an array so I think they still have some work to do.
-
The fact that numbers() is accepted, but not chosen(), for dynamic multiple choice fields, does not make sense. Oversight?
Another peculiarity of the feature: after selecting some options, perform some action that changes the list of options. It IS dynamic, no? Now, the options that were selected, but have become invisible, are still selected, as one can verify with numbers(), string() or raw(). Is this to be expected? Should the invisible options be reset or not? Expect some discussions...
By the way, the same applies to dynamic (single) choice fields. Is is perhaps less problematic, since choosing one of the visible choices will obviously deselect the invisible selected choice.
-
Interesting, that is rather bizarre behaviour. Haven't tested it myself.
Inspired by Sean's post, I realised I should have separated the logic for 1) grabbing a list of record IDs from a table based on certain criteria and 2) passing an array of record IDs into a hexstring generator.
The code snippet pasted below, stored as a global variable, should permit the following usage:
populateDMC("1")
# returns "1"
populateDMC("1, 2, 3, 4")
# returns "f"
populateDMC("1, 2, 3, 4, 41, 42, 43, 44")
# returns "f000000000f"
DMC := populateDMC("1, 2")
is the same as
DMC := "3"
which will select records with ids 1 and 2 in your dynamic multi choice field
"// Accepts a stringified array of record id numbers and generates a hex string for passing into a dynamic multi choice field";
function populateDMC(stringifiedArray : text) do
let idArray := split(stringifiedArray, ",");
idArray := for i in idArray do
trim(i)
end;
let dictArray := for recordID in idArray do
"// rounds up to determine which positional group it's in";
let hexgroup := ceil(number(recordID) / 4);
"// checks remainder to determine its position within group";
let posString := number(recordID) % 4;
let weighting := switch posString do
case 1:
1
case 2:
2
case 3:
4
case 0:
8
end;
let choicePosWeightDict := {
pos: number(hexgroup),
weight: weighting
};
choicePosWeightDict
end;
"// with length of hexstring determined, make an array returning a weight for each position along the hexstring";
let hexLength := max(for i in dictArray do
i.pos
end);
let hexArray := for item in range(1, number(hexLength) + 1) do
let weightValue := 0;
"// iterates through each dict and returns the sum of all weights that match pos";
last(for dict in dictArray do
if item = dict.pos then
weightValue := weightValue + number(dict.weight)
end;
let weightEncoded := switch weightValue do
case 10:
"a"
case 11:
"b"
case 12:
"c"
case 13:
"d"
case 14:
"e"
case 15:
"f"
default:
text(weightValue)
end;
text(weightEncoded)
end)
end;
"// turn that array to a string and return";
let hexString := join(hexArray, "");
hexString
end
-
And how can do to find the selection? Because if i use numbers(DMC) it return me effectivly the selected item but he place at the beginin 1 or 2 and i dont understand what is.
Thanks in adanvance.
-
As I understand it, numbers(DMC) just returns an array with the IDs of the records in the table to which the DMC is linked. If you call DMC without numbers() just as it is, or call it with a raw() function, you'll see a hexadecimal representation of the multi choice selection, as explained by Alain.
-
if use numers(DMC) it return e.g. 1,2,12,15,18,19
the two first value 1 and 2 i dont undersntant what is but 12, 15, 18 and 19 is my selected items
Then how can understand whats is the firsts values.
-
Check the Table you are linking too... you may have deleted the records in that table with ID 1 and 2. as Alain noted a few posts ago, even if you delete those, they may still show as selected - even though there is nothing to select.
As a fix, you could attempt the following, using the code snippet I attached earlier as a global function.
myDMCField := populateDMC("12, 15, 18, 19")
That should get rid of 1 and 2?
-
This code could be help you to understantd en use codeing en décoding MultiChoice and DynamicMultiChoice flieds :
function applyBinaryOp(value : number,mask : number,op : text) do
var r := 0;
var n := 1;
var a := value;
var b := mask;
while a + b > 0 do
r := r + switch op do
case "add":
number(number(odd(a) or odd(b)) * n)
case "remove":
number(if odd(b) then 0 else number(odd(a)) * n end)
case "toggle":
number(if odd(b) then
number(not odd(a)) * n
else
number(odd(a)) * n
end)
end;
n := n * 2;
a := floor(a / 2);
b := floor(b / 2)
end
;
r
end;
function setMultiChoice(id : number,multi : text,op : text) do
var i := id - 1;
var hexa := "0123456789ABCDEF";
var t := length(multi);
var pos := floor(i / 4);
var mod := i % 4;
var s := multi;
while length(s) < pos + 1 do
s := s + "0"
end
;
debug("id : " + id + ", t : " + t + ", pos : " + pos + ", mod : " + mod);
var val := index(hexa, item(s, pos));
var maskId := pow(2, mod);
val := applyBinaryOp(val, maskId, op);
substr(s, 0, pos) + item(hexa, val) + substr(s, pos + 1, length(s))
endYou must to be use like that :
Behind add button there is this code :
Tarte := setMultiChoice(number(Fruits), string(Tarte), "add")
-
It same way with DynamicMultiChoice :
The code behind add button is :
'Sale Team' := setMultiChoice(number(Customer), string('Sale Team'), "add")
For remove and toggle button, juste replace « add » parameter by « remove » or « toggle »
-
I found one error when value is "a" to "f" beacaus hex array contain uppercase char and the value come with lowercase char. You must to replace hexa array by this in the code : var hexa := "0123456789abcdef";
Content aside
-
1
Likes
- 2 yrs agoLast active
- 16Replies
- 5121Views
-
1
Following