Chrome extension : Download/export content created "on the fly" - html

I need to let the user to download a file created on the fly from some data contained in the extension, but I don't want to do this server-side.
As a real-world example : There is a variable containing the text "hello world". I want the user to be able to download/create a .TXT file containing such text.
Is it possible ?
-edited Oct 30, 2010-
Solutions so far:
window.saveAs - not working at all
createObjectURL - works, but the filename and other options can't be customized yet
copy data to clipboard - works, but too many steps are required to the user
create a GoogleDocs document - could work, but needs messing with the API and Oauth
-edited Apr 15, 2011
Looks like things are improving: https://github.com/eligrey/FileSaver.js
But Chrome 14+ is still a bit weird about filenames.

I think the only way is to call save dialog through flash, see Downloadify library.

In the Chromium-extensions Google Group I have found this working example: (I have modified it to work from the pop-up)
BuiltBlob = new BlobBuilder("");
BuiltBlob.append("Hello, world");
BlobToSave = BuiltBlob.getBlob();
chrome.tabs.create({'url': createObjectURL(BlobToSave), 'selected': false});
But the filename is not set, ending with something like cf8a56bf-d724-4b97-b10f-e252961135bd
On the The W3C docs ( http://dev.w3.org/2009/dap/file-system/file-writer.html ) I've found this not working example:
var bb = new BlobBuilder();
bb.append("Lorem ipsum");
var fileSaver = window.saveAs(bb.getBlob(), "test_file");
fileSaver.onwriteend = myOnWriteEnd;
but window.saveAs doesn't appear to exists.
Googleing around I've found outdated Google Gears references, but nothing else, maybe because I'm dealing with something too new to have proper documentation ?
Is there a way to set the filename/mime-type to the first example?

I don't think this is possible because of safety reasons

Related

What's the lightest way to add the smallest amount of dynamics to a static HTML site?

I have a personal website that's all static html.
It works perfectly for my needs, except for one tiny thing.
I want to dynamically change a single word on a single page: the name of the current map for a game server I'm running.
I can easily run a cron job to dump the name of the map into a file in the site's html directory, call it mapname.txt. This file contains a single line of text, the name of the map.
How would I update, say, game.html to include this map name?
I would very strongly prefer to not pull in some massive framework, or something like php or javascript to accomplish this.
I want the lightest weight solution possible. Using sed is an option, although definitely a hacky one. What's the tiniest step up from static html?
If you say "dynamically", do you mean:
If the information changes ...
A) the user should see it after they have re-loaded the page?
B) the page should update without the need to reload?
For A, you can use PHP (or any other language your server supports) to read the data from the file and print it into the web page. This will happen on server side.
For B, you can use JS that queries the file and updates the HTML. This will happen on client side.
To change text there are a few way though only two appropriate methods.
First is using textContent:
document.getElementById('example').textContent = 'some example text';
Secondly is the older nodeValue however it's a bit more tricky since you have to specify the exact textNode (e.g. .firstChild):
document.getElementById('example').firstChild.nodevalue = 'some example text';
You're 100% on the mark about not using frameworks or libraries, almost everything exists without the suck.
I'm not going to test this though this is a very stripped down version of my ajax function from my web platform. Some people might scream about the Fetch API however the syntax is an absolute mess. I recommend figuring out how to standardize this function so you can use it for everything instead of making copies of the code for every instance. Mine supports both GET and POST requests.
function ajax(method, url, param_id_container_pos, id_container)
{
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.timeout = 8000;
xhr.open(method,url,true);
xhr.send(null);
xhr.onreadystatechange = function()
{
if (xhr.readyState == 4)
{
if (xhr.getResponseHeader('content-type'))
{
var type = xhr.getResponseHeader('content-type').split('/')[1];
if (type.indexOf(';') >- 1) {type = type.split(';')[0];}
}
else {var type = 'xml';}//Best guess for now.
console.log(type,xhr);
console.log(xhr.responseText);
//console.log(type,xhr.responseXML);
//document.getElementById('example').textContent = xhr.responseText;
}
}
}
You're also going to have to ensure that the url is set to an absolute path. I use path variable in my platform (see my profile for the link, wildly clean and organized code).
There are plenty of ways to make this function reusable and I highly recommend doing that. For now use the last non-curley-bracket line to update your line of text.

Open a file from Google Sheets script

I've had a Google Sheets script running for some time (a year) that needs to read an HTML file from it's Google Drive directory. The code to open the file looks like this:
var myHtmlFile = UrlFetchApp.fetch("https://googledrive.com/host/0B1m........JtZzQ/myfile.htm");
... and I could use the HTM file for further parsing.
Suddenly, the code above is throwing an error 404.
Has anything changed recently, stopping me from opening the file?
After a discussion with 'azawaza' (thanks for all the tips), I have finally solved this, so I'm posting the resolution in case others fall into this.
It looks like the construct
https://googledrive.com/host/{public_folder_id}/myfile.htm
in UrlFetchApp.fetch(url, true) can no longer be used. It gives error 404.
I was getting it from the following construct (for simplicity, assuming there is only one parent folder of my spreadsheet):
...
var myId = DocsList.getFileById(SpreadsheetApp.getActive().getId());
var folderId = myId.getParents()[0].getId();
var url = "https://googledrive.com/host/" + folderId + "/myfile.htm";
// url looks like: https://googledrive.com/host/0B1m....JtZzQ/myfile.htm"
var httpResp = UrlFetchApp.fetch(url,true); //throws 404 !!!
// now, parse 'httpResp'
The solution that worked for me, is to find the file directly using this construct (again, assuming there is only one file of given name) :
var htmlCont = DocsList.find("myfile.htm")[0].getContentAsString();
// now, parse htmlCont
I don't know why the 'old' solution no longer works. As I mentioned it worked for a year.
UPDATE (May 2015)
The 'DocsList' has been deprecated, a new construct:
var files = DriveApp.getFilesByName(myURL);
if (files.hasNext()) {
var htmlCont = files.next().getBlob().getDataAsString()
}
has to be used instead
I find it strange that it ever worked before! If it did, it was probably a bug - pretty sure it was never intended to work like that with "local" files... I have never seen it mentioned anywhere that UrlFetchApp.fetch() can fetch "local" files like that.
A simple fix would be to just use proper full url of the file:
var myHtmlFile = UrlFetchApp.fetch("https://googledrive.com/host/{public_folder_id}/myfile.htm");
That will ensure your code complies with the API and does not break next time Google changes something.

Basic UiApp failing with error "ReferenceError: "UIApp" is not defined."

I am creating some google app scripts for my company to use to generate random tests for employee training. I already have the basic scripts written to grab a list of questions from a google sheet, randomize them, grab the first 10 questions, etc. That all works fine. I decided it might be better to re-do the whole thing using a UiApp instead of just separate scripts. That is where the problem comes in. I did a simple bare bones UiApp to test with, published it and tried to hit the URL and that's where I encounter this error. I searched for this error and all I could find was some discussion about this being part of google apps premiere(which should have been folded into regular google apps around 2010). I've been staring at this so long I've frustrated myself. It should be something very simple and yet it's refusing to work. I'm assuming I am doing something wrong at a basic level but I've reached the point where my brain refuses to see it.
Here is the basic script I started with:
function doGet(e) {
var app = UiApp.createApplication();
var mainPanel = app.createVerticalPanel().setId('mainPanel');
mainPanel.add(app.createLabel('test'));
return app;
}
I save it, publish it and go to the URL and that's when I get the above error message. I know it's something simple but I've reached the point of frustration and simply can't see it.
Update: to reflect comments
Another possibility for WebApps not updating is not publishing a new version and only checking the exec URL. For instant changes to the code, always check the dev URL. The exec will only change after saving a version in Manage Versions and re-publishing the app.
First Answer:
I think your question title says it all.
UIApp is not defined, but Class is UiApp. JS is case sensitive. I copied and pasted the code exactly as it is in your question and received no errors. I did have to add one line to make the label show up.
function doGet(e) {
var app = UiApp.createApplication();
var mainPanel = app.createVerticalPanel().setId('mainPanel');
mainPanel.add(app.createLabel('test'));
app.add(mainPanel); // <-- I added this line to see the label
return app;
}

Chrome Console: VM

When executing a script directly in the console in Chrome, I saw this:
Does anyone know what's the meaning of VM117:2
What does VM stand for ?
It is abbreviation of the phrase Virtual Machine.
In the Chrome JavaScript engine (called V8) each script has its own script ID.
Sometimes V8 has no information about the file name of a script, for example in the case of an eval. So devtools uses the text "VM" concatenated with the script ID as a title for these scripts.
Some sites may fetch many pieces of JavaScript code via XHR and eval it. If a developer wants to see the actual script name for these scripts she can use sourceURL. DevTools parses and uses it for titles, mapping etc.
Thanks to #MRB,
I revisited this problem, and found the solution today,
thanks to https://stackoverflow.com/a/63221101/1818089
queueMicrotask (console.log.bind (console, "Look! No source file info..."));
It will group similar elements, so make sure you add a unique identifier to each log line to be able to see all data.
Demonstrated in the following example.
Instead of
data = ["Apple","Mango","Grapes"];
for(i=0;i<10;i++){
queueMicrotask (console.log.bind (console, " info..."+i));
}
use
data = ["Apple","Mango","Grapes"];
for(i=0;i<data.length;i++){
queueMicrotask (console.log.bind (console, " info..."+i));
}
A better way would be to make a console.print function that does so and call it instead of console.log as pointed out in https://stackoverflow.com/a/64444083/1818089
// console.print: console.log without filename/line number
console.print = function (...args) {
queueMicrotask (console.log.bind (console, ...args));
}
Beware of the grouping problem mentioned above.

Opening specific MimeTypes with google picker

I am working with realtime API and I am making use of realtime-client-utils.
Using existing code, I am creating realtime files with this method:
createRealtimeFile = function(title, callback) {
gapi.client.load('drive', 'v2', function() {
gapi.client.drive.files.insert({
'resource': {
mimeType: rtclient.REALTIME_MIMETYPE,
title: title
}
}).execute(callback);
});
}
and then I am trying to open files with this picker:
var popupOpen = function () {
var token = gapi.auth.getToken().access_token;
var view = new google.picker.View(google.picker.ViewId.DOCS);
view.setMimeTypes(rtclient.REALTIME_MIMETYPE+ "."+realTimeOptions.appId);
var picker = new google.picker.PickerBuilder()
.enableFeature(google.picker.Feature.NAV_HIDDEN)
.setAppId(realTimeOptions.appId)
.setOAuthToken(token)
.addView(view)
.addView(new google.picker.DocsUploadView())
.setCallback(pickerCallback)
.build();
picker.setVisible(true);
};
Although if I use the setMimeTypes, no documents are found. If I remove that filter, my documents appear normally(along with every time of document in the drive). THe mime type I am using is the default one:
rtclient.REALTIME_MIMETYPE = 'application/vnd.google-apps.drive-sdk';
I am adding the appID as this is how its done on realtime-playground. I also tried without the "." or the appID but no documents are found.
Any suggestions about how to fix the filter ?
You should look for mimeType you created with.
You created your file with mimeType rtclient.REALTIME_MIMETYPE and you're looking for files with mimeType rtclient.REALTIME_MIMETYPE+ "."+realTimeOptions.appId That is the reason why you're not getting any files.
Change filepicker code to:
view.setMimeTypes(rtclient.REALTIME_MIMETYPE);
And make sure you change
rtclient.REALTIME_MIMETYPE = 'application/{{YOURE_CUSTOM_MIMETYPE}}';
to avoid collision with other apps.
Found the answer on an android forum. I had to create files with this mimeType:
REALTIME_MIMETYPE = 'application/vnd.google-apps.drive-sdk.'+appID;
and use same mimeType on view :
view.setMimeTypes(REALTIME_MIMETYPE);
Short answer: correct your appID. It is the first part of your CLIENT-ID. That is, if your client-id is 1088706429537-4oqhqr7o826ditbok23sll1rund1jim1.apps.googleusercontent.com, your APP_ID is 1088706429537. I initially confused it with the project-id and had your problem.
I started new answer because all accepted ones are wrong since they disregard the main issue. Might be they are right advising to change the mime-type to something completely custom. Yet, the most popular answer says that
You created your file with mimeType rtclient.REALTIME_MIMETYPE and
you're looking for files with mimeType rtclient.REALTIME_MIMETYPE+
"."+realTimeOptions.appId
This is seems natural but wrong fact. The topic author is right reproducing the demos. Google demos seem to do exactly that: they create application/vnd.google-apps.drive-sdk file and look for appid-expanded ones. This is correct actually because whenever you create a REALTIME_MIMETYPE=application/vnd.google-apps.drive-sdk file, google drive silently fixes file type! It expands it with .APP_ID. This also means that you do not need to customize your mime-type, as other answers propose. You can proceed with application/vnd.google-apps.drive-sdk, I suppose. I have checked: Google will even fix your mime type if some letters are misspelled, which makes the proposed customization even harder. Google API will take another mime type only if it is too different from REALTIME one. But I am not sure that this is a right thing to do, despite this is proposed by all other accepted answers as 'solution' because I do not know about the difference this implies. Nevertheless,
to to tie up, the reasons to use application/vnd.google-apps.drive-sdk instead of proposed custom types are:
This standard type is proposed by Google in realtime playground demos instead of custom type
Google calls this type 'REALTIME_MIMETYPE'. If your use something different, you use Realtime API to create non-realtime files. At least not that much realtime as proposed by google.
If you specify a different mime type, google still corrects it to REALTIME_MIMETYPE. This means that custom mime types are unwelcomed.
Proponents of customization do not understand anything of this. They even dare to laugh at Google official examples as 'ridiculous'. They basically say that we should not trust them.