1

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

null
    • Alain_Fontaine
    • 3 yrs ago
    • Reported - view

    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.

    • Kaan_Dikmen
    • 3 yrs ago
    • Reported - view

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

    • Alain_Fontaine
    • 3 yrs ago
    • Reported - view

    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.

    • Kaan_Dikmen
    • 3 yrs ago
    • Reported - view

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

    • Kaan_Dikmen
    • 3 yrs ago
    • Reported - view

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

    • Sean
    • 3 yrs ago
    • Reported - view

    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 choices

    MC := range(2, N, 2) - Sets the even numbered choices

     

    The Dynamic Multiple Choice field will not take an array so I think they still have some work to do.

    • Alain_Fontaine
    • 3 yrs ago
    • Reported - view

    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.

    • Kaan_Dikmen
    • 3 yrs ago
    • Reported - view

    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

    • CISOFT_Sarl
    • 3 yrs ago
    • Reported - view

    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.

    • Kaan_Dikmen
    • 3 yrs ago
    • Reported - view

    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.

    • CISOFT_Sarl
    • 3 yrs ago
    • Reported - view

    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.

    • Kaan_Dikmen
    • 3 yrs ago
    • Reported - view

    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?

    • Ninox developper
    • Jacques_TUR
    • 3 yrs ago
    • Reported - view

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

    You must to be use like that :

    MultiChoici Copie d'écran

    Behind add button there is this code :

    Tarte := setMultiChoice(number(Fruits), string(Tarte), "add")

    • Ninox developper
    • Jacques_TUR
    • 3 yrs ago
    • Reported - view

    It same way with DynamicMultiChoice :

    DynamicMultiChoici Copie d'écran

    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 »

    • Ninox developper
    • Jacques_TUR
    • 3 yrs ago
    • Reported - view

    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";

      • UKenGB
      • 2 yrs ago
      • Reported - view

      Jacques TUR Brilliant. Works great. Thank you for your excellent contributions.

Content aside

  • 1 Likes
  • 2 yrs agoLast active
  • 16Replies
  • 5121Views
  • 1 Following