I'm building a Shiny app that lets users upload images to the server. I'd like to display the image on the screen without having to upload it first and then get the rendered output back. Is this possible?
This is my code right now. You can select an image file, which gets uploaded. The image is then rendered from the file on the server side, after it's been received. I'd like to avoid the roundtrip.
UI
fluidPage(
titlePanel("File upload"),
sidebarLayout(
sidebarPanel(
fileInput("img", "Choose image file",
accept=c("image/jpeg", "image/x-windows-bmp"))
),
mainPanel(
imageOutput("picture", width="500px", height="500px")
)
)
)
Server
function(input, output, session)
{
output$picture <- renderImage({
imgFile <- input$img
if(is.null(imgFile))
return(list(src=""))
list(src=imgFile$datapath, alt=imgFile$name, contentType=imgFile$type)
}, deleteFile=FALSE)
# do more stuff with the file
}
You can use package shinyjs to call FileReader from HTML 5 read here
library(shinyjs)
shinyApp(ui = fluidPage(
useShinyjs(),
titlePanel("File upload"),
sidebarLayout(
sidebarPanel(
fileInput("img", "Choose image file",
accept=c("image/jpeg", "image/x-windows-bmp")),
HTML('<output id="list"></output>')
),
mainPanel(
imageOutput("picture", width="500px", height="500px")
)
)),
server = function(input, output, session){
shinyjs::runjs("
function handleFileSelect(evt) {
var files = evt.target.files; // FileList object
// Loop through the FileList and render image files as thumbnails.
for (var i = 0, f; f = files[i]; i++) {
// Only process image files.
if (!f.type.match('image.*')) {
continue;
}
var reader = new FileReader();
// Closure to capture the file information.
reader.onload = (function(theFile) {
return function(e) {
// Render thumbnail.
var span = document.createElement('span');
span.innerHTML = ['<img class=\"thumb\" src=\"', e.target.result,
'\" title=\"', escape(theFile.name), '\"/>'].join('');
document.getElementById('list').insertBefore(span, null);
};
})(f);
// Read in the image file as a data URL.
reader.readAsDataURL(f);
}
}
document.getElementById('img').addEventListener('change', handleFileSelect, false);")
output$picture <- renderImage({
imgFile <- input$img
if(is.null(imgFile))
return(list(src=""))
list(src=imgFile$datapath, alt=imgFile$name, contentType=imgFile$type)
}, deleteFile=FALSE)
})
Edit:
Okay, I now the question is clear for me, I hope :).
The problem is that pictures are added within <output id="list"></output>. So I would suggest clearing it before a new picture is added with: document.getElementById('list').innerHTML = ''
library(shiny)
library(shinyjs)
shinyApp(ui = fluidPage(
useShinyjs(),
titlePanel("File upload"),
sidebarLayout(
sidebarPanel(
fileInput("img", "Choose image file",
accept=c("image/jpeg", "image/x-windows-bmp"))
),
mainPanel(
HTML('<output id="list"></output>')
)
)),
server = function(input, output, session){
shinyjs::runjs("
function handleFileSelect(evt) {
document.getElementById('list').innerHTML = ''
var files = evt.target.files; // FileList object
// Loop through the FileList and render image files as thumbnails.
for (var i = 0, f; f = files[i]; i++) {
// Only process image files.
if (!f.type.match('image.*')) {
continue;
}
var reader = new FileReader();
// Closure to capture the file information.
reader.onload = (function(theFile) {
return function(e) {
// Render thumbnail.
var span = document.createElement('span');
span.innerHTML = ['<img class=\"thumb\" src=\"', e.target.result,
'\" title=\"', escape(theFile.name), '\"/>'].join('');
document.getElementById('list').insertBefore(span, null);
};
})(f);
// Read in the image file as a data URL.
reader.readAsDataURL(f);
}
}
document.getElementById('img').addEventListener('change', handleFileSelect, false);")
})
Related
The Shiny app below redirects to Google as soon as you press the button 'Submit'. I would like a similar app that redirects to two URLs (in sequence). So, for example it would go to "https://www.google.com" first and then to "https://www.stackoverflow.com". Is this possible?
library(shiny)
jscode <- "Shiny.addCustomMessageHandler('mymessage', function(message) { window.location = message;});"
ui <- fluidPage(
tags$head(tags$script(jscode)),
actionButton("submit", "Submit")
)
server <- function(input, output, session) {
observeEvent(input$submit, {
url <- "https://www.google.com"
session$sendCustomMessage("mymessage", url)
})
}
shinyApp(ui,server)
jscode <- "Shiny.addCustomMessageHandler('redirect', function(URLS) {
for (var i = 0; i < URLS.length; i++) {
setTimeout(function() {
var a = document.createElement('a');
a.style.display = 'none';
a.href = URLS[i];
a.target = '_blank';
document.body.appendChild(a);
a.click();
a.remove();
}, i * 500);
}
});"
observeEvent(input$submit, {
urls <- list("https://www.google.com", "https://www.stackoverflow.com")
session$sendCustomMessage("redirect", urls)
})
Seems like shiny cannot recognize my .js file. Question is why?
Inlined js script text runs smoothly:
library(shiny)
header = dashboardHeader(disable = TRUE)
sidebar = dashboardSidebar(disable = TRUE)
body = dashboardBody(
shiny::tags$script(
HTML("document.body.style.backgroundColor = 'skyblue';")),
)
ui = dashboardPage(header = header, sidebar = sidebar, body = body)
server = function(input, output, session){
}
shinyApp(ui, server)
However when i embedd my .js file (myscript.js stored within www subdirectory)
document.body.style.backgroundColor = "skyblue";
$("firstinput").on("keypress", function(){
alert("Pressed");
})
$(document).on('shiny:connected', function(event) {
alert('Connected to the server');
})
...like this:
library(shiny)
header = dashboardHeader(disable = TRUE)
sidebar = dashboardSidebar(disable = TRUE)
body = dashboardBody(
shiny::tags$head(
shiny::tags$script(
src = "myscript.js"
)),
HTML('<input type="button" id="firstinput">')
)
ui = dashboardPage(header = header, sidebar = sidebar, body = body)
server = function(input, output, session){
}
shinyApp(ui, server)
nothing is applied... How comes?
No need of tags$head(). The following works fine.
tags$script(src = "myscript.js")
I have a file_field_tag inside a rails form with a select file field:
<%= file_field_tag "attachments[media_files][]", multiple: true, id: "files" %>
And I have an area to preview the images/videos and remove if need be:
<span id="result"></span>
Everything is working correct but there is only one glitch.... If the images/videos are in separate folders, I have to add the files from one folder first and then from the other folder. But after this process only the second batch of files gets saved.
Here is the javascript for all of the above:
window.onload = function(){
//Check File API support
if(window.File && window.FileList && window.FileReader)
{
var filesInput = document.getElementById("files");
filesInput.addEventListener("change", function(event){
var files = event.target.files; //FileList object
var output = document.getElementById("result");
for(var i = 0; i< files.length; i++)
{
var file = files[i];
//Allowed files
//if (!file.type.match(/.(jpg|jpeg|png|gif|mp4|avi|flv|wmv|mov|tiff|bmp|exif)$/i))
//continue;
var picReader = new FileReader();
picReader.addEventListener("load",function(event){
var picFile = event.target;
var span = document.createElement("span");
span.innerHTML = ['<img class="thumb" src="', picFile.result, '" title="', picFile.name, '"/><span class="remove_img_preview"></span>'].join('');
output.insertBefore(span,null);
span.children[1].addEventListener("click", function(event){
span.parentNode.removeChild(span);
});
});
//Read the image
picReader.readAsDataURL(file);
}
});
}
else
{
console.log("Your browser does not support File API");
alert("Your browser does not support File API")
}
}
Any ideas on how to fix this glitch??
I am trying to prevent the image file selected from loading to the canvas if it is larger than the maximum allowed I have set. The logic checks the image and throws a warning, but still loads the image being checked. How can I prevent this from occurring? Also, I need to be able to clear the image previously loaded, if a new one is loaded and checked. A newly loaded image that is too large, is not checked for validity or have a warning thrown if its too big.
var fileInput = document.getElementById("file"),
renderButton = $("#renderButton"),
submit = $(".submit"),
imgly = new ImglyKit({
container: "#container",
ratio: 1 / 1
});
// As soon as the user selects a file...
fileInput.addEventListener("change", function (event) {
//CHECK FILE SIZE OF INPUT
if (window.File && window.FileReader && window.FileList && window.Blob)
{
//get the file size and file type from file input field
var fsize = $('#file')[0].files[0].size;
var ftype = $('#file')[0].files[0].type;
var fname = $('#file')[0].files[0].name;
if(fsize>54000) //do something if file size more than 1 mb (1048576)
{
$(".file-warning").html("<div class='alert alert-danger'><p>The image: <b>" + fname +"</b> is <b>" + fsize/1000 + "KB</b> and too big!</p></div>");
//alert("Type :"+ ftype +" | "+ fsize +" bites\n(File: "+fname+") Too big!");
}
}else{
alert("Please upgrade your browser, because your current browser lacks some new features we need!");
}
//END FILE SIZE CHECK
var file;
var fileToBlob = event.target.files[0];
var blob = new Blob([fileToBlob], {
"type": fileToBlob.type
});
// do stuff with blob
console.log(blob);
// Find the selected file
if (event.target.files) {
file = event.target.files[0];
} else {
file = event.target.value;
}
// Use FileReader to turn the selected
// file into a data url. ImglyKit needs
// a data url or an image
var reader = new FileReader();
reader.onload = (function (file) {
return function (e) {
data = e.target.result;
// Run ImglyKit with the selected file
try {
imgly.run(data);
} catch (e) {
if (e.name == "NoSupportError") {
alert("Your browser does not support canvas.");
} else if (e.name == "InvalidError") {
alert("The given file is not an image");
}
}
};
})(file);
reader.readAsDataURL(file);
});
When file size check is failed - you should save wrong state (by changing to Boolean variable) or simply break execution of function:
if(fsize>54000) //do something if file size more than 1 mb (1048576)
{
$(".file-warning").html("<div class='alert alert-danger'><p>The image: <b>" + fname +"</b> is <b>" + fsize/1000 + "KB</b> and too big!</p></div>");
//alert("Type :"+ ftype +" | "+ fsize +" bites\n(File: "+fname+") Too big!");
isValidSize=false; // this variable should be created somewhere at the top. It will keep the state of file-picker.
return; //this will stop any further actions;
}
I'm writing an app that uploads multiple posts to Tumblr. I'm using Ruby with Sinatra, and since I'm new to both I was wondering how to actually use the files uploaded using the HTML5 multiple file reader.
Here is my Ruby pseudocode in app.rb:
post '/' do |file|
if file is text file
##client.text("#{session[:user_id]}.tumblr.com", {:body => "Stuff from index.erb"})
elsif file is photo file
##client.photo("#{session[:user_id]}.tumblr.com", {:data => ['/path/to/pic.jpg']})
else
"Sorry, text and photo only at this time."
end
end
And here's my HTML/Javascript in index.erb:
<body>
<h1>File Uploader</h1>
<form method="post">
<input type="file" id="files" name="files[]" multiple="multiple">
<output id="list"></output>
<div id="progress_bar"><div class="percent">0%</div></div>
<input type="submit" value="Post Something!">
</form>
<!-- FILE LOADER -->
<script>
function handleFileSelect(evt) {
var files = evt.target.files; // FileList object
// Loop through the FileList and render image files as thumbnails.
for (var i = 0, f; f = files[i]; i++) {
// Only process image files.
if (!f.type.match('image.*')) {
continue;
}
var reader = new FileReader();
// Closure to capture the file information.
reader.onload = (function(theFile) {
return function(e) {
// Render thumbnail.
var span = document.createElement('span');
span.innerHTML = ['<img class="thumb" src="', e.target.result,
'" title="', escape(theFile.name), '"/>'].join('');
document.getElementById('list').insertBefore(span, null);
};
})(f);
// Read in the image file as a data URL.
reader.readAsDataURL(f);
}
}
document.getElementById('files').addEventListener('change', handleFileSelect, false);
</script>
<!-- PROGRESS BAR -->
<script>
var reader;
var progress = document.querySelector('.percent');
function abortRead() {
reader.abort();
}
function errorHandler(evt) {
switch(evt.target.error.code) {
case evt.target.error.NOT_FOUND_ERR:
alert('File Not Found!');
break;
case evt.target.error.NOT_READABLE_ERR:
alert('File is not readable');
break;
case evt.target.error.ABORT_ERR:
break; // noop
default:
alert('An error occurred reading this file.');
};
}
function updateProgress(evt) {
// evt is an ProgressEvent.
if (evt.lengthComputable) {
var percentLoaded = Math.round((evt.loaded / evt.total) * 100);
// Increase the progress bar length.
if (percentLoaded < 100) {
progress.style.width = percentLoaded + '%';
progress.textContent = percentLoaded + '%';
}
}
}
function handleFileSelect(evt) {
// Reset progress indicator on new file selection.
progress.style.width = '0%';
progress.textContent = '0%';
reader = new FileReader();
reader.onerror = errorHandler;
reader.onprogress = updateProgress;
reader.onabort = function(e) {
alert('File read cancelled');
};
reader.onloadstart = function(e) {
document.getElementById('progress_bar').className = 'loading';
};
reader.onload = function(e) {
// Ensure that the progress bar displays 100% at the end.
progress.style.width = '100%';
progress.textContent = '100%';
setTimeout("document.getElementById('progress_bar').className='';", 2000);
}
// Read in the image file as a binary string.
reader.readAsBinaryString(evt.target.files[0]);
}
document.getElementById('files').addEventListener('change', handleFileSelect, false);
</script>
</body>
Once I have the actual file objects, I can send them to Tumblr via the Ruby code, BUT I don't really know how to get the file from index.erb to the Ruby code. Should I put the Ruby code inside the .erb file?
TL;DR:
1.) How can I convert whatever the Javascript returns into a readable file for Ruby? I want to return the photos as files, and the text as parseable text.
2.) How can I send the file returned by Javascript to Ruby to send to Tumblr?