Compare commits

...

23 Commits

Author SHA1 Message Date
Barbara Borges Ribeiro
e71e8db0a2 added NEWS entry 2016-10-25 14:23:01 +01:00
Barbara Borges Ribeiro
6cc2f875b0 fix tests 2016-10-25 14:17:02 +01:00
Barbara Borges Ribeiro
bcbc6b8b92 allow for Radio Buttons to have choice names using HTML 2016-10-25 13:47:32 +01:00
Joe Cheng
e133290c57 Fix #1399: Duplicate binding error with insertUI and nested uiOutput (#1402)
* Fix #1399: Duplicate binding error with insertUI and nested uiOutput

* Update NEWS.md
2016-10-18 20:22:02 -05:00
shrektan
1429b0677e fix a typo: option() -> options() 2016-10-18 14:56:38 -05:00
Barbara Borges Ribeiro
d03ee36647 Fixes #1427: add event delegation so that modals do not close by mistake (#1430)
* Fixes #1427: add event delegation so that modal does not close when an element inside it is triggered as hidden

* use `this === e.target` instead

* added NEWS item

* `e.target` must be equal to `$(#shiny-modal)`, not `this`
2016-10-18 14:54:27 -05:00
Winston Chang
6e5880c642 Bump version and update NEWS 2016-10-18 13:51:43 -05:00
Winston Chang
fa93cffafb Add entry to staticdocs 2016-10-18 13:51:43 -05:00
Winston Chang
ce9af0fb57 Export function that applies input handlers 2016-10-18 13:51:43 -05:00
Winston Chang
95700d8d51 Fix dategrange comment 2016-10-17 13:37:25 -05:00
Winston Chang
fb15e98519 Merge pull request #1429 from rstudio/slider-setvalue
Make sliderInputBinding.setValue update value immediately
2016-10-17 12:29:14 -05:00
Winston Chang
3054cb7971 Update NEWS 2016-10-17 12:28:36 -05:00
Winston Chang
f84587cf5a Grunt 2016-10-17 12:22:21 -05:00
Winston Chang
538f38f314 sliderInputBinding: setValue changes value immediately 2016-10-17 12:22:21 -05:00
Winston Chang
06578349c7 Document InputBinding.subscribe's callback argument 2016-10-17 12:18:12 -05:00
Winston Chang
a807476171 sliderInputBinding: rename 'updating' to 'immediate' 2016-10-17 12:13:15 -05:00
Winston Chang
7aacf9ca89 Use Yarn instead of npm (#1416) 2016-10-12 12:51:05 -05:00
Winston Chang
50dae5fb83 Remove unneeded npm package 2016-10-11 13:04:38 -05:00
Winston Chang
0853c425fe Bump version to 0.14.1.9000 in DESCRIPTION 2016-10-11 12:59:06 -05:00
Barbara Borges Ribeiro
edcc676693 add "fade" arg to modalDialog() (#1414)
* add "fade" arg to modalDialog() that can be set to FALSE to remove default modal animation

* added documentation

* reflow comments

* news item
2016-10-10 15:03:25 -05:00
Winston Chang
c8a742a121 Bump version and update NEWS 2016-10-05 09:32:36 -05:00
Winston Chang
ee14a7e15f Merge tag 'v0.14.1'
Shiny 0.14.1 on CRAN
2016-10-05 09:29:07 -05:00
Winston Chang
e1eaccf409 Fix tests for compiled code on R-devel. Closes #1404 2016-10-03 16:23:11 -05:00
28 changed files with 2355 additions and 120 deletions

View File

@@ -1,7 +1,7 @@
Package: shiny
Type: Package
Title: Web Application Framework for R
Version: 0.14.1
Version: 0.14.1.9001
Authors@R: c(
person("Winston", "Chang", role = c("aut", "cre"), email = "winston@rstudio.com"),
person("Joe", "Cheng", role = "aut", email = "joe@rstudio.com"),

View File

@@ -38,6 +38,7 @@ export(actionButton)
export(actionLink)
export(addResourcePath)
export(animationOptions)
export(applyInputHandlers)
export(as.shiny.appobj)
export(basicPage)
export(bookmarkButton)

23
NEWS.md
View File

@@ -1,3 +1,26 @@
shiny 0.14.1.9001
============
## Full changelog
### Minor new features and improvements
* HTML markup can now be used in the choices' names for radio buttons. ([#1439](https://github.com/rstudio/shiny/pull/1439))
* Added a `fade` argument to `modalDialog()` -- setting it to `FALSE` will remove the usual fade-in animation for that modal window. ([#1414](https://github.com/rstudio/shiny/pull/1414))
* Exported function to apply input handlers to input values. This can be used for testing Shiny applications. ([#1421](https://github.com/rstudio/shiny/pull/1421))
* Fixed a "duplicate binding" error that occurred in some edge cases involving `insertUI` and nested `uiOutput`. ([#1402](https://github.com/rstudio/shiny/pull/1402))
### Bug fixes
* Fixed [#1427](https://github.com/rstudio/shiny/issues/1427): make sure that modals do not close incorrectly when an element inside them is triggered as hidden. ([#1430](https://github.com/rstudio/shiny/pull/1430))
* Fixed [#1404](https://github.com/rstudio/shiny/issues/1404): stack trace tests were not compatible with the byte-code compiler in R-devel, which now tracks source references.
* `sliderInputBinding.setValue()` now sends a slider's value immediately, instead of waiting for the usual 250ms debounce delay. ([#1429](https://github.com/rstudio/shiny/pull/1429))
shiny 0.14.1
============

View File

@@ -231,13 +231,13 @@ shinyAppDir_serverR <- function(appDir, options=list()) {
# ignored when checking extensions. If any changes are detected, all connected
# Shiny sessions are reloaded.
#
# Use option(shiny.autoreload = TRUE) to enable this behavior. Since monitoring
# Use options(shiny.autoreload = TRUE) to enable this behavior. Since monitoring
# for changes is expensive (we are polling for mtimes here, nothing fancy) this
# feature is intended only for development.
#
# You can customize the file patterns Shiny will monitor by setting the
# shiny.autoreload.pattern option. For example, to monitor only ui.R:
# option(shiny.autoreload.pattern = glob2rx("ui.R"))
# options(shiny.autoreload.pattern = glob2rx("ui.R"))
#
# The return value is a function that halts monitoring when called.
initAutoReloadMonitor <- function(dir) {

View File

@@ -76,7 +76,7 @@ getCallNames <- function(calls) {
}
getLocs <- function(calls) {
sapply(calls, function(call) {
vapply(calls, function(call) {
srcref <- attr(call, "srcref", exact = TRUE)
if (!is.null(srcref)) {
srcfile <- attr(srcref, "srcfile", exact = TRUE)
@@ -86,7 +86,7 @@ getLocs <- function(calls) {
}
}
return("")
})
}, character(1))
}
#' @details \code{captureStackTraces} runs the given \code{expr} and if any

View File

@@ -51,10 +51,10 @@ generateOptions <- function(inputId, choices, selected, inline, type = 'checkbox
# If inline, there's no wrapper div, and the label needs a class like
# checkbox-inline.
if (inline) {
tags$label(class = paste0(type, "-inline"), inputTag, tags$span(name))
tags$label(class = paste0(type, "-inline"), inputTag, tags$span(HTML(name)))
} else {
tags$div(class = type,
tags$label(inputTag, tags$span(name))
tags$label(inputTag, tags$span(HTML(name)))
)
}
},

View File

@@ -43,6 +43,8 @@ removeModal <- function(session = getDefaultReactiveDomain()) {
#' \code{FALSE} (the default), the modal dialog can't be dismissed in those
#' ways; instead it must be dismissed by clicking on the dismiss button, or
#' from a call to \code{\link{removeModal}} on the server.
#' @param fade If \code{FALSE}, the modal dialog will have no fade-in animation
#' (it will simply appear rather than fade in to view).
#'
#' @examples
#' if (interactive()) {
@@ -143,11 +145,12 @@ removeModal <- function(session = getDefaultReactiveDomain()) {
#' }
#' @export
modalDialog <- function(..., title = NULL, footer = modalButton("Dismiss"),
size = c("m", "s", "l"), easyClose = FALSE) {
size = c("m", "s", "l"), easyClose = FALSE, fade = TRUE) {
size <- match.arg(size)
div(id = "shiny-modal", class = "modal fade", tabindex = "-1",
cls <- if (fade) "modal fade" else "modal"
div(id = "shiny-modal", class = cls, tabindex = "-1",
`data-backdrop` = if (!easyClose) "static",
`data-keyboard` = if (!easyClose) "false",

View File

@@ -71,6 +71,72 @@ removeInputHandler <- function(type){
inputHandlers$remove(type)
}
# Apply input handler to a single input value
applyInputHandler <- function(name, val) {
splitName <- strsplit(name, ':')[[1]]
if (length(splitName) > 1) {
if (!inputHandlers$containsKey(splitName[[2]])) {
# No input handler registered for this type
stop("No handler registered for type ", name)
}
inputName <- splitName[[1]]
# Get the function for processing this type of input
inputHandler <- inputHandlers$get(splitName[[2]])
return(inputHandler(val, shinysession, inputName))
} else if (is.list(val) && is.null(names(val))) {
return(unlist(val, recursive = TRUE))
} else {
return(val)
}
}
#' Apply input handlers to raw input values
#'
#' The purpose of this function is to make it possible for external packages to
#' test Shiny inputs. It takes a named list of raw input values, applies input
#' handlers to those values, and then returns a named list of the processed
#' values.
#'
#' The raw input values should be in a named list. Some values may have names
#' like \code{"x:shiny.date"}. This function would apply the \code{"shiny.date"}
#' input handler to the value, and then rename the result to \code{"x"}, in the
#' output.
#'
#' @param inputs A named list of input values.
#'
#' @seealso registerInputHandler
#'
#' @examples
#' applyInputHandlers(list(
#' "m1" = list(list(1, 2), list(3, 4)),
#' "m2:shiny.matrix" = list(list(1, 2), list(3, 4)),
#'
#' "d1" = "2016-01-01",
#' "d2:shiny.date" = "2016-01-01", # Date object
#'
#' "n1" = NULL,
#' "n2:shiny.number" = NULL # Converts to NA
#' ))
#' @export
applyInputHandlers <- function(inputs) {
inputs <- mapply(applyInputHandler, names(inputs), inputs, SIMPLIFY = FALSE)
# Convert names like "button1:shiny.action" to "button1"
names(inputs) <- vapply(
names(inputs),
function(name) { strsplit(name, ":")[[1]][1] },
FUN.VALUE = character(1)
)
inputs
}
# Takes a list-of-lists and returns a matrix. The lists
# must all be the same length. NULL is replaced by NA.
registerInputHandler("shiny.matrix", function(data, ...) {

View File

@@ -247,38 +247,7 @@ createAppHandlers <- function(httpHandlers, serverFuncSource) {
withRestoreContext(shinysession$restoreContext, {
unpackInput <- function(name, val) {
splitName <- strsplit(name, ':')[[1]]
if (length(splitName) > 1) {
if (!inputHandlers$containsKey(splitName[[2]])) {
# No input handler registered for this type
stop("No handler registered for type ", name)
}
inputName <- splitName[[1]]
# Get the function for processing this type of input
inputHandler <- inputHandlers$get(splitName[[2]])
return(inputHandler(val, shinysession, inputName))
} else if (is.list(val) && is.null(names(val))) {
return(unlist(val, recursive = TRUE))
} else {
return(val)
}
}
msg$data <- mapply(unpackInput, names(msg$data), msg$data,
SIMPLIFY = FALSE)
# Convert names like "button1:shiny.action" to "button1"
names(msg$data) <- vapply(
names(msg$data),
function(name) { strsplit(name, ":")[[1]][1] },
FUN.VALUE = character(1)
)
msg$data <- applyInputHandlers(msg$data)
switch(
msg$method,

View File

@@ -53,10 +53,10 @@ NULL
#'
#' You can customize the file patterns Shiny will monitor by setting the
#' shiny.autoreload.pattern option. For example, to monitor only ui.R:
#' \code{option(shiny.autoreload.pattern = glob2rx("ui.R"))}
#' \code{options(shiny.autoreload.pattern = glob2rx("ui.R"))}
#'
#' The default polling interval is 500 milliseconds. You can change this
#' by setting e.g. \code{option(shiny.autoreload.interval = 2000)} (every
#' by setting e.g. \code{options(shiny.autoreload.interval = 2000)} (every
#' two seconds).}
#' \item{shiny.reactlog}{If \code{TRUE}, enable logging of reactive events,
#' which can be viewed later with the \code{\link{showReactLog}} function.

View File

@@ -184,6 +184,7 @@ sd_section("Utility functions",
"safeError",
"onFlush",
"restoreInput",
"applyInputHandlers",
"exprToFunction",
"installExprFunction",
"parseQueryString",

View File

@@ -1,6 +1,6 @@
'use strict';
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; };
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
//---------------------------------------------------------------------
// Source file: ../srcjs/_start.js
@@ -1361,7 +1361,7 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
var fadeDuration = 250;
function show() {
var _ref = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var _ref$html = _ref.html;
var html = _ref$html === undefined ? '' : _ref$html;
@@ -1520,7 +1520,7 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
// content is non-Bootstrap. Bootstrap modals require some special handling,
// which is coded in here.
show: function show() {
var _ref2 = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var _ref2$html = _ref2.html;
var html = _ref2$html === undefined ? '' : _ref2$html;
@@ -1529,7 +1529,7 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
// If there was an existing Bootstrap modal, then there will be a modal-
// backdrop div that was added outside of the modal wrapper, and it must be
// backdrop div that was added outside of the modal wrapper, and it must be
// removed; otherwise there can be multiple of these divs.
$('.modal-backdrop').remove();
@@ -1541,9 +1541,11 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
// If the wrapper's content is a Bootstrap modal, then when the inner
// modal is hidden, remove the entire thing, including wrapper.
$modal.on('hidden.bs.modal', function () {
exports.unbindAll($modal);
$modal.remove();
$modal.on('hidden.bs.modal', function (e) {
if (e.target === $("#shiny-modal")[0]) {
exports.unbindAll($modal);
$modal.remove();
}
});
}
@@ -3075,7 +3077,7 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
// inputs/outputs. `content` can be null, a string, or an object with
// properties 'html' and 'deps'.
exports.renderContent = function (el, content) {
var where = arguments.length <= 2 || arguments[2] === undefined ? "replace" : arguments[2];
var where = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : "replace";
exports.unbindAll(el);
@@ -3112,7 +3114,7 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
// Render HTML in a DOM element, inserting singletons into head as needed
exports.renderHtml = function (html, el, dependencies) {
var where = arguments.length <= 3 || arguments[3] === undefined ? 'replace' : arguments[3];
var where = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'replace';
renderDependencies(dependencies);
return singletons.renderHtml(html, el, where);
@@ -3414,6 +3416,10 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
this.getValue = function (el) {
throw "Not implemented";
};
// The callback method takes one argument, whose value is boolean. If true,
// allow deferred (debounce or throttle) sending depending on the value of
// getRatePolicy. If false, send value immediately.
this.subscribe = function (el, callback) {};
this.unsubscribe = function (el) {};
@@ -3646,18 +3652,25 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
}
},
setValue: function setValue(el, value) {
var slider = $(el).data('ionRangeSlider');
var $el = $(el);
var slider = $el.data('ionRangeSlider');
if (this._numValues(el) == 2 && value instanceof Array) {
slider.update({ from: value[0], to: value[1] });
} else {
slider.update({ from: value });
$el.data('immediate', true);
try {
if (this._numValues(el) == 2 && value instanceof Array) {
slider.update({ from: value[0], to: value[1] });
} else {
slider.update({ from: value });
}
forceIonSliderUpdate(slider);
} finally {
$el.data('immediate', false);
}
forceIonSliderUpdate(slider);
},
subscribe: function subscribe(el, callback) {
$(el).on('change.sliderInputBinding', function (event) {
callback(!$(el).data('updating') && !$(el).data('animating'));
callback(!$(el).data('immediate') && !$(el).data('animating'));
});
},
unsubscribe: function unsubscribe(el) {
@@ -3682,12 +3695,12 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
if (data.hasOwnProperty('label')) $el.parent().find('label[for="' + $escape(el.id) + '"]').text(data.label);
$el.data('updating', true);
$el.data('immediate', true);
try {
slider.update(msg);
forceIonSliderUpdate(slider);
} finally {
$el.data('updating', false);
$el.data('immediate', false);
}
},
getRatePolicy: function getRatePolicy() {
@@ -4031,8 +4044,8 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
return [formatDateUTC(start), formatDateUTC(end)];
},
// value must be an array of unambiguous strings like '2001-01-01', or
// Date objects.
// value must be an object, with optional fields `start` and `end`. These
// should be unambiguous strings like '2001-01-01', or Date objects.
setValue: function setValue(el, value) {
if (!(value instanceof Object)) {
return;
@@ -4848,7 +4861,7 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
var shinyapp = exports.shinyapp = new ShinyApp();
function bindOutputs() {
var scope = arguments.length <= 0 || arguments[0] === undefined ? document : arguments[0];
var scope = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : document;
scope = $(scope);
@@ -4864,6 +4877,11 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
// Check if ID is falsy
if (!id) continue;
// In some uncommon cases, elements that are later in the
// matches array can be removed from the document by earlier
// iterations. See https://github.com/rstudio/shiny/issues/1399
if (!$.contains(document, el)) continue;
var $el = $(el);
if ($el.hasClass('shiny-bound-output')) {
// Already bound; can happen with nested uiOutput (bindAll
@@ -4889,8 +4907,8 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
}
function unbindOutputs() {
var scope = arguments.length <= 0 || arguments[0] === undefined ? document : arguments[0];
var includeSelf = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1];
var scope = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : document;
var includeSelf = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
var outputs = $(scope).find('.shiny-bound-output');
@@ -4952,7 +4970,7 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
}
function bindInputs() {
var scope = arguments.length <= 0 || arguments[0] === undefined ? document : arguments[0];
var scope = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : document;
var bindings = inputBindings.getBindings();
@@ -5010,8 +5028,8 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
}
function unbindInputs() {
var scope = arguments.length <= 0 || arguments[0] === undefined ? document : arguments[0];
var includeSelf = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1];
var scope = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : document;
var includeSelf = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
var inputs = $(scope).find('.shiny-bound-input');
@@ -5040,7 +5058,7 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
return bindInputs(scope);
}
function unbindAll(scope) {
var includeSelf = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1];
var includeSelf = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
unbindInputs(scope, includeSelf);
unbindOutputs(scope, includeSelf);
@@ -5065,7 +5083,7 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
// Calls .initialize() for all of the input objects in all input bindings,
// in the given scope.
function initializeInputs() {
var scope = arguments.length <= 0 || arguments[0] === undefined ? document : arguments[0];
var scope = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : document;
var bindings = inputBindings.getBindings();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

39
man/applyInputHandlers.Rd Normal file
View File

@@ -0,0 +1,39 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/server-input-handlers.R
\name{applyInputHandlers}
\alias{applyInputHandlers}
\title{Apply input handlers to raw input values}
\usage{
applyInputHandlers(inputs)
}
\arguments{
\item{inputs}{A named list of input values.}
}
\description{
The purpose of this function is to make it possible for external packages to
test Shiny inputs. It takes a named list of raw input values, applies input
handlers to those values, and then returns a named list of the processed
values.
}
\details{
The raw input values should be in a named list. Some values may have names
like \code{"x:shiny.date"}. This function would apply the \code{"shiny.date"}
input handler to the value, and then rename the result to \code{"x"}, in the
output.
}
\examples{
applyInputHandlers(list(
"m1" = list(list(1, 2), list(3, 4)),
"m2:shiny.matrix" = list(list(1, 2), list(3, 4)),
"d1" = "2016-01-01",
"d2:shiny.date" = "2016-01-01", # Date object
"n1" = NULL,
"n2:shiny.number" = NULL # Converts to NA
))
}
\seealso{
registerInputHandler
}

View File

@@ -5,7 +5,7 @@
\title{Create a modal dialog UI}
\usage{
modalDialog(..., title = NULL, footer = modalButton("Dismiss"),
size = c("m", "s", "l"), easyClose = FALSE)
size = c("m", "s", "l"), easyClose = FALSE, fade = TRUE)
}
\arguments{
\item{...}{UI elements for the body of the modal dialog box.}
@@ -22,6 +22,9 @@ clicking outside the dialog box, or be pressing the Escape key. If
\code{FALSE} (the default), the modal dialog can't be dismissed in those
ways; instead it must be dismissed by clicking on the dismiss button, or
from a call to \code{\link{removeModal}} on the server.}
\item{fade}{If \code{FALSE}, the modal dialog will have no fade-in animation
(it will simply appear rather than fade in to view).}
}
\description{
This creates the UI for a modal dialog, using Bootstrap's modal class. Modals

View File

@@ -27,10 +27,10 @@ Since monitoring for changes is expensive (we simply poll for last
You can customize the file patterns Shiny will monitor by setting the
shiny.autoreload.pattern option. For example, to monitor only ui.R:
\code{option(shiny.autoreload.pattern = glob2rx("ui.R"))}
\code{options(shiny.autoreload.pattern = glob2rx("ui.R"))}
The default polling interval is 500 milliseconds. You can change this
by setting e.g. \code{option(shiny.autoreload.interval = 2000)} (every
by setting e.g. \code{options(shiny.autoreload.interval = 2000)} (every
two seconds).}
\item{shiny.reactlog}{If \code{TRUE}, enable logging of reactive events,
which can be viewed later with the \code{\link{showReactLog}} function.

View File

@@ -18,6 +18,12 @@ function initShiny() {
if (!id)
continue;
// In some uncommon cases, elements that are later in the
// matches array can be removed from the document by earlier
// iterations. See https://github.com/rstudio/shiny/issues/1399
if (!$.contains(document, el))
continue;
var $el = $(el);
if ($el.hasClass('shiny-bound-output')) {
// Already bound; can happen with nested uiOutput (bindAll

View File

@@ -14,6 +14,10 @@ this.getId = function(el) {
// to deserialize the JSON correctly
this.getType = function() { return false; };
this.getValue = function(el) { throw "Not implemented"; };
// The callback method takes one argument, whose value is boolean. If true,
// allow deferred (debounce or throttle) sending depending on the value of
// getRatePolicy. If false, send value immediately.
this.subscribe = function(el, callback) { };
this.unsubscribe = function(el) { };

View File

@@ -12,8 +12,8 @@ $.extend(dateRangeInputBinding, dateInputBinding, {
return [formatDateUTC(start), formatDateUTC(end)];
},
// value must be an array of unambiguous strings like '2001-01-01', or
// Date objects.
// value must be an object, with optional fields `start` and `end`. These
// should be unambiguous strings like '2001-01-01', or Date objects.
setValue: function(el, value) {
if (!(value instanceof Object)) {
return;

View File

@@ -53,18 +53,25 @@ $.extend(sliderInputBinding, textInputBinding, {
},
setValue: function(el, value) {
var slider = $(el).data('ionRangeSlider');
var $el = $(el);
var slider = $el.data('ionRangeSlider');
if (this._numValues(el) == 2 && value instanceof Array) {
slider.update({ from: value[0], to: value[1] });
} else {
slider.update({ from: value });
$el.data('immediate', true);
try {
if (this._numValues(el) == 2 && value instanceof Array) {
slider.update({ from: value[0], to: value[1] });
} else {
slider.update({ from: value });
}
forceIonSliderUpdate(slider);
} finally {
$el.data('immediate', false);
}
forceIonSliderUpdate(slider);
},
subscribe: function(el, callback) {
$(el).on('change.sliderInputBinding', function(event) {
callback(!$(el).data('updating') && !$(el).data('animating'));
callback(!$(el).data('immediate') && !$(el).data('animating'));
});
},
unsubscribe: function(el) {
@@ -90,12 +97,12 @@ $.extend(sliderInputBinding, textInputBinding, {
if (data.hasOwnProperty('label'))
$el.parent().find('label[for="' + $escape(el.id) + '"]').text(data.label);
$el.data('updating', true);
$el.data('immediate', true);
try {
slider.update(msg);
forceIonSliderUpdate(slider);
} finally {
$el.data('updating', false);
$el.data('immediate', false);
}
},
getRatePolicy: function() {

View File

@@ -7,7 +7,7 @@ exports.modal = {
show: function({ html='', deps=[] } = {}) {
// If there was an existing Bootstrap modal, then there will be a modal-
// backdrop div that was added outside of the modal wrapper, and it must be
// backdrop div that was added outside of the modal wrapper, and it must be
// removed; otherwise there can be multiple of these divs.
$('.modal-backdrop').remove();
@@ -19,9 +19,11 @@ exports.modal = {
// If the wrapper's content is a Bootstrap modal, then when the inner
// modal is hidden, remove the entire thing, including wrapper.
$modal.on('hidden.bs.modal', function() {
exports.unbindAll($modal);
$modal.remove();
$modal.on('hidden.bs.modal', function(e) {
if (e.target === $("#shiny-modal")[0]) {
exports.unbindAll($modal);
$modal.remove();
}
});
}

View File

@@ -33,15 +33,15 @@ test_that("Repeated names for selectInput and radioButtons choices", {
x <- radioButtons('id','label', choices = c(a='x1', a='x2', b='x3'))
choices <- x$children
expect_equal(choices[[2]]$children[[1]][[1]]$children[[1]]$children[[2]]$children[[1]], 'a')
expect_equal(choices[[2]]$children[[1]][[1]]$children[[1]]$children[[2]]$children[[1]], HTML('a'))
expect_equal(choices[[2]]$children[[1]][[1]]$children[[1]]$children[[1]]$attribs$value, 'x1')
expect_equal(choices[[2]]$children[[1]][[1]]$children[[1]]$children[[1]]$attribs$checked, 'checked')
expect_equal(choices[[2]]$children[[1]][[2]]$children[[1]]$children[[2]]$children[[1]], 'a')
expect_equal(choices[[2]]$children[[1]][[2]]$children[[1]]$children[[2]]$children[[1]], HTML('a'))
expect_equal(choices[[2]]$children[[1]][[2]]$children[[1]]$children[[1]]$attribs$value, 'x2')
expect_equal(choices[[2]]$children[[1]][[2]]$children[[1]]$children[[1]]$attribs$checked, NULL)
expect_equal(choices[[2]]$children[[1]][[3]]$children[[1]]$children[[2]]$children[[1]], 'b')
expect_equal(choices[[2]]$children[[1]][[3]]$children[[1]]$children[[2]]$children[[1]], HTML('b'))
expect_equal(choices[[2]]$children[[1]][[3]]$children[[1]]$children[[1]]$attribs$value, 'x3')
expect_equal(choices[[2]]$children[[1]][[3]]$children[[1]]$children[[1]]$attribs$checked, NULL)
})

View File

@@ -13,7 +13,15 @@ causeError <- function(full) {
B()
})
res <- try(captureStackTraces(isolate(renderTable({C()}, server = FALSE)())),
res <- try({
captureStackTraces({
isolate({
renderTable({
C()
}, server = FALSE)()
})
})
},
silent = TRUE)
cond <- attr(res, "condition", exact = TRUE)
@@ -50,7 +58,7 @@ test_that("integration tests", {
"isolate", "withCallingHandlers", "captureStackTraces", "doTryCatch",
"tryCatchOne", "tryCatchList", "tryCatch", "try"))
expect_equal(nzchar(df$loc), c(TRUE, TRUE, TRUE, FALSE, TRUE,
FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE,
FALSE, FALSE, FALSE, FALSE, TRUE, FALSE, TRUE, FALSE, FALSE,
FALSE, FALSE))
df <- causeError(full = TRUE)
@@ -72,8 +80,8 @@ test_that("integration tests", {
"tryCatch", "try"))
expect_equal(nzchar(df$loc), c(FALSE, FALSE, FALSE, TRUE,
TRUE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE,
FALSE, FALSE, FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, FALSE,
FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE,
FALSE, FALSE, FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, TRUE,
FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE, FALSE, TRUE,
FALSE, FALSE, FALSE, FALSE))
})

View File

@@ -1,30 +1,49 @@
This directory contains build tools for Shiny.
## Grunt
## JavaScript build tools
Grunt is a build tool that runs on node.js. In Shiny, it is used for concatenating, minifying, and linting Javascript code.
### Installing Grunt
### First-time setup
Grunt requires Node.js and npm (the Node.js package manager). Installation of these programs differs across platforms and is generally pretty easy, so I won't include instructions here.
Shiny's JavaScript build tools use Node.js, along with [yarn](https://yarnpkg.com/) to manage the JavaScript packages.
Once node and npm are installed, install grunt:
Installation of Node.js differs across platforms and is generally pretty easy, so I won't include instructions here.
There are a number of ways to [install yarn](https://yarnpkg.com/en/docs/install), but if you already have npm installed, you can simply run:
```
sudo npm install --global yarn
```
Then, in this directory (tools/), run the following to install the packages:
```
yarn
```
If in the future you want to upgrade or add a package, run:
```
yarn add --dev [packagename]
```
If someone else updates the package.json and/or yarn.lock files, simply run `yarn` to update your packages.
For information about upgrading or installing new packages, see the [yarn workflow documentation](https://yarnpkg.com/en/docs/yarn-workflow).
### Grunt
Grunt is a build tool that runs on node.js and will be installed. In Shiny, it is used for concatenating, minifying, and linting Javascript code.
#### Installing Grunt
```
# Install grunt command line tool globally
sudo npm install -g grunt-cli
# Install grunt plus modules for this project
npm install
# To update modules in the future
npm update
sudo yarn global add grunt-cli
```
Note: The `package.json` file contains a reference to `estraverse-fb`. This is needed only because the current version of ESLint has a [bug](https://github.com/eslint/eslint/issues/5476). At some point in the future, it can be removed.
### Using Grunt
To run all default grunt tasks (concatenation, minification, and jshint), simply go into the `tools` directory and run:
@@ -58,7 +77,7 @@ Updating web libraries
To update the version of babel-polyfill:
* Check if there is a newer version available by running `npm outdated babel-polyfill`. (If there's no output, then you have the latest version.)
* Run `npm install babel-polyfill --save-dev --save-exact`.
* Check if there is a newer version available by running `yarn outdated babel-polyfill`. (If there's no output, then you have the latest version.)
* Run `yarn add --dev babel-polyfill --exact`.
* Edit R/shinyui.R. The `renderPage` function has an `htmlDependency` for
`babel-polyfill`. Update this to the new version number.

View File

@@ -4,7 +4,6 @@
"babel-polyfill": "6.7.2",
"babel-preset-es2015": "^6.6.0",
"eslint-stylish-mapped": "^1.0.0",
"estraverse-fb": "^1.3.1",
"grunt": "~1.0.0",
"grunt-babel": "^6.0.0",
"grunt-contrib-clean": "^1.0.0",

2067
tools/yarn.lock Normal file

File diff suppressed because it is too large Load Diff