dreamRs / toastui

R htmlwidgets for ToastUI libraries: grid, calendar and chart
https://dreamrs.github.io/toastui
Other
82 stars 8 forks source link

Popup customization #8

Closed Teolone88 closed 6 months ago

Teolone88 commented 3 years ago

Hi,

Is there any way to customize the calendar() popup with different user inputs when isReadOnly = FALSE?

Should I change the inputs from https://github.com/dreamRs/toastui/blob/master/inst/htmlwidgets/calendar.js under cal.createSchedules([{}]) & Shiny.setInputValue({})

Please forgive my n000biness in Js.

Best, Teo

pvictor commented 3 years ago

Hello,

It might be difficult to add Shiny input widget or sophisticated elements in the built-in pop-up, a solution can be to use Shiny's logic with insertUI() / removeUI() and an absolutePanel(), like that you can put whatever you want in the pop-up. Here's a full example:


# Calendar: custom popup --------------------------------------------------

library(shiny)
library(toastui)

## Schedules data
schedules <- cal_demo_data()
# add an id
schedules$id <- seq_len(nrow(schedules))
# add custom data in "raw" attribute
schedules$raw <- lapply(
  X = seq_len(nrow(schedules)),
  FUN = function(i) {
    list(
      status = sample(c("Completed", "In progress", "Closed"), 1)
    )
  }
)

## App

ui <- fluidPage(
  fluidRow(
    column(
      width = 8, offset = 2,
      tags$h2("Custom popup made with Shiny"),
      calendarOutput(outputId = "cal"),
      uiOutput("ui")
    )
  )
)

server <- function(input, output, session) {

  output$cal <- renderCalendar({
    calendar(view = "month", taskView = TRUE, useDetailPopup = FALSE) %>% 
      cal_props(cal_demo_props()) %>% 
      cal_schedules(schedules) %>%
      cal_events(
        clickSchedule = JS(
          "function(event) {", 
          "Shiny.setInputValue('calendar_id_click', {id: event.schedule.id, x: event.event.clientX, y: event.event.clientY});", 
          "}"
        )
      )
  })

  observeEvent(input$calendar_id_click, {
    removeUI(selector = "#custom_popup")
    id <- as.numeric(input$calendar_id_click$id)
    # Get the appropriate line clicked
    sched <- schedules[schedules$id == id, ]

    insertUI(
      selector = "body",
      ui = absolutePanel(
        id = "custom_popup",
        top = input$calendar_id_click$y,
        left = input$calendar_id_click$x, 
        draggable = FALSE,
        width = "300px",
        tags$div(
          style = "width: 250px; position: relative; background: #FFF; padding: 10px; box-shadow: 0px 0.2em 0.4em rgb(0, 0, 0, 0.8); border-radius: 5px;",
          actionLink(
            inputId = "close_calendar_panel", 
            label = NULL, icon = icon("close"), 
            style = "position: absolute; top: 5px; right: 5px;"
          ),
          tags$br(),
          tags$div(
            style = "text-align: center;",
            tags$p(
              "Here you can put custom", tags$b("HTML"), "elements."
            ),
            tags$p(
              "You clicked on schedule", sched$id, 
              "starting from", sched$start,
              "ending", sched$end
            ),
            tags$b("Current status:"), tags$span(class = "label label-primary", sched$raw[[1]]$status),
            radioButtons(
              inputId = "status",
              label = "New status:",
              choices = c("Completed", "In progress", "Closed"),
              selected = sched$raw[[1]]$status
            )
          )
        )
      )
    )
  })

  observeEvent(input$close_calendar_panel, {
    removeUI(selector = "#custom_popup")
  })

  rv <- reactiveValues(id = NULL, status = NULL)
  observeEvent(input$status, {
    rv$id <- input$calendar_id_click$id
    rv$status <- input$status
  })

  output$ui <- renderUI({
    tags$div(
      "Schedule", tags$b(rv$id), "has been updated with status", tags$b(rv$status)
    )
  })

}

shinyApp(ui, server)

let me know if you have any questions about the above code

Victor

Teolone88 commented 3 years ago

Really smooth!!! Thank you so much Victor. I was not aware of the absolutePanelfunction. You should commit this option as it makes sense to customize your pop up going forward.

januz commented 2 years ago

Just came from the documentation to your Github repo to see whether one can select a date on the monthly calendar and get a menu to enter data for that specific date (or a date range starting from that date). From what I read here, this seems possible, correct? It would be amazing if you would include this as a feature!

pvictor commented 2 years ago

@januz yes you can interactively add schedules in a calendar, but it will only work in Shiny since you need a backend to save data.

gavril0 commented 2 years ago

This work well when the schedule is already created. However, the event is not triggered when creating a new schedule by clicking on some other part of the calendar.

image

For example, "clickSchedule" is triggered when clicking on the schedules with blue background but not when defining a new period (see period defined on Wednesday in the above image).

I looked at "beforeCreateEvent" which seems to be the relevant event but I am not sure how to redefine it (I am JS/htmlwidget newbie).

Accessorily, let me state some reasons why I want to do that in case I should use a different approach:

1) I want to be able to control id of the newly created schedule (I already have unique ids defined in other part of my application). 2) I already have title, body and calendardId info in my app when creating a new schedule. I am using the calendar to define the start and end times when creating a schedule, and to visualize/modify time period of existing schedules. I could use a date/time picker but I like better the calendar visualization which shows empty time slots.

UPDATE Looking at your example I see that there are shiny events that I could use:

observeEvent(input$cal_add, {
  new <- input$cal_add
  new$id <-patient_id  # new id
  new$title <- "X,Y"
  new$body <- paste0("patient id:", patient_id)
  new$calendardId <- 2
  cal_proxy_add("cal", new)
})

observeEvent(input$cal_update, {
  cal_proxy_update("cal", input$cal_update)
})

observeEvent(input$cal_delete, {
  cal_proxy_delete("cal", input$cal_delete)
})

I am still exploring it (are these events documented? why use cal_proxy_xxxx?)

pvictor commented 2 years ago

Hello @gavril0 , Can you open a new issue with a reproducible example ? It's possible that the documentation is incomplete, it's a work in progress

nick-youngblut commented 7 months ago

I looked at "beforeCreateEvent" which seems to be the relevant event but I am not sure how to redefine it (I am JS/htmlwidget newbie).

@gavril0 did you figure this out? I would also like to create a custom pop-up for new events, but I don't see any examples of how to do this (calendar-popup-shiny.R just works for existing events... after applying the bugfix for v0.3).

nick-youngblut commented 7 months ago

UPDATE Looking at your example I see that there are shiny events that I could use:

@gavril0 at least for me, input$cal_update is not created, which seems to be due to using a custom-popup. Did you find a way around this, such as "manually" creating the value in cal_proxy_update(proxy, value)?

gavril0 commented 7 months ago

@nick-youngblut I ended up not using pop-up. However, I have to go back to the issue because I need to update my code to the new version of calendar

nick-youngblut commented 7 months ago

Thanks @gavril0 for letting me know!

@pvictor any suggestions on how to create a custom pop-up for new events, and also for updating the toastui calendar with a custom pop-up (see above)?

pvictor commented 7 months ago

Hello @nick-youngblut, can you provide an exemple to reproduce the issue ?

nick-youngblut commented 7 months ago

@pvictor I have 2 issues:

  1. It's not clear to me how to create a custom pop-up for creating new events. I have created a custom pop-up for viewing/editing existing events (see attached), but how do I create a pop-up for new events?
  2. It's not clear how to update a rendered calendar once the user has edited an event via a custom pop-up. For my custom pop-up, it appears that the input$cal_update object is not created, so I cannot simply use cal_proxy_update(proxy, input$cal_update) as in this example.

I need a custom pop-up for both creating new events and updating existing events, because I need to include 2 drop-downs in the pop-up that the user MUST select when creating an event. This is why I cannot simply use Google Calendar, since one cannot add mandatory drop-downs for creating events.

If you could point me to any documentation/examples, or provide some here, I'd appreciate it.

I'm using toastui v0.3.

Screenshot 2024-01-31 at 7 57 48 AM

pvictor commented 7 months ago

For the first issue, you can try this example : https://github.com/dreamRs/toastui/blob/master/inst/examples/calendar-custom-create.R, note that you need last GitHub version where I added support for selectdatetime event.

For the second one,, you should be able to do something like this with your own infos about the schedule to update :

library(shiny)
library(toastui)
library(lubridate)

ui <- fluidPage(
  tags$h2("Update schedule"),
  actionButton("update", "Update"),
  calendarOutput(outputId = "cal")
)

server <- function(input, output, session) {

  output$cal <- renderCalendar({
    calendar() %>% 
      cal_schedules(
        id = "MY_CUSTOM_SCHEDULE_ID",
        calendarId = "MY_CUSTOM_CALENDAR_ID",
        title = "Title",
        body = "Some description",
        start = Sys.Date(),
        end = Sys.Date(),
        category = "allday"
      )
  })

  observeEvent(input$update, {
    date <- Sys.Date() + sample(-10:10, 1)
    cal_proxy_update(
      "cal",
      list(
        id = "MY_CUSTOM_SCHEDULE_ID", 
        calendarId = "MY_CUSTOM_CALENDAR_ID",
        title = sample(ls(pos = "package:base"), 1),
        start = date,
        end = date
      )
    )
  })

}

shinyApp(ui, server)
nick-youngblut commented 7 months ago

Thanks @pvictor for the detailed response!

I haven't tried the 1st issue solution yet, but the for 2nd, cal_proxy_update() does nothing. I know that it is triggered, but the calendar event is not updated.

What is actually needed for the value parameter in cal_proxy_update()? When I run the ex-proxy-schedule.R example, and print input$my_calendar_update after editing an event, I get:

$schedule
$schedule$id
[1] "shd_ec425d5b"

$schedule$title
[1] "test"

$schedule$location
[1] "test"

$schedule$start
[1] "2024-01-30T00:00:00-08:00"

$schedule$end
[1] "2024-01-30T00:00:00-08:00"

$schedule$isAllday
[1] FALSE

$schedule$category
[1] "time"

$schedule$calendarId
[1] "clnd_00c2cdbf"

$changes
$changes$title
[1] "test1"

Based on the cal_proxy_update code, it appears that I need to provide:

I've tried this:

$schedule$id
[1] "dxsr26fhl7xkk8m2cqfo121958"

$schedule$calendarId
[1] "c_189f0a7x8kle8genl785cu3rixqke@resource.calendar.google.com"

$changes
$changes$title
[1] "Nick Youngblut"

$changes$start
[1] "2024-01-30 09:55:00 PST"

$changes$end
[1] "2024-01-30 10:50:00 PST"

However, this input to cal_proxy_update doesn't seem to do anything. Where does the $schedule$id value come from?

UPDATE:

I see that I have to use input$calendar_id_click$id as the ID. The start and end times now update. However, to render the custom pop-up for editing events, I'm using the data.frame of events used for rendering the calendar. This data.frame is not updated by cal_proxy_update, so the pop-up info is not updated. I need to get the updated event info after cal_proxy_update is run, but I don't see how to pull the event info. The cal_* functions seems to focus on adding data/params to the calendar and not retrieving (updated) info from the calendar. I was hoping that input$my_calendar_schedules (in my case input$cal_sechedules) would provide the updated schedule information for all events (schedules), but it seems to only provide a list for the most recent event (schedule) that is on the calendar (e.g., the event for today and not the other events from previous days).

pvictor commented 7 months ago

cal_proxy_update() does nothing

the example I provided doesn't work for you?

cal_proxy_update() update an existing schedule using its id and calendarId to identify it, if you do not provide id / calendarId when creating a schedule, they will be generated automatically.

input$<outputId>_update is an event sent when using builtin popup, if you do not use buitlin popup this event isn't triggered.

I need to get the updated event info after cal_proxy_update

You already have that since you called cal_proxy_update() with it, calendar here is just for display You have to update yourself your starting data.frame with the infos you collect from your custom popup, the calendar cannot do your backend.

nick-youngblut commented 7 months ago

the example I provided doesn't work for you?

A challenge for me in creating a custom pop-up allowing for schedule editing was that I not only had to figure out what data structure to provide as the value parameter to cal_proxy_update(), but I also had to edit the reactive data.frame object that I was using to fill the values in the custom pop-up. Once I figured out the list structure for the value parameter of cal_proxy_update() (thanks for your help!), the schedule in the calendar would update (e.g., the schedule start time), but when I clicked on the pop-up again, the values in the pop-up (e.g., time or title) were not changing. I had to also update the reactive data.frame object in addition to running cal_proxy_update() so that when the custom pop-up was rendered, it used the updated values instead of the original schedule values.

The pop-up for schedule editing seems to be correctly working fully. Thanks for all of your help.

I was also able to create a custom pop-up for creating new events (after updating the package to the latest version on github). Will you be releasing a new version on CRAN soon, or are you waiting on some more updates first?

Thanks! 🙏

nick-youngblut commented 7 months ago

@pvictor in the calendar-custom-create.R example, you use showModal(modalDialog()) to show a model instead of a pop-up at the location on the calendar where the user clicked. One can use input$calendar_id_click$y and input$calendar_id_click$x for a custom pop-up that edits an existing schedule, but input$calendar_id_click$y and input$calendar_id_click$x do not seem to be available for a custom pop-up for creating a new event, as described in calendar-custom-create.R.

Is there a way to create a custom pop-up for creating an event at the location on the calendar (e.g., day) were the user clicks?

pvictor commented 7 months ago

Is there a way to create a custom pop-up for creating an event at the location on the calendar (e.g., day) were the user clicks?

It's certainly possible, but I find it hard to see the added value compared to the complexity it would require to implement. The modal solution seems simple, robust and immediately available.

nick-youngblut commented 7 months ago

The modal solution seems simple, robust and immediately available.

After trying out the modal solution, I agree... especially given that one can obtain the date/time info from where the user clicks on the calendar to fill in date/time info in the modal.

toxintoxin commented 7 months ago

@pvictor in the calendar-custom-create.R example, you use showModal(modalDialog()) to show a model instead of a pop-up at the location on the calendar where the user clicked. One can use input$calendar_id_click$y and input$calendar_id_click$x for a custom pop-up that edits an existing schedule, but input$calendar_id_click$y and input$calendar_id_click$x do not seem to be available for a custom pop-up for creating a new event, as described in calendar-custom-create.R.

Is there a way to create a custom pop-up for creating an event at the location on the calendar (e.g., day) were the user clicks?

How can I add namespaces to javascript in calendar-custom-create.R, if you know, thank you very much!

nick-youngblut commented 6 months ago

@pvictor after I add a new event, the event cannot be selected via my custom pop-up code for editing (no value for the new event in input$calendar_id_click$id).

I'm using cal_proxy_add() to add the new event, but it doesn't seem to create a new input$calendar_id_click$id value for the event. However, if I reload my app (and thus re-render the calendar), my newly added event is listed in input$calendar_id_click$id, so then it can be updated via my custom pop-up.

Any ideas on why input$calendar_id_click$id is not updated after creating a new event via a custom modal (as we discussed above)?

Note: it appears that calendar-custom-create.R also does not update input$calendar_id_click$id, given that if I add observeEvent(input$calendar_id_click, { print(input$calendar_id_click$id) }) to the app code, no click events occur when I click on events after creating them via the modal.

pvictor commented 6 months ago

@nick-youngblut , @toxintoxin : can you please open new issues with some reproducible example ?