Repeater

From Let's Role
Jump to navigation Jump to search

A repeater allows you to setup a dynamic list of view. Users can add or remove entries. On a view, a repeater component is like a container that will contain one “readable view” for each entry it contains. The “editable view” is initiated and show only to edit or add a new repeated entry to the repeater.

Editable view

The view used to edit the data. This view is initiated when you start to edit or add an entry to the repeater. The ids of the components used in this view will create data that you can use in the readable view using the # in front of their id. For example, if you have a TextInput with an id “Name”, you can refer this value in the readable view using a Label, with the Computed value #Name.

Readable view

The view used by default to display informations about the entry. Use references with # to refer to the components’ values used in the Editable view. If you need to parse the entries in a repeater, you can use the each utility on the value() of the repeater:

each(repeaterComponent.value(), function(entryValues){
  // do whatever you want with the data (values) of each entry
});

Note that entryValues object only contains the values of the input components (Checkbox, NumberInput, TextInput) used in the Editable view and the Readable View. It is not a component by itself, so you cannot use the component methods on it.

If you need to parse the components in a readable view of a repeater, you have to find the id of the entry in the repeater (which we call the unique random entry id). This is really simple because the id of each entry of a repeater is passed as the second argument when calling the each method.

You can then access the components by two methods:

1] The first is using the global id of the components which is build as this: repeaterId.uniqueRandomEntryId.readableViewComponentId. Note that this method does not allow you to access component from the editable view. These components (those of the editable view) are only in the sheet during editing. Since there is no event triggered during edition, those components are not accessible with this method. Here is a small example:

let repeaterComponent = sheet.get("repeaterComponentId");
each (repeaterComponent.value(), function(entryValues, uniqueRandomEntryId){
  let myReadableViewComponent = repeaterComponent.find(uniqueRandomEntryId).find("myReadableViewComponentId");
  // do whatever you want you want with the component (add an event handler, etc.)
});

2] The second method is using the find method of the components to get the components inside a repeater. To do so, you have to access first to the entry component, which is inside the repeater. This is not the first argument of the each method! Then, this entry component contains all the components used in the readable view AND in the editable view. Whith this method you can access to both components which might be usefull to configure the editable view (still, without any inti event triggered for the editable view, this can only be done at init of the main view for now). Here is a small example:

let repeaterComponent = sheet.get("repeaterComponentId");
each (repeaterComponent.value(), function(entryValues, uniqueRandomEntryId){
  let entryComponent = repeaterComponent.find(uniqueRandomEntryId); // look inside the repeater to find the entry container component
  let entrySubComponent = entryComponent.find(entrySubComponentId); // it can be "myLabel" for example; this is the same id that is used in the Lets Role editor; this can also be a reference to a component of the editor view of the repeater
  // do whatever you want you want with the component (add an event handler, etc.)
});

How to script init READABLE VIEW of a repeater

When Lets Role needs to show a repeater entry READABLE VIEW, it does not call the global init function. To alter the initialization of the READABLE VIEW of a repeater, you have to listen to the update event of the repeater. Inside this event, which is called each time an entry has been modified and or added, you have access to all the entries of the repeater using the previous code. Here is a small example where we would like to change an icon of the READABLE VIEW according to the choice (choice1) made in the EDITABLE VIEW:

init = function(sheet){
    if (sheet.id() == "main") {
        let repeater1Component = sheet.get("repeater1");              // <- A repeater component from the sheet
        repeater1Component.on("update", function (repeater) {         // <- When an update event occurs on "repeater1" component
            log("REPEATER UPDATE START: " + repeater.id());           // <- example output: "REPEATER UPDATE START: repeater1" 
            // Don't know which entry was changed, so update them all
            each(repeater.value(), function (entryValues, entryId) {  // <- example values: entryValues: { choice1: 'R1' } entryId: 'cxbsgdoo'
                // first, setup component references                     
                let entryComponent = repeater.find(entryId);          // <- This entry component in the repeater.
                let iconComponent = entryComponent.find("viewIcon");  // <- The "viewIcon" icon subcomponent within this entry VIEW component
                // second, some example logs
                log("   UPDATING REPEATER ENTRY: " + entryId);        // <- example output: "  UPDATING REPEATER ENTRY: cxbsgdoo" (unique)
                log("   ICON COMPONENT ID: " + iconComponent.id());   // <- example output: "  ICON COMPONENT ID: viewIcon"
                // third, the choice selection defines the icon 
                let iconName;                                         // <- We'll store our icon name in this variable
                switch (entryValues.choice1) {                        // <- get the value of the choice made in the EDITABLE VIEW
                    case "R1": iconName = "plus"; break;              // <- if the choice id is "R1", the icon will be "plus"
                    case "R2": iconName = "minus"; break;             // <- if the choice id is "R2", the icon will be "minus"
                    default: iconName = "ad"; break;                  // <- otherwise the icon will set as "ad"
                }
                // finally, set the new icon
                iconComponent.value(iconName);                        // <- change the icon of the component by setting its value
                log("   READABLE VIEW HAS BEEN UPDATED");             // <- example output: "  READABLE VIEW HAS BEEN UPDATED"
            });
            log("REPEATER UPDATE END: " + repeater.id());             // <- example output: "REPEATER UPDATE END: repeater1"
        });
    }

How to script init EDITABLE VIEW of a repeater

When Lets Role needs to show a repeater entry EDITABLE VIEW, it does not call the global init function. To alter the initialization of the EDITABLE VIEW of a repeater, you have to listen to the click event of the repeater. Inside this event, which is called each time a click is made on a repeater (Add button, Edit button, anynwhere inside the repeater), you have access to all the entries of the repeater using the previous code, including the new entry if you hit Add button. Be careful, as you can imagine, this event is called very often, and you need to initialized the EDITABLE VIEW only for the first time it is shown for new entry (most of the time anyway). To remember which entry has its EDITABLE VIEW intialized, you will have to memorize the ones you have already initialized. One way to do this is to create a global array that will contain the ids of the entries that have been already initialized, and check this array before initializing a new entry. Here is a small example where we would like to change the choices of a choice2 component in the EDITABLE VIEW according to the selected value of a choice1 component of this same EDITABLE VIEW:

// write your custom scripts here
let globalRepeaterEntryInits=[];                                                // <- holds entryId of every initialized repeater EDIT VIEW

init = function (sheet) {
    if (sheet.id() == "main") {
        // example variables for understanding
        let repeater1Component = sheet.get("repeater1");                        // <- The repeater component obtained from the sheet
        let repeater1Id = repeater1Component.id()                               // <- The id of the repeater component, aka "repeater1"
        let repeater1Value = repeater1Component.value()                         // <- The value of the repeater, containing it's entry ids
        let numberEntries = repeater1Component.value().length                   // <- Number of entries equal to length of the value array
    
        repeater1Component.on("click", function (repeater) {                    // <- When a click event occurs on "repeater1"
            // on click events to the repeater1 component...
            log("REPEATER CLICK START: " + repeater.id());                      // <- example output: "REPEATER CLICK START: repeater1"
            // some example logs        
            log("REPEATER HAS " + repeater.value().length + " ENTRIES:");       // <- example output: "repeater1 HAS 2 ENTRIES:"
            log(Object.keys(repeater.value()));                                 // <- example output: "['zzsdfeao', 'gjebduis']"
            // Add the following for each entry on the repeater       
            each(repeater.value(), function (entryValues, entryId) {            // <- example values: entryValues: { choice1: 'apple' } entryId: 'zzsdfeao'
                if (!globalRepeaterEntryInits.includes(entryId)) {              // <- Only do this once, check if this EDITABLE VIEW has been initialized
                    log("   INITIALIZING " + entryId);                          // <- example output: "   INITIALIZING zzsdfeao"
                    // setup component references 
                    let entryComponent = repeater.find(entryId);                // <- This individual entry component in the repeater.
                    let fruitComponent = entryComponent.find("choice1");        // <- The "choice1" subcomponent of the EDITABLE VIEW of this entry
                    // change "choice2" choices based on "choice1" value    
                    fruitComponent.on("update", function (updatedComponent) {   // <- updatedComponent is the component object of "choice1" (see 2 lines above)
                        // on update events to the choice1 component...
                        log("   UPDATE EVENT: " + targetCmp.id());              // <- example output: "   UPDATE EVENT: choice1"
                        let sizeComponent = entryComponent.find("choice2");     // <- The "choice2" subcomponent of the EDITABLE VIEW of this specific entry
                        let fruitVal = updatedComponent.value();                // <- example value: 'apple' (the currently selected choice1 value)
                        // create an object of choices
                        let newChoices = {};                                    // <- We'll use this to collect the new choice options based on the selection
                        newChoices["large_" + fruitVal] = "Large " + fruitVal;  // <- example result: large_apple: "Large apple" (put into choicesObject)
                        newChoices["small_" + fruitVal] = "Small " + fruitVal;  // <- example result: small_apple: "Small apple" (put into choicesObject)
                        // set the new choices to the "choice2" component
                        sizeComponent.setChoices(newChoices);                   // <- Fill "choice2" choice component with setChoices via setChoices() method
                    });
                    // mark this entry as initialized
                    globalRepeaterEntryInits.push(entryId);                     // <- Add entryId to global variable because this EDIT VIEW is initialized
                    log("   EDITABLE VIEW has been initialized");               //   (Initializing avoids infinite "onUpdate" handlers for EVERY SINGLE CLICK)
                }
            });
            log("REPEATER CLICK END: " + repeater.id());                        // <- example output: "REPEATER CLICK END: repeater1"
        });
    }
};

If you are interested in a little more complex example, feel free to fork: https://alpha.lets-role.com/sy/jPN4xbM2mUnWcdmx