I would like to make a widget in Ipython Notebook 3.x or 4.x (Jupyter, Python 3) for remote file upload which allows a user to select multiple files in the browser's file picker when uploading. Unfortunatelly, I have no clue about the javascript side.
I have found blueimp's widgets, however, I have no idea how to use them inside a notebook.
This is how a single file upload widget is made:
import base64
from __future__ import print_function # py 2.7 compat.
from IPython.html import widgets # Widget definitions.
from IPython.utils.traitlets import Unicode # Traitlet needed to add synced attributes to the widget.
class FileWidget(widgets.DOMWidget):
_view_name = Unicode('FilePickerView', sync=True)
value = Unicode(sync=True)
filename = Unicode(sync=True)
def __init__(self, **kwargs):
"""Constructor"""
widgets.DOMWidget.__init__(self, **kwargs) # Call the base.
# Allow the user to register error callbacks with the following signatures:
# callback()
# callback(sender)
self.errors = widgets.CallbackDispatcher(accepted_nargs=[0, 1])
# Listen for custom msgs
self.on_msg(self._handle_custom_msg)
def _handle_custom_msg(self, content):
"""Handle a msg from the front-end.
Parameters
----------
content: dict
Content of the msg."""
if 'event' in content and content['event'] == 'error':
self.errors()
self.errors(self)
%%javascript
require(["widgets/js/widget", "widgets/js/manager"], function(widget, manager){
var FilePickerView = widget.DOMWidgetView.extend({
render: function(){
// Render the view.
this.setElement($('<input />')
.attr('type', 'file'));
},
events: {
// List of events and their handlers.
'change': 'handle_file_change',
},
handle_file_change: function(evt) {
// Handle when the user has changed the file.
// Retrieve the first (and only!) File from the FileList object
var file = evt.target.files[0];
if (file) {
// Read the file's textual content and set value to those contents.
var that = this;
var file_reader = new FileReader();
file_reader.onload = function(e) {
that.model.set('value', e.target.result);
that.touch();
}
file_reader.readAsText(file);
} else {
// The file couldn't be opened. Send an error msg to the
// back-end.
this.send({ 'event': 'error' });
}
// Set the filename of the file.
this.model.set('filename', file.name);
this.touch();
},
});
// Register the FilePickerView with the widget manager.
manager.WidgetManager.register_widget_view('FilePickerView', FilePickerView);
});
file_widget = FileWidget()
# Register an event to echo the filename when it has been changed.
def file_loading():
print("Loading %s" % file_widget.filename)
file_widget.on_trait_change(file_loading, 'filename')
# Register an event to echo the filename and contents when a file
# has been uploaded.
def file_loaded():
print("Loaded, file contents: %s" % file_widget.value)
file_widget.on_trait_change(file_loaded, 'value')
# Register an event to print an error message when a file could not
# be opened. Since the error messages are not handled through
# traitlets but instead handled through custom msgs, the registration
# of the handler is different than the two examples above. Instead
# the API provided by the CallbackDispatcher must be used.
def file_failed():
print("Could not load file contents of %s" % file_widget.filename)
file_widget.errors.register_callback(file_failed)
file_widget
Comments, suggestions and fixes are welcome.
I got inspiration from Jupyter Notebook (4.x) itself from the NotebookList.prototype.handleFilesUpload function of the notebooklist.js file. After reading up on some javascript syntax, I came up with the following:
(Please note that files are uploaded in text mode without checking.)
import base64 # You need it if you define binary uploads
from __future__ import print_function # py 2.7 compat.
import ipywidgets as widgets # Widget definitions.
from traitlets import List, Unicode # Traitlets needed to add synced attributes to the widget.
class FileWidget(widgets.DOMWidget):
_view_name = Unicode('FilePickerView').tag(sync=True)
_view_module = Unicode('filepicker').tag(sync=True)
filenames = List([], sync=True)
# values = List(trait=Unicode, sync=True)
def __init__(self, **kwargs):
"""Constructor"""
super().__init__(**kwargs)
# Allow the user to register error callbacks with the following signatures:
# callback()
# callback(sender)
self.errors = widgets.CallbackDispatcher(accepted_nargs=[0, 1])
# Listen for custom msgs
self.on_msg(self._handle_custom_msg)
def _handle_custom_msg(self, content):
"""Handle a msg from the front-end.
Parameters
----------
content: dict
Content of the msg."""
if 'event' in content and content['event'] == 'error':
self.errors()
self.errors(self)
%%javascript
requirejs.undef('filepicker');
define('filepicker', ["jupyter-js-widgets"], function(widgets) {
var FilePickerView = widgets.DOMWidgetView.extend({
render: function(){
// Render the view using HTML5 multiple file input support.
this.setElement($('<input class="fileinput" multiple="multiple" name="datafile" />')
.attr('type', 'file'));
},
events: {
// List of events and their handlers.
'change': 'handle_file_change',
},
handle_file_change: function(evt) {
// Handle when the user has changed the file.
// Save context (or namespace or whatever this is)
var that = this;
// Retrieve the FileList object
var files = evt.originalEvent.target.files;
var filenames = [];
var file_readers = [];
console.log("Reading files:");
for (var i = 0; i < files.length; i++) {
var file = files[i];
console.log("Filename: " + file.name);
console.log("Type: " + file.type);
console.log("Size: " + file.size + " bytes");
filenames.push(file.name);
// Read the file's textual content and set value_i to those contents.
file_readers.push(new FileReader());
file_readers[i].onload = (function(file, i) {
return function(e) {
that.model.set('value_' + i, e.target.result);
that.touch();
console.log("file_" + i + " loaded: " + file.name);
};
})(file, i);
file_readers[i].readAsText(file);
}
// Set the filenames of the files.
this.model.set('filenames', filenames);
this.touch();
},
});
// Register the FilePickerView with the widget manager.
return {
FilePickerView: FilePickerView
};
});
file_widget = FileWidget()
def file_loaded(change):
'''Register an event to save contents when a file has been uploaded.'''
print(change['new'])
i = int(change['name'].split('_')[1])
fname = file_widget.filenames[i]
print('file_loaded: {}'.format(fname))
def file_loading(change):
'''Update self.model when user requests a list of files to be uploaded'''
print(change['new'])
num = len(change['new'])
traits = [('value_{}'.format(i), Unicode(sync=True)) for i in range(num)]
file_widget.add_traits(**dict(traits))
for i in range(num):
file_widget.observe(file_loaded, 'value_{}'.format(i))
file_widget.observe(file_loading, names='filenames')
def file_failed():
print("Could not load some file contents.")
file_widget.errors.register_callback(file_failed)
file_widget
A button with the text Browse... should appear stating how many files are selected. Since print statements are included in the file_loading and file_loaded functions you should see filenames and file contents in the output. Filenames and file types are shown in the console log, as well.
This issue https://github.com/ipython/ipython/issues/8383 answers your question partially. There is already upload button available in jupyter 4.0 on Dashboard screen. This upload button supports selecting multiple files.
Note that up to date links are located here for upload widget:
https://github.com/ipython/ipywidgets/blob/master/docs/source/examples/File%20Upload%20Widget.ipynb
Also there is a extension available for download and quick installation in your notebooks:
https://github.com/peteut/ipython-file-upload
pip install fileupload
or
pip install git+https://github.com/peteut/ipython-file-upload
Note that the extension is confirmed to work on linux only according to the author.
There is an even newer approach than fileupload, which I used in the past and works pretty well (posted by #denfromufa) with a natively supported file-upload widget.
import io
import ipywidgets as widgets
widgets.FileUpload(
accept='.txt', # Accepted file extension e.g. '.txt', '.pdf', 'image/*', 'image/*,.pdf'
multiple=True # True to accept multiple files upload else False
)
A couple tips:
If your notebook has code 'under' the uploader, it will proceed. I use ipython blocking to 'pause' the execution of the rest of the notebook until the file is successfully uploaded... I then have a second button that basically restarts code execution after the user has uploaded the right file(s) and is ready to proceed.
The ipywidget uploader differs from [fileupload] (pip install fileupload) in that it pulls the file into python so you can work with it. If you want to drop it into a directory, you have to use some other method to write the value data to a file:
For an fileupload widget 'myupload' you could write an event-driven function that includes something like the following when a file is uploaded:
# Get the original filename which is the first key in the widget's value dict:
uploaded_filename = next(iter(myupload.value))
content = myupload.value[uploaded_filename]['content']
with open('myfile', 'wb') as f: f.write(content)
Related
I'm trying to create a file in the File Cabinet and write to it in a Client Script. Checking the API reference, I see that all the File objects are Server-side only.
Does that mean you can't create and write to a file in a Client script? I tried to use the code in my Client script anyway, but got the error:
Fail to evaluate script: {"type":"error.SuiteScriptModuleLoaderError","name":"{stack=[Ljava.lang.Object;#59c89ae9, toJSON=org.mozilla.javascript.InterpretedFunction#5a4dd71f, name=MODULE_DOES_NOT_EXIST, toString=org.mozilla.javascript.InterpretedFunction#1818dc3c, id=, message=Module does not exist: N/file.js, TYPE=error.SuiteScriptModuleLoaderError}","message":"","stack":[]}
When I tried to save it in NetSuite as the script file. Does the above mean that the N/File object can't be loaded in a Client script?
Can I write to a file in a Client script?
Create a Client Script - this Script will contain the function to call the Suitelet and pass along information from the current record/session if needed.
function pageInit{
//required but can be empty
}
function CallforSuitelet(){
var record = currentRecord.get();
var recId = record.id;
var recType = record.type
var suiteletURL = url.resolveScript({
scriptId:'customscriptcase3783737_suitelet',// script ID of your Suitelet
deploymentId: 'customdeploycase3783737_suitelet_dep',//deployment ID of your Suitelet
params: {
'recId':recId,
'recType':recType
}
});
document.location=suiteletURL;
}
return {
CallforSuitelet : CallforSuitelet,
pageInit : pageInit
}
Create a Suitelet - this script will create the file
function onRequest(context) {
var requestparam = context.request.parameters;
var recId = requestparam.recId; //the same name of the fields specified in url.resolveScript parameters from Client Script
var recType = requestparam.recType;
var objRecord = record.load({
type: record.Type.___,//insert record type
id: recId
});
var content = 'Insert Content Here';
var xml = "<?xml version=\"1.0\"?>\n<!DOCTYPE pdf PUBLIC \"-//big.faceless.org//report\" \"report-1.1.dtd\">\n";
xml += "<pdf>\n<body font-size=\"12\">\n<h3>Sample PDF</h3>\n";
xml += "<p></p>";
xml += content;
xml += "</body>\n</pdf>";
context.response.renderPdf({xmlString: xml});
}
return {
onRequest: onRequest
}
As you've already discovered, server-only modules can't be called from client-side scripts directly, but this can be done via a Suitelet. You will need to decide how the Suitelet does it's work. An example of the principal at work can be found here and here
I am generating a ZipFile with Python 3.7 on Ubuntu 18.04 and serving it with Flask 1.0.2. I know the Flask code works because I can type the endpoint in my browser explicitly and get a valid ZipFile that I can decompress on Windows 10. I am now trying to get my Javascript code to download the ZipFile with a Button Click. The problem is that the resulting file is declared "Corrupt" by Windows and cannot be decompressed. How can I make the Javascript download the file correctly?
The Flask code is shown here:
#app.route("/get_new_training_data", methods=["GET"])
def create_and_serve_training_data_zipfile():
# Define the location of the new training data
newDataLocation = "%s" % (configuration.NEW_TRAINING_DATA_LOCATION)
# Ensure it exists or throw and error
if os.path.isdir(newDataLocation):
print("[%s] Creating new ZipFile for download from: %s" % (os.path.basename(__file__), newDataLocation))
# Create a zipfile in memory and send it back to the user
# The zipfile will contain the training data collected through
# the webbrowser button controls
try:
memoryFile = BytesIO()
with zipfile.ZipFile(memoryFile, 'w', zipfile.ZIP_DEFLATED) as zf:
for root, dirs, files in os.walk(newDataLocation):
for file in files:
print("[%s] Adding file to ZipFile: %s" % (os.path.basename(__file__), file))
zf.write(os.path.join(root, file))
memoryFile.seek(0)
return send_file(memoryFile, mimetype='application/zip', attachment_filename='ImageData.zip', as_attachment=True)
except Exception as err:
newStatus = {"download_image_data": "Failed: Error Could not create Zipfile: %s"%(err)}
print("[%s] Error downloading new Training Data - JSON Response is: %s" % (
os.path.basename(__file__), newStatus))
return jsonify(newStatus), 500
else:
newStatus = {"download_image_data": "Failed: Error Training Data directory does not exist"}
print("[%s] Error downloading new Training Data - JSON Response is: %s" % (os.path.basename(__file__), newStatus))
return jsonify(newStatus), 500
The Javascript code is here:
// Add an on click for the download data button
var downloadTrainingDataButton = document.getElementById("downloadTrainingData");
downloadTrainingDataButton.onclick = function() {
console.log("Downloading New Training Data ...");
// Do a web request to download a zip file of the training data
var logRequestXmlHttp = new XMLHttpRequest();
logRequestXmlHttp.open( "GET", "http://{{host_ip}}/get_new_training_data", true ); // false for synchronous request
logRequestXmlHttp.onload = function(e) {
code = logRequestXmlHttp.response;
if (logRequestXmlHttp.status == 200) {
var blob = new Blob([this.response], {type: "application/zip"});
var url = window.URL.createObjectURL(blob);
var link = document.createElement('a');
document.body.appendChild(link);
link.style = "display: none";
link.href = url;
link.download = "ImageData.zip";
link.click();
setTimeout(() => {
window.URL.revokeObjectURL(url);
link.remove(); } , 100);
console.log("Success downloading zip file");
}
};
logRequestXmlHttp.onerror = function () {
console.log("Error with downloading zip file: " + logRequestXmlHttp.responseText + " Code: " + code);
};
logRequestXmlHttp.send( null );
}
To download files you can use the HTML5 download attribute on an anchor tag to let it know it needs to download a resource instead of navigating to it. This is probably the easiest way to achieve what you want.
For example:
Download Training Data
I recently developed a universal application for Windows 10 with UWP web context (so JavaScript and HTML) and I would like to save a text file. It works well on a browser (Chrome, Firefox, Edge,...) but not in the application.
Can someone help me? :)
Thank you in advance!
Here is the code responsible for saving the text file.
function saveTextAsFile(fileName) {
var source = input.value.replace(/\n/g, "\r\n");
var fileUrl = window.URL.createObjectURL(new Blob([source], {type:"text/plain"}));
var downloadLink = createDownloadLink(fileUrl, fileName);
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);}
To download a file using a Progressive Web App as a Universal Windows Platform you can use the Windows global object with the FileSavePicker. Note that you can check to see if it exists using if (window['Windows'] != null) { ... }
// Create the picker object and set options
var savePicker = new Windows.Storage.Pickers.FileSavePicker();
savePicker.suggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.documentsLibrary;
// Dropdown of file types the user can save the file as
savePicker.fileTypeChoices.insert("Plain Text", [".txt"]);
// Default file name if the user does not type one in or select a file to replace
savePicker.suggestedFileName = "New Document";
savePicker.pickSaveFileAsync().then(function (file) {
if (file) {
// Prevent updates to the remote version of the file until we finish making changes and call CompleteUpdatesAsync.
Windows.Storage.CachedFileManager.deferUpdates(file);
// write to file
Windows.Storage.FileIO.writeTextAsync(file, fileContents).done(function () {
// Let Windows know that we're finished changing the file so the other app can update the remote version of the file.
// Completing updates may require Windows to ask for user input.
Windows.Storage.CachedFileManager.completeUpdatesAsync(file).done(function (updateStatus) {
if (updateStatus === Windows.Storage.Provider.FileUpdateStatus.complete) {
WinJS.log && WinJS.log("File " + file.name + " was saved.", "sample", "status");
} else {
WinJS.log && WinJS.log("File " + file.name + " couldn't be saved.", "sample", "status");
}
});
});
} else {
WinJS.log && WinJS.log("Operation cancelled.", "sample", "status");
}
});
This assumes you are downloading a text file. To download a Uint8Array use the WriteBytesAsync function on FileIO instead. Note that many of the functions on FileIO are available in JavaScript even though they are not documented for JavaScript.
Check out the FileSavePicker Class documentation for more information.
I have this line in my Razor :
#Html.Raw(File.ReadAllText(Server.MapPath("~/Views/Home/index.html")))
And in HTML file, I have this :
<li>Personal Records</li>
And in my js file I have this :
if ($(link).text() === 'Personal Records') {
$("#govde").load("PersonalRecords.html");
}
But when I click on that link, nothing happens. When I open Index.html directly from file browser, it works. How can I fix this?
EDIT :
In console, it has this :
http://localhost:12345/PersonalRecords.html 404 (Not Found)
I guess I have placed the html files to a wrong folder. Can you tell me where to place? Thanks.
EDIT2 :
I have this in my JS :
var upperMenu = document.getElementById('upperMenu');
var requests = document.getElementById('requests');
$(upperMenu ).click(function (event) {
ustMenu.childNodes.forEach((myList) => {
$(myList).attr('class', ' ');
});
var link = event.target;
var list = link.parentNode;
$(myList).attr('class', 'active');
if ($(link).text() === 'Personal Records') {
$("#govde").load('#Url.Content("~/PersonalRecords.html")');
}
});
.load function is created in this(seperate) JS file.
The problem started with file name mentioned in $("#govde").load method:
$("#govde").load("PersonalRecords.html");
This statement tries to load "PersonalRecords.html" which assumed exists in the project's root directory, but it returns 404 since the target file exist in different directory.
Hence, it should be mentions full absolute path URL to load HTML content first:
var url = '#Url.Content("~/Views/Home/PersonalRecords.html")';
Then, since load method placed inside separate JS file, putting them together should results like this:
Razor
<script src="#Url.Content("~/[path_to_your_JS_file]")" type="text/javascript"></script>
<script>
var url = '#Url.Content("~/Views/Home/PersonalRecords.html")';
loadRequest(url);
</script>
JavaScript file
function loadRequest(url) {
var upperMenu = $("#upperMenu").get(0);
var requests = $("#requests").get(0);
$(upperMenu).click(function (event) {
ustMenu.childNodes.forEach((myList) => {
$(myList).attr('class', ' ');
});
var link = event.target;
var list = link.parentNode;
$(myList).attr('class', 'active');
if ($(link).text() === 'Personal Records') {
$("#govde").load(url);
}
}
}
Next, as of first mentioned part:
#Html.Raw(File.ReadAllText(Server.MapPath("~/Views/Home/index.html")))
I considered this is not a good practice to read all file contents in view side, hence I prefer return the file contents from controller side using FilePathResult like #Guruprasad Rao said:
// taken from /a/20871997 (Selman Genç)
[ChildActionOnly]
public ActionResult GetHtmlFile(String path)
{
// other stuff
// consider using Server.MapPath(path) if in doubt determining file path
return new FilePathResult(path, "text/html");
}
Usage as link in view:
<li>#Html.ActionLink("HTML File", "GetHtmlFile", "Controller", new { path = "~/Views/Home/PersonalRecords.html" }, null)</li>
Similar issues:
Rendering .html files as views in ASP.NET MVC
Render HTML file in ASP.NET MVC view?
I'm trying to upload generated client side documents (images for the moment) with Dropzone.js.
// .../init.js
var myDropzone = new Dropzone("form.dropzone", {
autoProcessQueue: true
});
Once the client have finished his job, he just have to click a save button which call the save function :
// .../save.js
function save(myDocument) {
var file = {
name: 'Test',
src: myDocument,
};
console.log(myDocument);
myDropzone.addFile(file);
}
The console.log() correctly return me the content of my document
data:image/png;base64,iVBORw0KGgoAAAANS...
At this point, we can see the progress bar uploading the document in the drop zone but the upload failed.
Here is my (standart dropzone) HTML form :
<form action="/upload" enctype="multipart/form-data" method="post" class="dropzone">
<div class="dz-default dz-message"><span>Drop files here to upload</span></div>
<div class="fallback">
<input name="file" type="file" />
</div>
</form>
I got a Symfony2 controller who receive the post request.
// Get request
$request = $this->get('request');
// Get files
$files = $request->files;
// Upload
$do = $service->upload($files);
Uploading from the dropzone (by drag and drop or click) is working and the uploads are successfull but using the myDropzone.addFile() function return me an empty object in my controller :
var_dump($files);
return
object(Symfony\Component\HttpFoundation\FileBag)#11 (1) {
["parameters":protected]=>
array(0) {
}
}
I think i don't setup correctly my var file in the save function.
I tryied to create JS image (var img = new Image() ...) but without any success.
Thanks for your help !
Finally i found a working solution without creating canvas :
function dataURItoBlob(dataURI) {
'use strict'
var byteString,
mimestring
if(dataURI.split(',')[0].indexOf('base64') !== -1 ) {
byteString = atob(dataURI.split(',')[1])
} else {
byteString = decodeURI(dataURI.split(',')[1])
}
mimestring = dataURI.split(',')[0].split(':')[1].split(';')[0]
var content = new Array();
for (var i = 0; i < byteString.length; i++) {
content[i] = byteString.charCodeAt(i)
}
return new Blob([new Uint8Array(content)], {type: mimestring});
}
And the save function :
function save(dataURI) {
var blob = dataURItoBlob(dataURI);
myDropzone.addFile(blob);
}
The file appears correctly in dropzone and is successfully uploaded.
I still have to work on the filename (my document is named "blob").
The dataURItoBlob function have been found here : Convert Data URI to File then append to FormData
[EDIT] : I finally wrote the function in dropzone to do this job. You can check it here : https://github.com/CasperArGh/dropzone
And you can use it like this :
var dataURI = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAmAAAAKwCAYAAA...';
myDropzone.addBlob(dataURI, 'test.png');
I can't comment currently and wanted to send this to you.
I know you found your answer, but I had some trouble using your Git code and reshaped it a little for my needs, but I am about 100% positive this will work for EVERY possible need to add a file or a blob or anything and be able to apply a name to it.
Dropzone.prototype.addFileName = function(file, name) {
file.name = name;
file.upload = {
progress: 0,
total: file.size,
bytesSent: 0
};
this.files.push(file);
file.status = Dropzone.ADDED;
this.emit("addedfile", file);
this._enqueueThumbnail(file);
return this.accept(file, (function(_this) {
return function(error) {
if (error) {
file.accepted = false;
_this._errorProcessing([file], error);
} else {
file.accepted = true;
if (_this.options.autoQueue) {
_this.enqueueFile(file);
}
}
return _this._updateMaxFilesReachedClass();
};
})(this));
};
If this is added to dropzone.js (I did just below the line with Dropzone.prototype.addFile = function(file) { potentially line 1110.
Works like a charm and used just the same as any other. myDropzone.addFileName(file,name)!
Hopefully someone finds this useful and doesn't need to recreate it!
1) You say that: "Once the client have finished his job, he just have to click a save button which call the save function:"
This implies that you set autoProcessQueue: false and intercept the button click, to execute the saveFile() function.
$("#submitButton").click(function(e) {
// let the event not bubble up
e.preventDefault();
e.stopPropagation();
// process the uploads
myDropzone.processQueue();
});
2) check form action
Check that your form action="/upload" is routed correctly to your SF controller & action.
3) Example Code
You may find a full example over at the official Wiki
4) Ok, thanks to your comments, i understood the question better:
"How can i save my base64 image resource with dropzone?"
You need to embedd the image content as value
// base64 data
var dataURL = canvas.toDataURL();
// insert the data into the form
document.getElementById('image').value = canvas.toDataURL('image/png');
//or jQ: $('#img').val(canvas.toDataURL("image/png"));
// trigger submit of the form
document.forms["form1"].submit();
You might run into trouble doing this and might need to set the "origin-clean" flag to "true". see http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#security-with-canvas-elements
how to save html5 canvas to server