From 339f2b9127b029df6fb738d585ad113e2a6edf53 Mon Sep 17 00:00:00 2001 From: madeliri Date: Sun, 29 Mar 2026 18:40:49 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=B2=D0=BC=D0=B5=D1=81=D1=82=D0=BE=20?= =?UTF-8?q?=D0=B2=D1=81=D1=82=D1=80=D0=BE=D0=B5=D0=BD=D0=BD=D1=8B=D1=85=20?= =?UTF-8?q?=D1=82=D0=B0=D0=B1=D0=BB=D0=B8=D1=86=20-=20=D0=B2=D0=BB=D0=BE?= =?UTF-8?q?=D0=B6=D0=B5=D0=BD=D0=BD=D1=8B=D0=B5=20=D1=84=D0=BE=D1=80=D0=BC?= =?UTF-8?q?=D1=8B,=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BC=D0=B5=D1=89=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BA=D0=BE=D0=B4=D0=B0=20=D0=B2=20=D0=BE?= =?UTF-8?q?=D1=82=D0=B4=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D0=B5=20=D0=BC=D0=BE?= =?UTF-8?q?=D0=B4=D1=83=D0=BB=D0=B8,=20=D0=B8=D0=BD=D0=B8=D1=86=D0=B8?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D1=8F=20=D1=82=D0=B0=D0=B1=D0=BB=D0=B8=D1=86?= =?UTF-8?q?=20=D0=B4=D0=BB=D1=8F=20=D0=B2=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD?= =?UTF-8?q?=D0=BD=D1=8B=D1=85=20=D1=84=D0=BE=D1=80=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.R | 221 +++++++++++------------------- configs/schemas/main.xlsx | Bin 11709 -> 11692 bytes configs/schemas/test_inline2.xlsx | Bin 11430 -> 0 bytes configs/schemas/test_inline3.xlsx | Bin 0 -> 11567 bytes helpers/functions.R | 23 +--- modules/data_manipulations.R | 25 ++++ modules/data_validation.R | 90 ++++++++++++ modules/db.R | 106 ++++++++++++++ modules/utils.R | 26 ++-- 9 files changed, 318 insertions(+), 173 deletions(-) delete mode 100644 configs/schemas/test_inline2.xlsx create mode 100644 configs/schemas/test_inline3.xlsx create mode 100644 modules/data_manipulations.R create mode 100644 modules/data_validation.R diff --git a/app.R b/app.R index c19950a..0dcacd7 100644 --- a/app.R +++ b/app.R @@ -28,7 +28,8 @@ box::purge_cache() box::use( modules/utils, modules/global_options, - modules/db + modules/db, + modules/data_validation ) # SETTINGS ================================ @@ -41,7 +42,6 @@ rmarkdown::find_pandoc(dir = "/opt/homebrew/bin/") # TODO: dynamic button render depend on pandoc installation if (!rmarkdown::pandoc_available()) warning("Can't find pandoc!") - load_scheme_from_xlsx <- function( filename, colnames = c("part", "subgroup", "form_id", "form_label", "form_type") @@ -56,15 +56,19 @@ load_scheme_from_xlsx <- function( } +extract_forms_id_and_types_from_scheme <- function(scheme) { + scheme |> + dplyr::filter(!form_type %in% c("inline_table", "inline_table2","description", "description_header")) |> + dplyr::distinct(form_id, form_type) |> + tibble::deframe() +} + # SCHEME_MAIN UNPACK ========================== # load scheme SCHEME_MAIN <- load_scheme_from_xlsx(FILE_SCHEME) # get list of simple inputs -inputs_simple_list <- SCHEME_MAIN |> - dplyr::filter(!form_type %in% c("inline_table", "inline_table2","description", "description_header")) |> - dplyr::distinct(form_id, form_type) |> - tibble::deframe() +inputs_simple_list <- extract_forms_id_and_types_from_scheme(SCHEME_MAIN) # get list of inputs with inline tables inputs_tables_list <- SCHEME_MAIN |> @@ -80,61 +84,32 @@ inputs_table_df <- SCHEME_MAIN |> con <- db$make_db_connection() # init DB (write dummy data to "main" table) -if (!"main" %in% DBI::dbListTables(con)) { - dummy_df <- dplyr::mutate(get_dummy_df(), id = "dummy") +db$check_if_table_is_exist_and_init_if_not("main", inputs_simple_list) - # write dummy df into base, then delete dummy row - DBI::dbWriteTable(con, "main", dummy_df, append = TRUE) - DBI::dbExecute(con, "DELETE FROM main WHERE id = 'dummy'") +purrr::walk( + .x = unique(inputs_table_df$form_id), + .f = \(table_name) { - rm(dummy_df) -} + this_inline_table2_info <- inputs_table_df |> + dplyr::filter(form_id == {table_name}) -# checking if db structure in form compatible with alrady writed data (in case on changig form) -if (identical(colnames(DBI::dbReadTable(con, "main")), names(inputs_simple_list))) { - print("identical") -} else { - df_to_rewrite <- DBI::dbReadTable(con, "main") - form_base_difference <- setdiff(names(inputs_simple_list), colnames(df_to_rewrite)) - base_form_difference <- setdiff(colnames(df_to_rewrite), names(inputs_simple_list)) + # получение имя файла с таблицой + inline_table2_file_name <- this_inline_table2_info$choices + + # загрузка схемы для данной вложенной формы + this_inline_table2_scheme <- fs::path(folder_with_schemas, inline_table2_file_name) |> + load_scheme_from_xlsx(colnames = c("subgroup","form_id", "form_label", "form_type")) + + this_table_id_and_types_list <- extract_forms_id_and_types_from_scheme(this_inline_table2_scheme) + + db$check_if_table_is_exist_and_init_if_not( + table_name, + this_table_id_and_types_list, + con = con + ) - # if lengths are equal - if (length(names(inputs_simple_list)) == length(colnames(df_to_rewrite)) && - length(form_base_difference) == 0 && - length(base_form_difference) == 0) { - warning("changes in scheme file detected: assuming order changed only") } - - if (length(names(inputs_simple_list)) == length(colnames(df_to_rewrite)) && - length(form_base_difference) != 0 && - length(base_form_difference) != 0) { - stop("changes in scheme file detected: structure has been changed") - } - - if (length(names(inputs_simple_list)) > length(colnames(df_to_rewrite)) && length(form_base_difference) != 0) { - warning("changes in scheme file detected: new inputs form was added") - warning("trying to adapt database") - - # add empty data for each new input form - for (i in form_base_difference) { - df_to_rewrite <- df_to_rewrite |> - dplyr::mutate(!!dplyr::sym(i) := utils$get_empty_data(inputs_simple_list[i])) - } - - # reorder due to scheme - df_to_rewrite <- df_to_rewrite |> - dplyr::select(dplyr::all_of(names(inputs_simple_list))) - - DBI::dbWriteTable(con, "main", df_to_rewrite, overwrite = TRUE) - DBI::dbExecute(con, "DELETE FROM main WHERE id = 'dummy'") - } - - if (length(names(inputs_simple_list)) < length(colnames(df_to_rewrite))) { - stop("changes in scheme file detected: some of inputs form was deleted! it may cause data loss!") - } - # cleaning - rm(df_to_rewrite, form_base_difference) -} +) # close connection to prevent data loss db$close_db_connection(con) @@ -281,35 +256,66 @@ server <- function(input, output) { observeEvent(input[[table_name]], { + ns <- NS(table_name) + + # данные для данной вложенной формы this_inline_table2_info <- inputs_table_df |> dplyr::filter(form_id == {table_name}) + # получение имя файла с таблицой inline_table2_file_name <- this_inline_table2_info$choices + # загрузка схемы для данной вложенной формы this_inline_table2_scheme <- fs::path(folder_with_schemas, inline_table2_file_name) |> - load_scheme_from_xlsx(colnames = c("form_id", "form_label", "form_type")) + load_scheme_from_xlsx(colnames = c("subgroup","form_id", "form_label", "form_type")) - yay_its_fun <- purrr::pmap( - .l = dplyr::distinct(this_inline_table2_scheme, form_id, form_label, form_type), - .f = utils$render_forms, - main_scheme = this_inline_table2_scheme - ) + # # формирование карточек для данной формы + # yay_its_fun <- purrr::pmap( + # .l = dplyr::distinct(this_inline_table2_scheme, form_id, form_label, form_type), + # .f = utils$render_forms, + # main_scheme = this_inline_table2_scheme, + # ns = ns + # ) + yay_its_fun <- purrr::map( + .x = unique(this_inline_table2_scheme$subgroup), + .f = \(subgroup) { + subroup_scheme <- this_inline_table2_scheme |> + filter(subgroup == {{subgroup}}) + + bslib::nav_panel( + title = subgroup, + purrr::pmap( + .l = dplyr::distinct(subroup_scheme, form_id, form_label, form_type), + .f = utils$render_forms, + main_scheme = subroup_scheme, + ns = ns + ) + ) + } + ) + + # ui для всплывающего окна ui_for_inline_table <- card( - height = "800px", - layout_sidebar( - sidebar = selectizeInput( - inputId = "aboba", - label = "key", - choices = c("a", "b") + navset_card_underline( + sidebar = sidebar( + width = 300, + selectizeInput( + inputId = "aboba", + label = "key", + choices = c("a", "b") + ) ), - yay_its_fun + !!!yay_its_fun ) ) + # проверка данных для внутренних таблиц + iv_inner <- data_validation$init_val(this_inline_table2_scheme, ns) + iv_inner$enable() + showModal(modalDialog( ui_for_inline_table, - # title = modalButton("Dismiss"),, footer = modalButton("Dismiss"), size = "l" )) @@ -320,76 +326,8 @@ server <- function(input, output) { # VALIDATIONS ============================ # create new validator - iv <- shinyvalidate::InputValidator$new() - - # add rules to all inputs - purrr::walk( - .x = names(inputs_simple_list), - .f = \(x_input_id) { - form_type <- inputs_simple_list[[x_input_id]] - - choices <- dplyr::filter(SCHEME_MAIN, form_id == {{x_input_id}}) |> - dplyr::pull(choices) - - val_required <- dplyr::filter(SCHEME_MAIN, form_id == {{x_input_id}}) |> - dplyr::distinct(required) |> - dplyr::pull(required) - - # for `number` type: if in `choices` column has values then parsing them to range validation - # value `0; 250` -> transform to rule validation value from 0 to 250 - if (form_type == "number") { - - iv$add_rule(x_input_id, function(x) { - # exit if empty - if (check_for_empty_data(x)) { - return(NULL) - } - # check for numeric - # if (grepl("^[-]?(\\d*\\,\\d+|\\d+\\,\\d*|\\d+)$", x)) NULL else "Значение должно быть числом." - if (grepl("^[+-]?\\d*[\\.|\\,]?\\d+$", x)) NULL else "Значение должно быть числом." - }) - - # проверка на соответствие диапазону значений - if (!is.na(choices)) { - # разделить на несколько елементов - ranges <- as.integer(stringr::str_split_1(choices, "; ")) - - # проверка на кол-во значений - if (length(ranges) > 3) { - warning("Количество переданных элементов'", x_input_id, "' > 2") - } else { - iv$add_rule( - x_input_id, - function(x) { - - # замена разделителя десятичных цифр - x <- stringr::str_replace(x, ",", ".") - - # exit if empty - if (check_for_empty_data(x)) { - return(NULL) - } - - # check for currect value - if (between(as.double(x), ranges[1], ranges[2])) { - NULL - } else { - glue::glue("Значение должно быть между {ranges[1]} и {ranges[2]}.") - } - } - ) - } - } - } - - # if in `required` column value is `1` apply standart validation - if (!is.na(val_required) && val_required == 1) { - iv$add_rule(x_input_id, shinyvalidate::sv_required(message = "Необходимо заполнить.")) - } - } - ) - # enable validator - iv$enable() + iv_main <- data_validation$init_val(SCHEME_MAIN) + iv_main$enable() # STATUSES =============================== # вывести отображение что что-то не так @@ -401,8 +339,7 @@ server <- function(input, output) { }) output$status_message2 <- renderText({ - iv$is_valid() - # res_auth$admin + iv_main$is_valid() }) # CREATE RHANDSOME TABLES ===================== diff --git a/configs/schemas/main.xlsx b/configs/schemas/main.xlsx index 3aa4c49919b098d14470584b4c78026038dbbf9d..611089907a4c8ac2bb0906c22c3884e51b522edc 100644 GIT binary patch delta 4542 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 delta 4583 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 diff --git a/configs/schemas/test_inline3.xlsx b/configs/schemas/test_inline3.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..0ccd690934bc65fc42bcff4376b49e5ae49c2b36 GIT binary patch literal 11567 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 Z7pW`<^SZkL0OIS%|5Ybb>Hhfk{{Wf4rGWqd literal 0 HcmV?d00001 diff --git a/helpers/functions.R b/helpers/functions.R index bd1c908..a5a5ca3 100644 --- a/helpers/functions.R +++ b/helpers/functions.R @@ -1,32 +1,11 @@ -get_dummy_data <- function(type) { - if (type %in% c("text", "select_one", "select_multiple")) return("dummy") - if (type %in% c("radio", "checkbox")) return("dummy") - if (type %in% c("date")) return(as.Date("1990-01-01")) - if (type %in% c("number")) return(as.double(999)) -} - -# get_empty_data <- function(type) { -# if (type %in% c("text", "select_one", "select_multiple")) return(as.character(NA)) -# if (type %in% c("radio", "checkbox")) return(as.character(NA)) -# if (type %in% c("date")) return(as.Date(NA)) -# if (type %in% c("number")) return(as.character(NA)) -# } - -get_dummy_df <- function() { - purrr::map( - .x = inputs_simple_list, - .f = get_empty_data - ) %>% - as_tibble() -} - #' @description Function check if variable contains some sort of empty data #' (NULL, NA, "", other 0-length data) and return `TRUE` (`FALSE` if data is #' not 'empty'). #' #' Needed for proper data validation. +#' (ДУБЛИРУЕТ МОДУЛЬ `data_manipulation`) check_for_empty_data <- function(value_to_check) { # for any 0-length diff --git a/modules/data_manipulations.R b/modules/data_manipulations.R new file mode 100644 index 0000000..f668aaa --- /dev/null +++ b/modules/data_manipulations.R @@ -0,0 +1,25 @@ + +#' @description Function check if variable contains some sort of empty data +#' (NULL, NA, "", other 0-length data) and return `TRUE` (`FALSE` if data is +#' not 'empty'). +#' +#' Needed for proper data validation. +check_for_empty_data <- function(value_to_check) { + + # for any 0-length + if (length(value_to_check) == 0) return(TRUE) + + # for NA + if (is.logical(value_to_check) && is.na(value_to_check)) return(TRUE) + + # for NULL + if (is.null(value_to_check)) return(TRUE) + + # for non-empty Date (RETURN FALSE) + if (inherits(value_to_check, "Date") && length(value_to_check) != 0) return(FALSE) + + # for empty strings (stands before checking non-empty data for avoid mistakes) + if (value_to_check == "") return(TRUE) + + FALSE +} diff --git a/modules/data_validation.R b/modules/data_validation.R new file mode 100644 index 0000000..27edb02 --- /dev/null +++ b/modules/data_validation.R @@ -0,0 +1,90 @@ + + +init_val <- function(scheme, ns) { + + options(box.path = here::here()) + box::use(modules/data_manipulations[check_for_empty_data]) + + iv <- shinyvalidate::InputValidator$new() + + # если передана функция с пространством имен, то происходит модификация id + if(!missing(ns)) { + scheme <- scheme |> + dplyr::mutate(form_id = ns(form_id)) + } + + # формируем список id - тип + inputs_simple_list <- scheme |> + dplyr::filter(!form_type %in% c("inline_table", "inline_table2","description", "description_header")) |> + dplyr::distinct(form_id, form_type) |> + tibble::deframe() + + # add rules to all inputs + purrr::walk( + .x = names(inputs_simple_list), + .f = \(x_input_id) { + + form_type <- inputs_simple_list[[x_input_id]] + + choices <- dplyr::filter(scheme, form_id == {{x_input_id}}) |> + dplyr::pull(choices) + + val_required <- dplyr::filter(scheme, form_id == {{x_input_id}}) |> + dplyr::distinct(required) |> + dplyr::pull(required) + + # for `number` type: if in `choices` column has values then parsing them to range validation + # value `0; 250` -> transform to rule validation value from 0 to 250 + if (form_type == "number") { + + iv$add_rule(x_input_id, function(x) { + # exit if empty + if (check_for_empty_data(x)) { + return(NULL) + } + # check for numeric + # if (grepl("^[-]?(\\d*\\,\\d+|\\d+\\,\\d*|\\d+)$", x)) NULL else "Значение должно быть числом." + if (grepl("^[+-]?\\d*[\\.|\\,]?\\d+$", x)) NULL else "Значение должно быть числом." + }) + + # проверка на соответствие диапазону значений + if (!is.na(choices)) { + # разделить на несколько елементов + ranges <- as.integer(stringr::str_split_1(choices, "; ")) + + # проверка на кол-во значений + if (length(ranges) > 3) { + warning("Количество переданных элементов'", x_input_id, "' > 2") + } else { + iv$add_rule( + x_input_id, + function(x) { + + # замена разделителя десятичных цифр + x <- stringr::str_replace(x, ",", ".") + + # exit if empty + if (check_for_empty_data(x)) { + return(NULL) + } + + # check for currect value + if (dplyr::between(as.double(x), ranges[1], ranges[2])) { + NULL + } else { + glue::glue("Значение должно быть между {ranges[1]} и {ranges[2]}.") + } + } + ) + } + } + } + + # if in `required` column value is `1` apply standart validation + if (!is.na(val_required) && val_required == 1) { + iv$add_rule(x_input_id, shinyvalidate::sv_required(message = "Необходимо заполнить.")) + } + } + ) + iv +} \ No newline at end of file diff --git a/modules/db.R b/modules/db.R index ff458da..9c9dd9d 100644 --- a/modules/db.R +++ b/modules/db.R @@ -24,4 +24,110 @@ close_db_connection <- function(con, where = "") { warning = function(w) if (getOption("APP.DEBUG", FALSE)) message("=!= ALREADY DISCONNECTED ", where), finally = if (getOption("APP.DEBUG", FALSE)) message("=/= DB DISCONNECT ", where) ) +} + +#' @export +#' проверить если таблица есть в базе данных и инициировать ее, если от +check_if_table_is_exist_and_init_if_not <- function( + table_name, + forms_id_type_list, + con = rlang::env_get(rlang::caller_env(), nm = "con") +) { + + if (table_name %in% DBI::dbListTables(con)) { + + cli::cli_inform("таблица есть такая: 'table_name'") + + # если таблица существует, производим проверку структуры таблицы + compare_existing_table_with_schema(table_name, forms_id_type_list) + + } else { + + dummy_df <- dplyr::mutate(get_dummy_df(forms_id_type_list), id = "dummy") + + # write dummy df into base, then delete dummy row + DBI::dbWriteTable(con, table_name, dummy_df, append = TRUE) + DBI::dbExecute(con, "DELETE FROM main WHERE id = 'dummy'") + + cli::cli_alert_success("таблица '{table_name}' успешно создана") + } + +} + +get_dummy_data <- function(type) { + + if (type %in% c("text", "select_one", "select_multiple")) return("dummy") + if (type %in% c("radio", "checkbox")) return("dummy") + if (type %in% c("date")) return(as.Date("1990-01-01")) + if (type %in% c("number")) return(as.double(999)) + +} + +get_dummy_df <- function(forms_id_type_list) { + + options(box.path = here::here()) + box::use(modules/utils) + + purrr::map( + .x = forms_id_type_list, + .f = utils$get_empty_data + ) |> + dplyr::as_tibble() + +} + +compare_existing_table_with_schema <- function( + table_name, + forms_id_type_list, + con = rlang::env_get(rlang::caller_env(), nm = "con") +) { + + options(box.path = here::here()) + box::use(modules/utils) + + # checking if db structure in form compatible with alrady writed data (in case on changig form) + if (identical(colnames(DBI::dbReadTable(con, table_name)), names(forms_id_type_list))) { + print("identical") + } else { + df_to_rewrite <- DBI::dbReadTable(con, table_name) + form_base_difference <- setdiff(names(forms_id_type_list), colnames(df_to_rewrite)) + base_form_difference <- setdiff(colnames(df_to_rewrite), names(forms_id_type_list)) + + # if lengths are equal + if (length(names(forms_id_type_list)) == length(colnames(df_to_rewrite)) && + length(form_base_difference) == 0 && + length(base_form_difference) == 0) { + warning("changes in scheme file detected: assuming order changed only") + } + + if (length(names(forms_id_type_list)) == length(colnames(df_to_rewrite)) && + length(form_base_difference) != 0 && + length(base_form_difference) != 0) { + stop("changes in scheme file detected: structure has been changed") + } + + if (length(names(forms_id_type_list)) > length(colnames(df_to_rewrite)) && length(form_base_difference) != 0) { + warning("changes in scheme file detected: new inputs form was added") + warning("trying to adapt database") + + # add empty data for each new input form + for (i in form_base_difference) { + df_to_rewrite <- df_to_rewrite |> + dplyr::mutate(!!dplyr::sym(i) := utils$get_empty_data(forms_id_type_list[i])) + } + + # reorder due to scheme + df_to_rewrite <- df_to_rewrite |> + dplyr::select(dplyr::all_of(names(forms_id_type_list))) + + DBI::dbWriteTable(con, table_name, df_to_rewrite, overwrite = TRUE) + DBI::dbExecute(con, "DELETE FROM main WHERE id = 'dummy'") + } + + if (length(names(forms_id_type_list)) < length(colnames(df_to_rewrite))) { + stop("changes in scheme file detected: some of inputs form was deleted! it may cause data loss!") + } + # cleaning + rm(df_to_rewrite, form_base_difference) + } } \ No newline at end of file diff --git a/modules/utils.R b/modules/utils.R index 28b3912..bd3d314 100644 --- a/modules/utils.R +++ b/modules/utils.R @@ -50,19 +50,28 @@ render_forms <- function( form_id, form_label, form_type, - main_scheme + main_scheme, + ns ) { + # заготовку для формы (проверка на выходе функции) form <- NULL + + # параметры только для этой формы filterd_line <- dplyr::filter(main_scheme, form_id == {{form_id}}) - # check if have condition + # если передана ns() функция то подмеяем id для каждой формы в соответствии с пространством имен + if (!missing(ns)) { + form_id <- ns(form_id) + } + + # отдельно извлечение параметров условного отображения condition <- unique(filterd_line$condition) - # get choices from schema + # элементы выбора choices <- filterd_line$choices - # get choices from schema + # описание description <- unique(filterd_line) |> dplyr::filter(!is.na(form_description)) |> dplyr::distinct(form_description) |> @@ -84,12 +93,11 @@ render_forms <- function( shiny::tagList( if (!is.na(form_label)) { shiny::span(form_label, style = "color: #444444; font-weight: 550; line-height: 1.4;") - # если в схеме есть поле с описанием - добавлеяем его следующей строчкой + # если в схеме есть поле с описанием - добавляем его следующей строчкой }, if (!is.na(description) && !is.na(form_label)) shiny::br(), if (!is.na(description)) { shiny::span(shiny::markdown(description)) |> htmltools::tagAppendAttributes(style = "color:gray; font-size:small; line-height: 1.4;") - # span(description, style = "color:gray; font-size:small;") } ) } @@ -130,7 +138,7 @@ render_forms <- function( }) } - # еденичный выбор + # единичный выбор if (form_type == "select_one") { form <- shiny::selectizeInput( inputId = form_id, @@ -208,7 +216,8 @@ render_forms <- function( if (!is.na(condition)) { form <- shiny::conditionalPanel( condition = condition, - form + form, + ns = ifelse(missing(ns), shiny::NS(NULL), ns) ) } @@ -228,7 +237,6 @@ get_empty_data <- function(type) { if (type %in% c("number")) as.character(NA) } - #' @export #' @description Function to update input forms (default variants only) #' @param id - input form id;