button that's not a submitButton

882 views
Skip to first unread message

Harlan Harris

unread,
Nov 9, 2012, 5:05:19 PM11/9/12
to shiny-...@googlegroups.com
Hi, I'd like to be able to create a button in an app that triggers something to run, but isn't a submit button. Right now, it looks like the existence of a submitButton causes all other changes to wait until the button is pressed. I'd like to keep the existing reactive behavior, but have a button that users can press to cause other things to happen (in my current case, to push various parameter and state information to a database). Is there a way to fake that right now? What's the right way to build it?

Thanks! Really impressed so far!

Joe Cheng

unread,
Nov 9, 2012, 5:37:42 PM11/9/12
to shiny-...@googlegroups.com
That's not currently possible--at least not without JavaScript. If you're able, can you sketch out for me a little more about the scenario?

Harlan Harris

unread,
Nov 9, 2012, 5:45:43 PM11/9/12
to shiny-...@googlegroups.com
Yes, I figured it would require some JS... Defining a new input widget sort of thing...

Scenario is that the user enters information (numbers, text) in the input panel on the left. Using the current reactive model, I can generate output on the right-hand-side pretty easily. But I want the user to be able to push a button, not to update the output, but to run other (arbitrary code). In this case, I want to take the information in the left-hand panel, plus some computed data that's rendered on the right-hand output panel, and store it in a database. The only output I'd want to change might be a status message below the button (which seems straightforward).

What doesn't work now is that having a submitButton in the UI (even in a different panel!) seems to cause the whole UI to stop updating reactively. I don't want that. I just want the button to be like any other widget, where pressing it causes some sort of event to happen which in turn causes a reactive function on the server side to run.

I tried adapting the incrementing-button example in the docs to see if that works, but it seems like any sort of button does the UI-freezing behavior, even if it's created from scratch.


On Fri, Nov 9, 2012 at 5:37 PM, Joe Cheng <j...@joecheng.com> wrote:
That's not currently possible--at least not without JavaScript. If you're able, can you sketch out for me a little more about the scenario?

--
 
 

Joe Cheng [RStudio]

unread,
Nov 9, 2012, 6:24:37 PM11/9/12
to shiny-...@googlegroups.com
If you do <button type="button"></button> (or in R, tags$button(type="button", ...) ) then it won't freeze the UI. Only <button type="submit"></button> (which I believe is the default if you don't specify the type) will freeze the UI. Adapting the incrementing button example should work; as long as the input is updated with a distinct value every time the button is clicked, then the reactive stuff on the server will react.

I think I forgot to document something that will come in handy as you try to do this. You can run "options(shiny.trace=TRUE)" before calling runApp, and your R console will show messages going from the browser to the server and vice versa. In this way you can easily see if clicking on the button actually causes anything to get sent.

Let me know how it goes!

Harlan Harris

unread,
Nov 10, 2012, 7:18:14 PM11/10/12
to shiny-discuss
Hm, I see.

I'd definitely like to find a way to not have to update the button text. It works for testing, but doesn't look that great. For now, I'm toggling the text's case. :)

el.text(el.text().toUpperCase() == el.text() ? el.text().toLowerCase() : el.text().toUpperCase())

Incidentally, I've got a couple of cases (including this one) where I want a method to fire on a signal from the UI, but I don't actually need the input's value. I'm writing the code like this:

  output$buttontext <- reactiveText(function() {
    input$store
    as.character(rnorm(1))
  })


Can you suggest another way of doing this that isn't a moral failing? :)

Thanks for the shiny.trace tip! That's useful!

 -Harlan



--
 
 

Joe Cheng

unread,
Nov 10, 2012, 7:36:16 PM11/10/12
to shiny-...@googlegroups.com
Sorry, I didn't mean that the button text had to change, just that getValue had to return a distinct value each time it was called (say, Math.random()).

If that's not clear enough, please send me the JS code for your InputBinding and I'll see if I can show you what I mean.

Thanks

Harlan Harris

unread,
Nov 12, 2012, 8:37:00 AM11/12/12
to shiny-...@googlegroups.com
Oh, duh. I wasn't thinking through the dataflow completely. OK, here's the code. Returning the client-side timestamp should never collide. This seems generally useful and worth potentially adding to Shiny:

$(document).on("click", "button.eventbutton", function(evt) {
  // evt.target is the button that was clicked
  var el = $(evt.target);
 
  // Raise an event to signal that the value changed
  el.trigger("change");
});
var eventButtonBinding = new Shiny.InputBinding();
$.extend(eventButtonBinding, {
  find: function(scope) {
    return $(scope).find(".eventbutton");
  },
  getValue: function(el) {
    return new Date().getTime();
  },
  setValue: function(el, value) {
    // no-op
  },
  subscribe: function(el, callback) {
    $(el).on("change.eventButtonBinding", function(e) {
      callback();
    });
  },
  unsubscribe: function(el) {
    $(el).off(".eventButtonBinding");
  }
});

Shiny.inputBindings.register(eventButtonBinding);

eventButton <- function(inputId, value) {
  tagList(
    singleton(tags$head(tags$script(src = "js/eventbutton.js"))),
    tags$button(id = inputId,
                class = "eventbutton btn",
                type = "button",
                as.character(value))
  )
}

 -Harlan



--
 
 

Reply all
Reply to author
Forward
0 new messages