ramnathv/htmlwidgets

Using without package?

Closed this issue · 11 comments

Is it possible to build an HTML widget without needing it to be its own package?

I am currently using HTML widgets to build custom visualization tools for proprietary medical data and have run into the issue of deploying to RStudio Connect as it needs packrat to be able to find the package source. What I'm planning to do is just convert it to a plain shiny module, but I will miss some of the nice scaffolding features that widgets provide.

I apologize if this has been answered somewhere already, my searches came up empty.

Thanks for a wonderful package!

We started down this path a while ago in #176 but never resolved. Glad you are reviving the discussion.

I need to familiarize myself with the internals but could this be accomplished by providing a path to libraries and to the main js file to a constructor which returns a list of the main function and the two shiny functions? I'm assuming the package build step is there to gather and inject the JavaScript to a single source?

@nstrayer I'd like to pursue this further as it relates to @fbreitwieser #304 and could provide a good mechanism for quick inclusion of htmlwidget functionality. From what I remember, one of the primary reasons for requiring a full package was the desire for local dependencies in a knowable location. However, with many packages, such as d3r, reactR, and rmarkdown, offering dependencies and the difficulty of specifying order of dependencies (#179) in the yaml configuration, I am not sure this is as necessary as we originally thought. For most of my recent or actively maintained widgets such as sunburstR, my yaml is blank.

Really, if we remove the dependency requirement, we should be able to achieve this objective (unless I am missing something - likely). So, hopefully this will push the conversation forward.

There is one place where we go looking for file dependencies

One option would be to look for the files as we currently do, but if they do not exist then proceed without error assuming the user or widget builder is intentionally breaking the convention. Any necessary dependencies would then be assumed to not exist or to have been provided in the dependencies argument of createWidget().

I think you could create htmlwigets outside of a package except for the requirement of the YAML file. If this was optional rather than required then any dependencies could also be provided via the dependencies argument to createWidget

Yep, something like this in place of lines seems to work to remove the necessity of yaml.

  # if yaml does not exist then assume no dependencies
  #  in this cases dependencies should be provided through the
  #  dependencies argument of createWidget
  widgetDep <- list()
  if(file.exists(system.file(config, package = package))) {
    config = yaml::yaml.load_file(
      system.file(config, package = package)
    )
    widgetDep <- lapply(config$dependencies, function(l){
      l$src = system.file(l$src, package = package)
      do.call(htmlDependency, l)
    })
  }

I'll submit a pull for better testing later today.

Then, the next step toward complete freedom would be to remove the necessity of a JavaScript binding. For this pursuing a similar approach to above, we could replace lines with

  # If js binding does not exist then assume provided through
  #  some other mechanism such as a specified `htmlDependency` or `script` tag.
  #  Note, this is a very special case.
  bindingDep <- NULL
  if(file.exists(system.file(jsfile, package = package))) {
    bindingDir <- system.file("htmlwidgets", package = package)
    argsDep <- NULL
    bindingDep <- do.call(htmlDependency, c(list(
      paste0(name, "-binding"), packageVersion(package),
      bindingDir, script = basename(jsfile)
    ), argsDep))
  }

  c(
    list(htmlDependency("htmlwidgets", packageVersion("htmlwidgets"),
                        src = system.file("www", package="htmlwidgets"),
                        script = "htmlwidgets.js"
    )),
    widgetDep,
    list(bindingDep)
  )

@timelyportfolio This makes sense and should work. Looking forward to your PR.

As a test, I manually removed the yaml from my sunburstR package in library and moved + prepended _ to the sund2b.js. Then, I successfully created an htmlwidget without a package using

library(htmltools)

browsable(
  tagList(
    sund2b(sequences),
    htmlDependency(
      name = "sund2b",
      version = "0.1.0",
      src = c(file="C://Users/kent.russell/Documents"),
      script = "_sund2b.js"
    )
  )
)

Even more aggressive, I was able to

library(htmltools)

browsable(
  tagList(
    tags$script("..."), # with body of sund2b.js binding
    sund2b(sequences)
  )
)

And in the even more extreme case it works...

htmlwidgets::createWidget("widget", x=list())

image

closed by #306; feel free to reopen for discussion or issues