0

I am trying to use fluidRow inside a bs4TabItem that will have a maximum of 3 bs4UserCard items. The fluidRows should be built dynamically inside a for loop and the max. 3 bs4UserCard should also be built dynamically inside a for loop. Below is a snippet of the code. I have tried the code both in the UI and the Server section base on other recommendations but it still doesn't work.

# Get number of rows from dataset
records = nrow(candidatesDF)

# Each row will have max. 3 items
numRows = ceiling(nrow(candidatesDF) / 3)
numRows = c(1:numRows)

count = 0
offset = 3

candidates =  bs4TabItem(tabName = "candidates",
  for (row in numRows) {
    fluidRow(
      if (records < offset) {
        offset = records
      }
      for (column in 1:offset) {
        count = count + 1

        # Convert the names to a more legible format
        name = explode(candidatesDF[count, "Candidate"], sep = ", ")
        name = paste0(name[2], ' ', name[1])
        name = capitalizeStrings(name, all.words = TRUE, lower.back = TRUE)

        # Convert the names to the img name format
        imgName = explode(name, sep = " ")
        imgName = tolower(implode(imgName, "_"))
        imgUrl = paste0("img/", imgName, ".png")

        # Create a user card on each iteration.
        bs4UserCard(
          title = name,
          subtitle = candidatesDF[count, "Party"],
          type = NULL,
          width = 4,
          src = imgUrl,
          status = "danger",
          closable = TRUE,
          elevation = 4,
          imageElevation = 4,
          fluidRow(
            column(
              width = 4,
              descriptionBlock(header = "District",
               text = capitalizeStrings(candidatesDF[count, "District"],
                      all.words = TRUE, lower.back = TRUE ))
            ),
            column(
              width = 4,
              descriptionBlock(header = "Votes",
               text = candidatesDF[count, "Votes"])
            ),
            column(
              width = 4,
              descriptionBlock(header = "Result",
               text = candidatesDF[count, "Result"], right_border = FALSE)
            )
          )
        )

        records = records - 1
      }
    ) 
  } 
)

With the if statement I get this error

Possible missing comma at:
87:  for (row in fluidRows) {
              ^

If I remove the if statement just for testing purposes I get this error

Warning: Error in explode: Assertion on 'x' failed: May not be NA.

I'm not sure how x in explode is NA because I don't have any NA values in the dataset. When I run the code by line to test the explode function, the expected result is returned, so I don't understand why the NA.

Nonetheless my goal is to create x amount of fluidRows with a max of 3 items in each row, with the info for the items dynamically generated from the dataset. ######################################################################## I have updated the code to reflect suggestion to use lapply().

# Get number of rows from dataset
records = nrow(candidatesDF)

# Each row will have max. 3 items
numRows = ceiling(nrow(candidatesDF) / 3)
numRows = c(1:numRows)

count = 0
offset = 3

checkOffset = function(records, offset) {
  if (records < offset) {
    offset = records
  }
  
  return(offset) 
}


candidates =  bs4TabItem(tabName = "candidates",
 lapply(numRows, function(r) {
   fluidRow(
     lapply(1:checkOffset(records, offset), function(c) {
       count = count + 1
       print(count)
       # Convert the names to a more legible format
       name = explode(candidatesDF[count, "Candidate"], sep = ", ")
       name = paste0(name[2], ' ', name[1])
       name = capitalizeStrings(name, all.words = TRUE, lower.back = TRUE)

       # Convert the names to the img name format
       imgName = explode(name, sep = " ")
       imgName = tolower(implode(imgName, "_"))
       imgUrl = paste0("img/", imgName, ".png")
       
       records = records - 1
       
       # Create a user card on each iteration.
       bs4UserCard(
         title = name,
         subtitle = candidatesDF[count, "Party"],
         type = NULL,
         width = 4,
         src = imgUrl,
         status = "primary",
         closable = TRUE,
         elevation = 4,
         imageElevation = 4,
         fluidRow(
           column(
             width = 4,
             descriptionBlock(header = "District",
              text = capitalizeStrings(candidatesDF[count, "District"],
                     all.words = TRUE, lower.back = TRUE ))
           ),
           column(
             width = 4,
             descriptionBlock(header = "Votes",
              text = candidatesDF[count, "Votes"])
           ),
           column(
             width = 4,
             descriptionBlock(header = "Result",
              text = candidatesDF[count, "Result"], right_border = FALSE)
           )
         )
       )
     })
   )
 })

While this helped a great deal in getting close to the desired result, every card in each grid is the same. That is because the count variable does not increment, nor does the record variable.

Thanks in advance.

Ravi
  • 86
  • 6
  • 1
    Are you trying to make this responsive? Or just trying to hard code the number of elements at server start? `for` loops in R don't return values. What you should do is built a `list()` of objects that you want in insert into the UI and then pass that list to `fluidRow()`. It's easier to help if you provide a minimal [reproducible example](https://stackoverflow.com/questions/5963269/how-to-make-a-great-r-reproducible-example). Take out any code that isn't essential to the question. – MrFlick Aug 04 '20 at 05:13
  • Please try `lapply` instead of `for` loop. A MRE would have helped to identify any other issue as MrFlick pointed out. – YBS Aug 04 '20 at 14:12
  • @MrFlick I would like for the loop to generate the required values using an index that represents each row in the dataset. I thought the code I placed in the question was sufficient to be reproducible, so I'm not sure what more to add, and the link is not much clear as there are many possibilities. – Ravi Aug 05 '20 at 02:53
  • @YBS I have removed the for loops and replaced them with lapply. This worked up to a point. It was able to generate the expected number of rows and columns however it does not increase the count variable on each iteration. Hence it will show the same user card. – Ravi Aug 05 '20 at 02:57
  • @Ravi, it is hard to debug without a fully reproducible code. Two items I would consider are:(1) There is no need to define `numRows = c(1:numRows)`, instead you should just define `lapply(1:numRows, …`, (2) For the second `lapply`, please try `lapply(1:(checkOffset(records, offset)),...` – YBS Aug 05 '20 at 03:23
  • @YBS I would love to provide you with reproducible code if I knew exactly what is meant by that. would a sample dataset work? – Ravi Aug 05 '20 at 03:43
  • The most crucial item right now is understanding why the count variable which was declared globally as well as the records variable which was also declared globally is not being incremented in the case of count and decrementing in the case of records on every iteration. – Ravi Aug 05 '20 at 03:54

1 Answers1

0

After switching the for loop to lapply base on the recommendation by @YBS, my main issue became figuring out why the count and record variable would not keep their values after the first lapply iterates.

The answer is to use <<- for the assignment instead of =. An explanation can be found at can lapply not modify variables in a higher scope.

Ravi
  • 86
  • 6