I would like to embed numericInput and checkBoxInput in a data table. I have an example from Xie Yihui that works perfectly fine. Exactly like what I wanted.
But when the code is wrapped in a module, the edited table does not update. I read that this could be due to namespace but I do not know how to fix it.
Here are the Gists:
Gist: edit DataTable in Shiny App
Gist: edit DataTable in Shiny Module
Thanks in advance :)
There was just one little fix, which is of course hard to see if you're not familiar with Shiny modules. Every Input ID you create in the Module Server function has to be wrapped in ns (as you do in the UI), but this function is hidden in session$ns in the server function. So in line 18, where the inputs are created, their ID had to be adjusted using session$ns. From
inputs[i] = as.character(FUN(paste0(id, i), label = NULL, ...))
to
inputs[i] = as.character(FUN(paste0(session$ns(id), i), label = NULL, ...))
Full code:
library(shiny)
library(DT)
editTableUI <- function(id) {
ns <- NS(id)
tagList(
DT::dataTableOutput(ns('x1')),
verbatimTextOutput(ns('x2'))
)
}
editTable <- function(input, output, session) {
# create a character vector of shiny inputs
shinyInput = function(FUN, len, id, ...) {
inputs = character(len)
for (i in seq_len(len)) {
inputs[i] = as.character(FUN(paste0(session$ns(id), i), label = NULL, ...))
}
inputs
}
# obtain the values of inputs
shinyValue = function(id, len) {
unlist(lapply(seq_len(len), function(i) {
value = input[[paste0(id, i)]]
if (is.null(value)) NA else value
}))
}
# a sample data frame
res = data.frame(
v1 = shinyInput(numericInput, 100, 'v1_', value = 0),
v2 = shinyInput(checkboxInput, 100, 'v2_', value = TRUE),
v3 = rnorm(100),
v4 = sample(LETTERS, 100, TRUE),
stringsAsFactors = FALSE
)
# render the table containing shiny inputs
output$x1 = DT::renderDataTable(
res, server = FALSE, escape = FALSE, options = list(
preDrawCallback = JS('function() {
Shiny.unbindAll(this.api().table().node()); }'),
drawCallback = JS('function() {
Shiny.bindAll(this.api().table().node()); } ')
)
)
# print the values of inputs
output$x2 = renderPrint({
data.frame(v1 = shinyValue('v1_', 100), v2 = shinyValue('v2_', 100))
})
}
shinyApp(
ui = fluidPage(
editTableUI("test")
),
server = function(input, output) {
callModule(editTable, "test")
}
)
Related
I'm adding a "Remove" button in a datatable to remove row. It works in a shiny app but not in a shiny module. I am pretty sure it's a namespace problem, somewhere in the shinyInput function or getRemoveButton function but I do not now how to fix it.
library(DT)
library(dplyr)
library(purrr)
library(shiny)
getRemoveButton <- function(name, idS = "", lab = "Pit") {
if (stringr::str_length(idS) > 0) idS <- paste0(idS, "-")
ret <- shinyInput(actionButton, name,
'button_', label = "Remove",
onclick = sprintf('Shiny.onInputChange(\"%sremove_button_%s\", this.id)' ,idS, lab))
return(ret)
}
shinyInput <- function(FUN, name, id, ses, ...) {
n <- length(name)
inputs <- character(n)
for (i in seq_len(n)) {
inputs[i] <- as.character(FUN(paste0(id, i), ...))
}
inputs
}
uploadFigUI <- function(id) {
ns <- NS(id)
tagList(
fluidPage(
titlePanel("Uploading Files"),
sidebarLayout(
sidebarPanel(
fileInput(inputId = ns('files'),
label = 'Select an Image',
multiple = TRUE,
accept=c('image/png', 'image/jpeg')),
DT::dataTableOutput(ns("myTable"))
),
mainPanel(
uiOutput(ns('images'))
)
)
)
)
}
uploadFig <- function(input, output, session) {
ns <- session$ns
files <- eventReactive(input$files, {
req(input$files)
files <- input$files
files$datapath <- gsub("\\\\", "/", files$datapath)
files
})
values <- reactiveValues()
observeEvent(files(), {
if(is.null(values$tab)){
values$tab <- files() %>%
mutate(Remove = getRemoveButton(files()$name, idS = "", lab = "Tab1"))
}else{
tab <- files() %>%
mutate(Remove = getRemoveButton(files()$name, idS = "", lab = "Tab1"))
myTable <- bind_rows(values$tab,tab)
replaceData(proxyTable, myTable, resetPaging = FALSE)
values$tab <- myTable
}
})
output$images <- renderUI({
req(values$tab$datapath)
image_output_list <-
lapply(1:nrow(values$tab),
function(i)
{
imagename = ns(paste0("image", i))
uiOutput(imagename)
})
do.call(tagList, image_output_list)
})
observe({
req(values$tab$datapath)
for (i in 1:nrow(values$tab))
{
print(i)
local({
my_i <- i
imagename = paste0("image", my_i)
img <- knitr::image_uri(values$tab$datapath[my_i])
values$img[[i]] <- img
output[[imagename]] <- renderUI({
req(values$img[[i]])
tags$img(src = img, width = "100%", height= "auto")
})
})
}
})
proxyTable <- DT::dataTableProxy("tab")
output$myTable <- DT::renderDataTable({
req(values$tab)
DT::datatable(values$tab %>%
select(-datapath),
options = list(pageLength = 25,
dom = "rt"),
rownames = FALSE,
escape = FALSE,
editable = FALSE)
})
observeEvent(input$remove_button_Tab1, {
myTable <- values$tab
values$what <- str_replace(input$remove_button_Tab1, "button_", "")
s <- str_replace(input$remove_button_Tab1, "button_", "") %>% as.numeric()
myTable <- myTable[-s, ]
replaceData(proxyTable, myTable, resetPaging = FALSE)
values$tab <- myTable
})
}
shinyApp(
ui = fluidPage(
uploadFigUI("test")
),
server = function(input, output) {
suppfigure <- callModule(uploadFig, "test")
}
)
If this "Remove" button works, it should delete the row and the picture on the main panel.
First problem: the proxy should be proxyTable <- DT::dataTableProxy("myTable").
Second problem: you must prefix the id of the remove button with the namespace:
getRemoveButton(files()$name, idS = "test", lab = "Tab1")
This works like this.
I ran in to an issue in generating oxyplot with dynamic data. This is the first time I'm using oxyplot with dynamic data.
public async Task<PlotModel> AreaChart_NoOfPoliciesAsync()
{
var plotModel1 = new PlotModel { Title = "Number of Policies with last year" };
plotModel1.InvalidatePlot(true);
try
{
var s1 = new AreaSeries()
{
Title = "Number of policies in last year",
Color = OxyColors.LightPink,
MarkerType = MarkerType.Circle,
MarkerSize = 6,
MarkerStroke = OxyColors.White,
MarkerFill = OxyColors.LightPink,
MarkerStrokeThickness = 1.5
};
string year_str = DateTime.Today.AddYears(-1).ToString("yyyy");
int running_month = 1;
string running_month_str;
List<MonthlyPerformance> last_year = await _apiServices.GetMonthlyPerformance(Settings.AccessToken, Settings.agentCode, year_str);
last_year = last_year.FindAll(x => x.BUSS_TYPE == "Total");
last_year.Sort((x, y) => x.Y_MONTH.CompareTo(y.Y_MONTH));
s1.Points.Add(new DataPoint(0, 0));
foreach (MonthlyPerformance item in last_year)
{
s1.Points.Add(new DataPoint(running_month, item.NO_BUSINESS));
running_month++;
}
plotModel1.Series.Add(s1);
plotModel1.Axes.Add(new LinearAxis { Position = AxisPosition.Left, IsPanEnabled = false, IsZoomEnabled = false });
plotModel1.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom, IsPanEnabled = false, IsZoomEnabled = false });
return plotModel1;}
The plot generated with hard coded values used for demo. But when the function
List<MonthlyPerformance> last_year = await _apiServices.GetMonthlyPerformance(Settings.AccessToken, Settings.agentCode, year_str);
is called, oxyplot is not being generated. I just get a blank space. I dont get any compilation errors.
I'm using MVVM. Could someone please point out what I'm doing wrong here.
Thanks in advance.
Once await and async were removed, the chart appeared.
I don't think it's the right way to handled the issue. But I was running out of development time.
I have created a simple function that would "animate" the cell backcolor at a tap, it works perfectly fine:
Color nOldColor = _grid.BackgroundColor;
for (int i = 0; i <= 100; i += 5)
{
double f = (double)i / (double)100;
Color nNewColor = PCLHelper.BlendColors(nOldColor, Color.Red, f);
_grid.BackgroundColor = nNewColor;
_label1.BackgroundColor = nNewColor;
await Task.Delay(5);
}
_grid.BackgroundColor = nOldColor;
_label1.BackgroundColor = nOldColor;
Now I wanted to do the same with an Animation, but the animation doesn't show the steps "in-between" but rather (as it looks to me) switches to the final color:
private async void animateButtonTouched()
{
int repeatCountMax = 100;
Color nOldColor = _grid.BackgroundColor;
var repeatCount = 0;
_grid.Animate("changeBG", new Animation((val) =>
{
double f = (double)repeatCount / (double)100;
Color nNewColor = PCLHelper.BlendColors(nOldColor, Color.Red, f);
_grid.BackgroundColor = nNewColor;
_label1.BackgroundColor = nNewColor;
}),
5, //duration. I've also tried it with 100. Nothing helped
finished: (val, b) =>
{
repeatCount++;
}, repeat: () =>
{
return repeatCount < repeatCountMax;
});
What am I doing wrong?
"You are making it more difficult than it needs to be." Trademark pending 🍣
The Animate callback is providing the stepping value (or keyframe value). This is a double from 0 to 1 that is called ever X milliseconds (i.e. the length of a single animation frame, 16 default) over the course of X milliseconds (250 default).
So in this example the ShiftToColor gets called 125 times (2000 / 16) with a value that is evenly divided from 0 to 1, thus steps of .008.
var orgColor = aFormsElementInstance.BackgroundColor;
aFormsElementInstance.Animate("changeBG", new Animation((val) =>
{
Color ShiftToColor(Color From, Color To, double pct)
{
var r = From.R + ((To.R - From.R) * val);
var g = From.G + ((To.G - From.G) * val);
var b = From.B + ((To.B - From.B) * val);
return new Color(r, g, b);
}
Device.BeginInvokeOnMainThread(() =>
{
aFormsElementInstance.BackgroundColor = ShiftToColor(orgColor, Color.Red, val);
});
}), 16, 2000);
Results in:
I am facing a problem while calling multiple functions through AJAX. What i mainly have to do is to call the different functions through AJAX.
This should be working such that when i try to call those functions, they should be sent a request and the response should be the returned values of those function, but i am getting the error while doing so. Can anyone suggest me how to make it work?
public virtual IQueryable<CardViewModel> ChannelQuery(PageInfo p)
{
var q = from client in my.Clients(0, 0)
join m in db.Channels on client.ClientId equals m.ClientId
join c in db.Contents on m.ChannelId equals c.ContentId
where c.ContentName.Contains(p.Where) || m.ChannelShortDescription.Contains(p.Where) || m.ChannelName.Contains(p.Where)
select new CardViewModel
{
Name = "Channel",
ID = m.ChannelId,
Title = m.ChannelName,
Description = m.ChannelShortDescription,
Pic = new VZDev.ViewModels.Pic { width = 255, height = 170, source = `enter code here`m.ChannelLogo, text = m.ChannelName },
PictureViewTemplate = "_PicBanner",
ShowTools = true
};
return q;
}
public virtual IQueryable<CardViewModel> ContentsQueries(PageInfo p)
{
var query = from content in db.Contents
join clients in my.Clients(0, 0) on content.ClientId equals clients.ClientId
join m in db.Channels on clients.ClientId equals m.ClientId
where content.ContentName.Contains(p.Where) || `enter code here`m.ChannelShortDescription.Contains(p.Where) || m.ChannelName.Contains(p.Where)
select new CardViewModel
{
Name = "Content",
ID = content.ContentId,
Title = content.ContentName,
Description = clients.ResellerName,
Pic = new VZDev.ViewModels.Pic { width = 255, height = 170, source = `enter code here`"None", text = content.ContentName },
PictureViewTemplate = "_PicBanner",
ShowTools = true
};
return query;
}
public ViewResult Advance(PageInfo p)
{
return View(ChannelQuery(p).ToPagedResult(p, "ID Desc"));
}
You can only call one function per AJAX call. If you need Channel and Content data in one request, make a dictionary to hold both sets of values. Something like this:
Dictionary<string,object> data = new Dictionary<string,object>();
data.Add("Channel", ChannelQuery());
data.Add("Content", ContentsQuery());
I have documents with field xyz containing
{ term: "puppies", page: { skip: 1, per_page: 20 } } // not useful as a composite key...
{ page: { skip: 1, per_page: 20 }, term: "puppies" } // different order, same contents
For the sake of determining the "top" values in xyz, I want to map them all to something like
emit('term="puppies",page={ skip: 1, per_page: 20 }', 1); // composite key
but I can't get the embedded objects into a meaningful strings:
emit('term="puppies",page=[object bson_object]', 1); // not useful
Any suggestions for a function to use instead of toString()?
# return the top <num> values of <field> based on a query <selector>
#
# example: top(10, :xyz, {}, {})
def top(num, field, selector, opts = {})
m = ::BSON::Code.new <<-EOS
function() {
var keys = [];
for (var key in this.#{field}) {
keys.push(key);
}
keys.sort ();
var sortedKeyValuePairs = [];
for (i in keys) {
var key = keys[i];
var value = this.#{field}[key];
if (value.constructor.name == 'String') {
var stringifiedValue = value;
} else if (value.constructor.name == 'bson_object') {
// this just says "[object bson_object]" which is not useful
var stringifiedValue = value.toString();
} else {
var stringifiedValue = value.toString();
}
sortedKeyValuePairs.push([key, stringifiedValue].join('='));
}
// hopefully we'll end up with something like
// emit("term=puppies,page={skip:1, per_page:20}")
// instead of
// emit("term=puppies,page=[object bson_object]")
emit(sortedKeyValuePairs.join(','), 1);
}
EOS
r = ::BSON::Code.new <<-EOS
function(k, vals) {
var sum=0;
for (var i in vals) sum += vals[i];
return sum;
}
EOS
docs = []
collection.map_reduce(m, r, opts.merge(:query => selector)).find({}, :limit => num, :sort => [['value', ::Mongo::DESCENDING]]).each do |doc|
docs.push doc
end
docs
end
Given that MongoDB uses SpiderMonkey as its internal JS engine, can't you use JSON.stringify (will work even if/when MongoDB switches to V8) or SpiderMonkey's non-standard toSource method?
(sorry, can't try it ATM to confirm it'd work)
toSource method will do the work, but it adds also brackets.
for a clean document use:
value.toSource().substring(1, value.toSource().length - 1)