Dynamiczne repeating section w Nintex Forms – jak to zrobić
Table of Contents
W swoim ostatnim projekcie musiałem stworzyć dynamiczną listę osób zatwierdzających w procesie, wybranych na podstawie lokalizacji i kwoty oraz kilku innych parametrach, jednak to nie ma znaczenia teraz. Na początku pomyślałem naturalnie, by stworzyć listę SharePoint, która będzie przechowywać odpowiednie mapowania. Następnie pomyślałem o akcji w przepływie pracy, która po prostu odpyta tę listę i korzystając z filtrowania uzyska listę tylko tych rekordów, które faktycznie reprezentować będą zatwierdzających dla danego procesu, którym następnie proces przypisze zadania.
Ale był haczyk 🙂 Klient oczekiwał również, że formularz będzie pokazywać listę tych dynamicznie zebranych zatwierdzających i w miarę postępów w procesie, będzie zaznaczać jak każdy z nich zatwierdzał. I również z możliwością ręcznego dodania lub usunięcia osoby z tak wygenerowanej listy!
[tds_note] Potrzebujesz pomocy w modyfikacji swoich formularzy Nintex? Skontaktuj się ze mną![/tds_note]
Pomyślałem więc, by użyć kontrolkę o nazwie Repeating Section, ale nie miałem pojęcia, w jaki sposób zasilić ją danymi dynamicznie. W końcu jednak to osiągnąłem i efekt wygląda jak poniżej:
Zaś lista źródłowa, z której pochodzą dane, wygląda następująco:
Jak zrobić taką dynamiczną repeating section?
Krok po kroku. Zaczynamy!
Struktura danych
Opisywana w tym poście aplikacja używa poniższej struktury danych (listy SharePoint):
- Locations (prosta lista, tylko z polem Tytuł)
- ApprovalThresholds – a lista zbudowana z poniższych pól:
- Title
- Approver (pole typu osoba, możliwy wielokrotny wybór)
- Location (lookup do listy Locations)
- Threshold (numer)
- OrderNo (numer, w moim wypadku chodzi o to, by umieć zidentyfikować do jakiej grupy zatwierdzających należy dany użytkownik)
- WorkingList (główna lista, na której tworzony będzie formularz)
- Title
- Location (lookup do listy Locations)
- Volume (numer)
- Approvers (pole tekstowe, wielo-linijkowe. Ważne! Musi to być typ „plain text”!)
Formularz
Stworzyłem prosty formularz dla listy WorkingList. Z pewnymi zmianami oczywiście:
- Volume – pole otrzymało zmienną JavaScript o nazwie: var_Volume;
- Location pole otrzymało zmienną JavaScript o nazwie: var_Location;
- Potem usunąłem domyślne pole „Approvers” i zastąpiłem je kontrolką repeating section:
Polu dodałem klasę CSS „approvers”:
[tds_info] To jest tutaj kluczowe. Nazwa klasy jest jedynym sposobem dla skryptu, by odnaleźć tę kontrolkę w kodzie formularza i następnie dostać się do jej zawartości.[/tds_info]
Kontrolka jest zbudowana z takich pól:
- Approver name – pole otrzymało klasę CSS: approversApprover
- Approver email – pole otrzymało klasę CSS: approversApproverEmail
- Approval group – pole otrzymało klasę CSS: approversOrderNo
Dodałem także dwa pola typu checkbox, jedno posiada zmienną JavaScript o nazwie var_IsEditMode, drugie var_IsNewMode. Są używane, by przekazać do skryptu informację, w jakim trybie otwarty jest formularz. Oba mają ustawiony parametr „Default value” na „Expression”:
I w zasadzie to wszystko, jeśli chodzi o formularz. Całą magię robi kod jQuery.
Skrypt
Skrypt odpowiada za wykonanie poniższych rzeczy:
- Wiąże listenery dla zdarzeń „change” i „blur” na polach Volume i Location;
- Definiuje funkcję, która jest uruchamiana w przypadku wystąpienia zdarzeń;
- Dodaje obsługę repeating section w trybie edycji formularza;
- Pozwala także na ukrywanie (lub pozostawianie nietkniętych) kontrolek dla obsługi repeating section – usuwanie wierszy, dodawanie wierszy (zmienna hideNativeRepeatingSectionControlls)
AD. 1 – wiązanie listenerów
var clientContext = new SP.ClientContext(); var siteurl = _spPageContextInfo.webAbsoluteUrl; var hideNativeRepeatingSectionControlls = 0; NWF$(document).ready(function () { //hide "add row" link in repeating section if (hideNativeRepeatingSectionControlls) NWF$(".approvers").find('.nf-repeater-addrow').css("visibility", "hidden"); //trigger if location, CapitalExp or Volume is changed - recalculate list of Approvers NWF$("#" + var_Location).change(function () { retrieveApprovers(); }); NWF$("#" + var_Volume).blur(function () { retrieveApprovers(); }); if (NWF$("#" + var_IsEditMode).prop("checked")) redrawRepeatingTableEditMode(); });
AD. 2- funkcja obsługująca zmiany na polach
function retrieveApprovers() { var oList = clientContext.get_web().get_lists().getByTitle('ApprovalThresholds'); var camlQuery = new SP.CamlQuery(); var locationArr = NWF$("#" + var_Location).val().split(";#"); var location = locationArr[1]; var locationCaml = '<Eq><FieldRef Name="Location" /><Value Type="LookupMulti">' + location + '</Value></Eq>'; var volume = NWF$("#" + var_Volume).val().replace(/[\,]/gi, ""); if (!volume) volume= 0; camlQuery.set_viewXml('<View><Query><Where><And>' + locationCaml + '<Leq><FieldRef Name="Threshold" /><Value Type="Number">' + volume+ '</Value></Leq>' + '</And></Where>' + '<OrderBy><FieldRef Name="Title"/><FieldRef Name="GroupOrderNo"/></OrderBy></Query></View>'); this.collListItem = oList.getItems(camlQuery); clientContext.load(collListItem); clientContext.executeQueryAsync( Function.createDelegate(this, this.onQuerySucceeded), Function.createDelegate(this, this.onQueryFailed) ); }
Funkcja zbiera informacje z pól Location i Volume, a następnie konstruuje z nich zapytanie CAML, które następnie wysyła do SharePoint w celu uzyskania listy zatwierdzających. W przypadku sukcesu, wykonuje funkcję „onQuerySucceeded”, która iteruje po znalezionych wierszach.
Następnie, dla każdego zatwierdzającego, w bieżącym wierszu (może być ich więcej niż jeden) woła endpoint SharePoint w celu pozyskania dodatkowych informacji o nim (email, login):
function onQuerySucceeded(sender, args) { // Redraw the existing table, remove everything what exists, leave fresh instance redrawRepeatingTable(); var listItemEnumerator = collListItem.getEnumerator(); while (listItemEnumerator.moveNext()) { var oListItem = listItemEnumerator.get_current(); var approvers = oListItem.get_item('Approvers'); var approvalOrder = oListItem.get_item('GroupOrderNo'); NWF$(approvers).each(function (idx, obj) { var person = JSON.stringify(obj); person = JSON.parse(person); // get user's display name and ID var approverId = person.$1T_1; var approverName = person.$4K_1; var userData = ""; // ask for users additional data NWF$.ajax({ url: siteurl + "/_api/web/getuserbyid(" + approverId + ")", method: "GET", async: false, headers: { "Accept": "application/json; odata=verbose" }, error: function (data) { console.log("Error: " + data); }
Po zebraniu komplety danych zapisuje je do pól w ostatnim, znalezionym wierszu Repeating Section (.nf-repeater-row:last) używając klasy ’.approvers’ (pamiętaj, to bardzo ważne!) jako selektor.
[tds_council] Jeśli zmienna „hideNativeRepeatingSectionControlls” funkcja usuwa także ikonkę „X” z przetwarzanego wiersza, dzięki czemu użytkownik nie będzie w stanie samodzielnie usunąć wygenerowanego wiersza.
Pamiętaj też, że nie możesz ustawić pól jako „disabled” lub ukryte korzystając z CSS lub reguł Nintex Forms. Z jakiegoś powodu elementy mające atrybut disabled lub styl „display:none” nie są brane pod uwagę podczas zapisu i wartości z nich nie trafiają do SharePoint.
By tego uniknąć używaj „visibility:hidden” (zamiast „display:none”) i pól obliczeniowych lub overlay by uczynić pole nieaktywnym.[/tds_council]
}).done(function (userData) { NWF$(".approvers .nf-repeater-row:last").find('.approversOrderNo input').val(approvalOrder); NWF$(".approvers .nf-repeater-row:last").find('.approversOrderNo input.nf-associated-control').attr("style", NWF$(".approvers .nf-repeater-row:last").find('.approversOrderNo input.nf-associated-control').attr("style") + "background-color: transparent !important;"); NWF$(".approvers .nf-repeater-row:last").find('.approversApproverEmail input').val(userData.d.Email); NWF$(".approvers .nf-repeater-row:last").find('.approversApprover input').val(approverName); // remove image for row deletion if (hideNativeRepeatingSectionControlls) NWF$(".approvers .nf-repeater-row:last").find('.nf-repeater-deleterow-image').css("visibility", "hidden"); // append overlay to avoid editting ;) fields must be enabled to allow proper save if (hideNativeRepeatingSectionControlls) NWF$(".approvers .nf-repeater-row:last").append('<div class="approverOverlay"></div>'); //add next row NWF$(".approvers").find('a').click(); }); }); }
Na koniec pętli usuwa ostatni, pusty wiersz, który zawsze pozostaje. Dodaje także, do każdego wygenerowanego wiersza, klasę „toRemoveOnReload”, dzięki czemu funkcja „redrawRepeatingTable()” będzie wiedzieć, które wiersze powinny zostać usunięte w przypadku konieczności przegenerowania zawartości kontrolki (np. z powodu zmiany wartości parametrów wejściowych).
// remove last, empty row, as it is always empty NWF$(".approvers .nf-repeater-row:last").find('.nf-repeater-deleterow-image').click(); // mark all additional rows as to be removed once control requires redraw: var addedRowsSuffixes = NWF$("input[name$='InternalRepeaterAddedRowSuffixes']").val().split(","); NWF$(addedRowsSuffixes).each(function (key, val) { NWF$("div[name='" + val + "undefined'").addClass("toRemoveOnReload"); }); return true; }
Funkcja redrawRepeatingTable() wygląda nastepująco:
//function used to delete existing rows in repeating table leaving it as new function redrawRepeatingTable() { //delete all existing repeating table rows, then build them again NWF$(".approvers .toRemoveOnReload").each(function () { NWF$(this).find('.nf-repeater-deleterow-image').click(); }); NWF$(".approvers .nf-repeater-row:last").find('.approversOrderNo input').val("").css("background-color", "rgb(248, 248, 248) !important"); NWF$(".approvers .nf-repeater-row:last").find('.approversApprover input').val(""); NWF$(".approvers .nf-repeater-row:last").find('.approversApproverEmail input').val(""); }
Ostatnią funkcją jaka jest używana w skrypcie, jest redrawRepeatingTableEditMode(). Istnieje w celu wstrzyknięcia sufiksów i klas „toRemoveOnReload” dla wierszy wygenerowanych w przypadku zbudowania zawartości repeating section w trybie edycji formularza:
//function used to enchance table of approvers created in the edit mode: function redrawRepeatingTableEditMode() { var suffix = 1; var suffixes = ""; NWF$(".approvers .nf-repeater-row").each(function () { NWF$(this).find('.nf-repeater-deleterow-image').css("visibility", "hidden"); // inject prefix into div's ID' if (suffix > 1) { NWF$(this).attr("id", suffix + "_" + NWF$(this).attr("id")); NWF$(this).attr("name", suffix + "_undefined"); suffixes += suffix + "_,"; } //increment prefix value suffix += 1; }); // inject suffixes into the dedicated field NWF$(".approvers").find('.nf-repeater-addeddrow-suffixes').val(suffixes.substr(0, suffixes.length - 1)); var addedRowsSuffixes = suffixes.split(","); NWF$(addedRowsSuffixes).each(function (key, val) { NWF$("div[name='" + val + "undefined'").addClass("toRemoveOnReload"); }); }
[tds_note] Zauważ, że Repeating Section trzyma suffiksy każdego, wygenerowanego wiersza w ukrytym polu o naywie „InternalRepeaterAddedRowSuffixes”. Wartość pola zbudowana jest wg wzoru: #_;#_;#_ gdzie # to kolejna cyfra naturalna, licząc od 1. Zauważ rónież, że każdy wiersz posiada nazwę, która zaczyna się właśnie od tegoż sufiksu i słowa „undefined”, toteż możliwe jest również użycie tych informacji w celu iteracji po wygenerowanych wierszach.
Co ciekawe, w trybie edycji formularza ukryte pole jest puste, a sufiksów nie ma, dlatego skrypt je wstrzykuje. [/tds_note]
Podsumowanie
Ten sposób tworzenia dynamicznych repeating section nie jest jedynie dedykowany dla zbierania informacji o zatwierdzających w procesie. Może byc użyty dla tworzenia dowolnych, dynamicznych zestawów danych. Na przykład dla pokazywania produktów dla określonej kategorii, czy części dla wybranych produktów. Ilość przypadków użycia zależy tylko od Ciebie 😉
Poniżej załączyłem wyeksportowany formularz (Nintex Forms 2016) oraz JavaScript. Powodzenia!
[wpdm_package id=’713′]