I'm trying to create an Office-JS project which will take some form data, which i've created inside a Excel Taskpane Form. Then when they submit it will send a request to my website which will process the form and spit back some HTML/JS as strings for Bokeh or ggplot.
Thus my question: Is there a way to create an "embedded" iframe or Microsoft web browser that I can put html/js into and send the "left side" with context.sync() using the Excel JS API?
I've found documentation about using the built in graphs, but I was hoping to pass back more complex graphs: https://learn.microsoft.com/en-us/office/dev/add-ins/excel/excel-add-ins-charts
Something like this:
To put it into code, this is where i'm stuck:
function getData(){
return Excel.run( function (context) {
return context.sync().then( function () {
.ajax({
<my url and form data>
}).done( function(data, status, xhr) {
var browser = create_browser(); // I do not know what this would be
// Bokeh route returns data["script"] & data["div"]
// (\n<script type="text/javascript">\n ... <div class="bk-plotdiv" id="30d5b5cf-da07-4382-9b93-09ac605ba96d"></div>\n</div>)
browser.render(data["script"], data["div"])
sheet.getRange().values = browser
})
})
})
I think what you might want is a Content add-in. This is an example of one: Excel Content Add-in Humongous-Insurance. Some more information at Create new objects in Office documents. But you can't have both a browser in a task pane and one in the Excel document. These are different add-in types and the add-in manifest specifies the type. You will need to have the form as well as the resulting graphic in the content window.
David E. Craig describes a way of communicating between a Task Pane Office app and a Content Office app (what would be embedded on the spreadsheet) on his website. He summarizes it as follows:
The solution is to use the Document as a communication medium. In the particular case we used CustomXMLParts in the document. Here is how it would work:
One add-in would need to send an update to the other, so it would write a CustomXMLPart with a specific namespace and a “context” (basically, I am the TaskPane communicating) to the document.
Both add-ins will have a window.setInterval() thread running to check the documents for CustomXMLParts in that given namespace.
The timer on the Content Add-in would fire, find the new customXMLPart from the taskpane, read the contents and then update itself as needed and finally, delete the CustomXMLPart.
Related
I have a C++ app that uses WebView2 as UI component. Native-side code and web-side code communicate via a host object. That works great, but:
I want to let the user (for example) drag and drop files to a drop box on the UI, hand the path names over to the client C++ app, which should read and process the files. Butfor this I would need the full path names (on the host).
I have currently no example for the other way round, but I could imagine to hand a path name to JavaScript which should the read and process this file.
I couldn't find any documentation neither on WebView2, nor in the File Aystem Access API on the Browser side (Java Script).
I know that there are security issues for real web apps, but - hey - this is a native component anyway!
So here is my code in JavaScript:
butOpenFile.addEventListener('click', async () => {
// Destructure the one-element array.
[fileHandle] = await window.showOpenFilePicker();
// Do something with the file handle.
});
How can I use *fileHandle *to retrieve the full path of the selected file, that the hostObject can use to open and process the file? (Note: fileHandle.name only has the name.ext-part of the path)
I'm new to GAS and JavaScript in general, so I'd like some help adapting a script from a spreadsheet to a Web App.
Based on some scripts I found, I developed a code to work the way I need in a Google Spreadsheet, but after making it work exactly the way I need it, I realized that a Web App could be a better alternative, mainly because of how it works on Mobile .
The point is that I didn't have a very linear JavaScript learning curve, my learning was solving specific needs, so I have difficulty with some basic concepts... and to be quite honest, deeply understanding JavaScript is not my main focus, but this knowledge is missing me now...
Let's get straight to the point
My current spreadsheet is this one:
Google Sheet - Stack Demonstration
In the GAS linked to this worksheet there are 2 .gs files and one HTML.
GAS files
1 - CSV.gs | Contains 2 scripts
CheckForFiles - Checks the amount of files in a given Google Drive folder before releasing the execution of other scripts.
SheetToCSV - Creates a .csv file of the sheet in the parent folder of that sheet.
This script is applied to the spreadsheet's Submit button.
2 - Upload.gs | Contains some functions responsible for uploading files through the spreadsheet.
ShowDialog0101 - Basically it's a script to call the upload page through an HTML alert in the spreadsheet.
GetParent - Basically it's a script that discovers the ID of the spreadsheet's parent folder and passes this information to the HTML file. I created this function because that way I can use this worksheet's folder as a model folder, simply duplicating the entire content without having to edit the code to update the worksheet's folder ID.
CreateOrGetFolder - This is the main function of the upload script, it checks if there is a child folder that has the name "Video" inside the parent folder, if it exists, it takes the ID of that folder so that the file is uploaded in that folder, if it does not exist, it creates a folder called "Video" and takes the ID of the created folder.
This is the Web App that launches when the Video File button is clicked:
Web App - Stack Demonstration
HTML file
Basically contains the client-side upload functions, I adapted this script based on this one.
What i would like to do
As I commented initially, I would like to adapt these scripts to work in a Web App.
My idea is that instead of the person filling out the worksheet, they fill out a form.
For this I need to adapt mainly the SheetToCSV script to link with a Submit button in the Web App, the idea is that as soon as the form is completed and the file upload is completed, this button is released and then when clicking on it the SheetToCSV script be triggered by creating a .csv file in the spreadsheet's parent folder with all the form responses.
My main difficulty is in linking the .csv generation script with the Submit button, I've been racking my brains over this for days.
I'm already having nightmares with this programming, literally... if anyone can help me with this, I'd be very grateful!
EDIT
I'll try to explain in a little more detail here.
Currently, I have this google spreadsheet here:
This worksheet contains modified versions of 2 scripts created by Tanaike.
Script 1 - Generates a .csv file with the worksheet fields in the same folder as the worksheet.
Script 2 - It is a modified version of Tanaike's Resumable Upload for Web Apps script, it is called in the spreadsheet via html alert.
Resumable Upload for Web Apps via HTML Alert
CSV file generated by the worksheet
Everything works as expected in this worksheet, but now I would like to convert it to a Web App, like this example:
The issue is that I don't know how to convert Tanaike's .csv generation script to generate the files through this html form, what I need is to integrate it with the Submit button of the Web App and collect the form fields in a .csv file.
The Spreadsheet and the Web App can be viewed at these links:
Google Sheet
Web App Form
Thank you for replying. Can I ask you about your expected values for the CSV data? In this case,
I confirmed your expected result in this question as follows.
You want to retrieve 2 text values of "name01", "description", and 2 values from dropdown lists of "Option1" and "Option2". The total values are 4 values.
When the HTML form is submitted, you want to create a new CSV file for every submission.
In this case, how about the following modification? Unfortunately, in your question, your script is not shown. So, in this answer, I would like to propose a simple modification.
When I saw your sample Spreadsheet including your script, when the submit button is clicked, it seems that the function submitForm() is run. In this answer, this is used.
Modified script:
Javascript side:
Please modify submitForm() as follows.
function submitForm() {
// Added the below script.
var name = $('#name01').val();
var description = $('#description').val();
var option1 = $('#Option1').val();
var option2 = $('#Option2').val();
var data = [name, description, option1, option2].join(",");
google.script.run.saveDataAsCSV(data, uploadParentFolderId);
if ($('#submit-btn.disabled')[0]) return; // short circuit
Google Apps Script side:
Please add the following function. Please modify the filename of "sample.csv" to your actual situation.
const saveDataAsCSV = (data, folderId) => DriveApp.getFolderById(folderId).createFile("sample.csv", data);
By this modification, 4 values in HTML form are retrieved and save it as a CSV file to the folder of uploadParentFolderId.
If you want to save the file to other folder, please modify uploadParentFolderId of google.script.run.saveDataAsCSV(data, uploadParentFolderId).
I am trying to make a spreadsheet capable of opening an HTML file as a sidebar, but the HTML file is located on a master spreadsheet. Here is the code that I have already for opening the sidebar using an HTML that is in the spreadsheet.
function sidebar1()
{
var b1 = SpreadsheetApp.getActiveSheet().getRange('C13').getValue();
html = HtmlService.createHtmlOutputFromFile(b1).setTitle(b1);
ui.showSidebar(html);
}
It's only possible if you are the current user of that spreadsheet and that spreadsheet is the container of the script because SpreadsheetApp.getUi() as described in the documentation Returns an instance of the spreadsheet's user-interface environment that allows the script to add features like menus, dialogs, and sidebars. if a spreadsheet is opened by id or url then there is no user interface
I just thought that if you have two spreadsheets already open it might be possible to call a server side function on one to run a server side function on the other using Apps Script API which might be able to launch a Sidebar on the second sheet. So maybe I'm wrong. Give it a try.
One solution would be to open the HTML file into a temporary workbook, and copy the sheet from there into the workbook containing all of them.
i would like to give our client one javascript code (e.g. Google Tag Manager) he/she has to implement on the website.
When this script is called i want to have a config file per client which features this client has enabled. Lets say we have 3 features:
feature1.js
feature2.js
feature3.js
Every feature is doing something else on the website (e.g. tracking user data, displaying a widget, etc.)
My questions:
1. How would you store the config which features are enabled? This should be flexible, whenever we add feature4.js, we just can enable it and the script will be loaded and the client does not have to implement new js code
2. Regarding performance, how would you do it? We are using AWS CloudFront.
Basically this is kind of the Google Tag Manager concept: One Code and on backend side the client can decide which JS code to be loaded / injected.
Thank you very much for your ideas!
A simple way is just to inject the required lib into the DOM.
On this example, the script will be the second element on the page.
You may want sometimes to inject at the bottom of the DOM.
function userlib1(){
let lib = document.getElementsByTagName('*')[1],
inj = document.createElement('script')
inj.src = 'https://..../script.js'
lib.appendChild(inj)
}
To store user data's, we can use localstorage, just like this:
localStorage.setItem('userdata','lib4')
console.log( localStorage.getItem('userdata') )
in my webpage you can read book in pdf format. The problem is that some books have around 1000 pages and the PDF is really big so even if the user reads just 10 pages the server download the full pdf, so this is awful for my hosting account because I have a transfer limit.
What could I do to display the pdf without load the full PDF.
I use pdf.js
Greetings.
ORIGINAL POST:
PDF files are designed in a way that forces the client side to download the whole file just to get the first page.
The last line of the PDF file tells the PDF reader where the root dictionary for the PDF file is located (the root dictionary tells the reader about the page catalog - order of pages - and other data used by the reader).
So, as you can see, the limitations of the PDF design require that you use a server side solution that will create a new PDF with only the page(s) you want to display.
The best solution (in my opinion) is to create a "reader" page (as opposed to a download page) that requests a specific page from the server and allows the user to advance page by page (using AJAX).
The server will need to create a new PDF (file or stream) that contains only the requested page and return it to the reader.
if you are running your server with Ruby (ruby on rails), you can use the combine_pdf gem to load the pdf and send just one page...
You can define a controller method that will look something like this:
def get_page
# read the book
book = CombinePDF.parse IO.read("book.pdf")
# create empty PDF
pdf_with_one_page = CombinePDF.new
# add the page you want
# notice that the pages array is indexed from 0,
# so an adjustment to user input is needed...
pdf_with_one_page << book.pages[ params[:page_number] - 1 ]
# no need to create a file, just stream the data to the client.
send_data pdf_with_one_page.to_pdf, type: 'application/pdf', disposition: 'inline'
end
if you are running PHP or node.js, you will need to find a different server-side solution.
Good luck!
EDIT:
I was looking over the PDF.js project (which looks very nice) and notice the limited support statement for Safari:
"Safari (desktop and mobile) lacks a number of features or has defects, e.g. in typed arrays or HTTP range requests"...
I understand from this statement that on some browsers you can manage a client-side solution based on the HTTP Byte Serving protocol.
This will NOT work with all browsers, but it will keep you from having to use a server-side solution.
I couldn't find the documentation for the PDF.js feature (maybe it defaults to ranges and you just need to set the range...?), but I would go with a server-side solution that I know to work on all browsers.
EDIT 2:
Ignore Edit 1, as iPDFdev pointed out (thank you iPDFdev), this requires a special layout of the PDF file and will not resolve the issue of the browser downloading the whole file.
You can take following approach governed by functionality
Add configuration (i.e. kind of flag) whether you want to display entire PDF or not.
While rendering your response read above mentioned configuration if flag is set generate minimal PDF with 20 pages with hyperlink to download entire PDF else minimal PDF with 20 pages only
When you prepare initial response of your web page add PDF which contains say 20 pages (minimal PDF) only and process the response