ramnathv / htmlwidgets

HTML Widgets for R
http://htmlwidgets.org
Other
786 stars 207 forks source link

htmlwidgets without packages in shinylive. #487

Closed seanbirchall closed 1 month ago

seanbirchall commented 1 month ago

I've created a very basic widget to handsotable.js It works great in a normal Shiny app, but has some weird bugs happening as a shinylive app that I believe is related to htmlwidgets.js. Specifically I think my htmlwidget is firing before it should, is there something I can look at in htmlwidgets.js to prevent this?

I see plenty of questions and solutions related to this already like @timelyportfolio, but I think this is a little different as the widget is only made to work with shiny and ideally using shinylive. I opted to go this way vs using session$sendCustomMessage as I felt like I could only get it working that way by sending data with a delay for when to fire my widget code, which trying to time right seems pointless. Was hoping if I created it as a very minimal widget I can fire this code when it's needed.

This widget is just one module within a much larger app. This module ui to my widget can be added or removed at any point, in app I assign the ui of this module to a reactivevalues object to then display in app. As the name would suggest this widget is for viewing dataframes, users can click within a table containing objects in the current R environment and view their dataframes using the widget. There's a dedicated viewer pane in my app which I would like to be showing this in. This viewer pane looks for changes in the reactivevalues object that the module is assigned to. From the viewer pane the user can also remove anything there at any point by clicking a clear button which sets this specific reactivevalues object to NULL. I've noticed that this widget works fine in both shiny and shinylive if I always click clear first before selecting a new dataframe to view. If I don't do that though the widget fails.

For the full context here is the much larger app. You can recreate the bug by running something like df <- iris and then clicking within the environment table to view the dataframe. At which point you will be sent to the viewer pane where you can click clear, then return to the environment pane and select the same df again. The app will work just fine, but if you do not click clear and try clicking df again to view you will be thrown this message in the console. Which suggests to me something is firing too soon and the widget cannot find the necessary dependencies. Once the bug happens the widget is unable to recover and won't work again unless you refresh the page. In my head a potential workaround could be watching something in htmlwidgets.js, but I'm not sure what to watch for.

App: https://seanbirchall.github.io/scrapeable/webR/

App code: https://seanbirchall.github.io/scrapeable/webR/edit/ or https://github.com/seanbirchall/scrapeable

The widget can be found in df_viewer.R. It depends on df_viewer.js, handsontable,js, and handsontable.css

TypeError: Cannot read properties of null (reading 'deps')
    at shinyBinding.renderValue (htmlwidgets.js:515:44)
    at e.<anonymous> (shiny.min.js:2:165477)
    at m (shiny.min.js:2:157941)
    at Generator.<anonymous> (shiny.min.js:2:159264)
    at Generator.next (shiny.min.js:2:158300)
    at e_ (shiny.min.js:2:163911)
    at f (shiny.min.js:2:164109)
    at shiny.min.js:2:164170
    at new Promise (<anonymous>)
    at e.<anonymous> (shiny.min.js:2:164050)

Assign df <- iris image

Click df in environment table, app sends us to Viewer pane, and widget works the first time.

image

Still working on making a smaller reprex, sorry.

timelyportfolio commented 1 month ago

@seanbirchall neat little app. I looked quickly hoping there might be an easy solution, but unfortunately I did not luck into easy or quick. I'll keep working at it over the next couple of days. Clicking "clear" for me did not result in tables. I only get a table sporadically and maybe only one good render out of a session. I see the dependencies are in Source when I debug, but the renderValue() does not seem to get called.

seanbirchall commented 1 month ago

Please don't spend much time on it @timelyportfolio, I tagged you as you're in almost every issue regarding this. I appreciate the check for a quick solution already, don't feel like you need to do more, unless compelled to.

And thank you! it's a very hacky app and I'm trying to get this widget to work in a similar hacky fashion. I just noticed htmlwidgets appear to be working in George's webR repl app so I should probably rewrite how I'm currently doing widgets.

Also working great was probably a poor way to describe this widget in a normal shiny app. This widget does not work as others do, probably because it's not a package, and I haven't done a great job with it yet. For any code run with a call to an htmlwidget it gets assigned to ide$viewer and will persist whether I click between environment and viewer. Example running reactable(iris) will send me to viewer pane where I can see the iris reactable. I can then swap between the environment and viewer pane and this reactable within ide$viewer will persist so long as I don't run additional code in the editor. With my df_viewer widget this is not the case. It will work the first time and display the widget but also not persist if I swap between environment and viewer. I think that is another clue about what is going on but I get nothing in the console to debug.

This could very well be more of a shiny issue and I just need to be writing better code, but everything I've tried in shiny hasn't worked.

My backup plan right now is to just make this a normal package widget on cran with some special shiny functionality if I can't figure this out. With the hopes that it would just work at that point similar to how other widgets currently just work in the app.

EDIT: bug 2 is just my shiny code. reactable selections in table fire when I move between environment and viewer (causing ide$viewer <- NULL. I might try just using shinyjs and hiding / showing my df_viewer while always keeping it around.

timelyportfolio commented 1 month ago

@seanbirchall my best initial guess based on the quick inspection is that the source of the problematic behavior is Shiny's handling of hidden elements which might be overcome by outputOptions(..., suspendWhenHidden = FALSE). It seems like the necessary DOM containers are not in place when the instruction to render the handsontable is sent. I need to figure out how to debug the messages in shinylive similar to how I like to inspect the websockets in a normal Shiny app.

jcheng5 commented 1 month ago

@seanbirchall Before I take a look myself, can you confirm that there is never a problem when you run it in normal Shiny, only in Shinylive? Thanks!

timelyportfolio commented 1 month ago

@jcheng5 @seanbirchall the app does not work locally for me. Never mind I was double-clicking on the df. If I single click, I can confirm that the app works locally. Sorry for the confusion.

seanbirchall commented 1 month ago

@jcheng5 can confirm that locally running as a shiny app the widget does work as expected. After exporting to shinylive is where I run into the above issues. Below video of me running it locally. I've yet to run into a problem running it locally as a shiny app, but can't say I've done very rigorous testing of it yet.

@timelyportfolio sorry yes single clicking should work and is what I'm doing. If you double click fast it will unselect before the widget can be displayed in the viewer. I should probably control for that at some point soon.

https://github.com/user-attachments/assets/6ac8da0b-4ade-48dd-8f3a-3234d03629e0

In the above clicking clear isn't needed for the widget to display as expected. I can swap between viewer and environment, reselect the same df, or select a new df and the widget will still work. In shinylive I can only do this by explicitly clicking clear before swapping between the viewer and environment pane. If I do not click clear after viewing the widget the app will throw an error the next time I click to view any other df (or the same df).

Also might be worth adding but I also needed to wrap my widget in the below initializeWidget function as df_viewer.js would fire before htmlwidget.js was available when exported as a shinylive app.


(function() {
  function initializeWidget() {
    if (typeof HTMLWidgets !== 'undefined' && typeof Shiny !== 'undefined') {
     // widget code
   } else { 
     setTimeout(initializeWidget, 100);
   }
  }
  initializeWidget();
})();
seanbirchall commented 1 month ago

@timelyportfolio weird using outputOptions(output, "spreadsheet", suspendWhenHidden = FALSE) within my server function for mod_df_viewer.R causes the the widget to fail immediately in shinylive, can't even get the first instance of clicking a df to show the widget. Now this makes me think I probably can't use shinyjs hide / show as a possible workaround.

I need to figure out how to debug the messages in shinylive similar to how I like to inspect the websockets in a normal Shiny app.

Let me know if you figure that out. I've had no luck so far, after quite a bit of looking. Still grateful for shinylive though, it really is magic!

timelyportfolio commented 1 month ago

@seanbirchall @jcheng5 I think I have identified the source of the problem Since the source is not related to this issue, I will close here and start a new issue on @seanbirchall repo (with link to this one for tracking) to discuss what I think is happening.