The powers that be have asked me to look into this and as much as I think it's not possible I want to be sure before going back to them. Don't ask me why they want this haha
They want to be able to use JavaScript that is defined in a variable. Let me explain...
Let's say you have this variable:
var testvar = "function dan() { alert('hello world'); }";
They want to be able to call dan() and have an alert popup in the web page. Something like this doesn't appear to be useable (of course).
var importjs = document.createElement('script');
importjs.src = testvar;
document.getElementsByTagName("head")[0].appendChild(importjs);
Any ideas or jargon I can use to explain why it's not doable. I believe it's essentially a cross origin issue. Our solution requires users to install software, which uses web sockets. This software performs a file GET for the JS and we then want to use the JS in the browser.
You should set the innerHTML of the script element to your String. The src of a script element should only be used to load an external script.
var testvar = "function dan() { alert('hello world'); }";
var importjs = document.createElement('script');
importjs.innerHTML = testvar;
document.getElementsByTagName("head")[0].appendChild(importjs);
dan();
I have a Google Sheets file with an attached Script. The script does a number of things, one is it makes a clone of it self using makeCopy. This portion works. Now I want to be able to keep the same cloned Google file name and same Google file ID and just update the content which includes a Spreadsheet and the associated Google script.
if (!fileFound){
var file = masterSSFile.makeCopy(reportFileName, RepFolder);
} else {
oldFile.setContent(masterSSFile.getBlob());
}
When I use makeCopy with the same file name it creates a second file with the same name but with a different file ID.
The else portion fails because .setContent argument seems to just accept text. The result is the word "Blob" in the oldFile, everything else is gone.
I have other scripts that update the contents of a existing spreadsheet by overriding the contents of the various sheets, but I also want the associated script to also be included in the updated file keeping the same file ID.
I found this....
Overwrite an Image File with Google Apps Script
and tried using
var masterSpreadsheetID = SpreadsheetApp.getActiveSpreadsheet().getId();
var masterSpreadsheetFile = DriveApp.getFileById(masterSpreadsheetID);
var oldFileID = oldFile.getId();
var oldFileName = oldFile.getName();
var newBlob = masterSpreadsheetFile.getBlob();
var file = {
title: oldFileName,
mimeType: 'application/vnd.google-apps.spreadsheet'
};
var f = Drive.Files.update(file, oldFileID, newBlob);
I get error: "We're sorry, a server error occurred. Please wait a bit and try again. " on this line: "Drive.Files.update(file, oldFileID, newBlob);"
After reading this:
https://github.com/google/google-api-nodejs-client/issues/495
it looks like Drive.Files.update(), does not support bound scripts.
I needed to get deeper into the page so my content script is really just an injector, like this
function injectScript(file) {
var th = document.getElementsByTagName('body')[0];
var s = document.createElement('script');
s.setAttribute('type', 'text/javascript');
s.setAttribute('src', file);
th.appendChild(s);
}
injectScript( chrome.extension.getURL('/cs.js') );
Now I need to transport settings (stored in the content scripts local storage) into there as well during runtime. I can do it once no problem by creating another script node and adding the variable declaration and value in its innerHTML. Updating it this way doesn't work as well though.
Currently I have a hidden input added to the page and I write the value there and have the script on the other side periodically check and update the local one. This is just "ugh" and "eww" to me though. Is there any other way I can share the settings object between the two?
Most javascript widget which can be embedded into a website use the following structure. First you embed a code snipped like this:
<script type="text/javascript">
window.$zopim||(function(d,s){var z=$zopim=function(c){
z._.push(c)},
$=z.s=d.createElement(s),
e=d.getElementsByTagName(s)[0];
z.set=function(o){
z.set._.push(o)
};
z._=[];
z.set._=[];
$.async=!0;
$.setAttribute('charset','utf-8');
$.src='//v2.zopim.com/?2342323423434234234';
z.t=+new Date;
$.type='text/javascript';
e.parentNode.insertBefore($,e)})(document,'script');
</script>
Then, when load your page this script creates a html structure like this:
<div class="widget-class">
<iframe src="about:blank">
// the content of the widget
</iframe>
</div
I see this same structure in many chat services like:
https://en.zopim.com/
http://banckle.com/
https://www.livechatinc.com/
All have in common that their iframe does not have a src, i.e., an URL attached.
Update: Here is the script I use to load my widget code into a third party website:
<script type="text/javascript">
(function(d){
var f = d.getElementsByTagName('SCRIPT')[0], p = d.createElement('SCRIPT');
window.WidgetId = "1234";
p.type = 'text/javascript';
p.setAttribute('charset','utf-8');
p.async = true;
p.src = "//www.example.com/assets/clientwidget/chatwidget.nocache.js";
f.parentNode.insertBefore(p, f);
}(document));
</script>
I want that the CSS of the site where the GWT widget is integrated should not influence the CSS of the GWT widget. I will prevent that the CSS of the host page influence the CSS of my GWT widget.
Note: I want to have access to tho host website from my GWT widget too.
The domain of the host page is www.example.com and the domain of the iframe is www.widget.com. I also want to set cookies of the host domain from the iframe.
What is the procedure of building a widget running on such a structure? How is the content of the iframe being set? Is there a pattern for that? How can I do that with GWT
I don't know GWT, but you can easily achieve this in plain JavaScript.
Let's assume you're creating an online-count widget. At first, create an iframe:
<script id="your-widget">
// Select the script tag used to load the widget.
var scriptElement = document.querySelector("your-widget");
// Create an iframe.
var iframe = document.createElement("iframe");
// Insert iframe before script's next sibling, i.e. after the script.
scriptElement.parentNode.insertBefore(iframe, scriptElement.nextSibling);
// rest of the code
</script>
Then fetch the online count using JSONP (see What is JSONP all about?), for example:
// The URL of your API, without JSONP callback parameter.
var url = "your-api-url";
// Callback function used for JSONP.
// Executed as soon as server response is received.
function callback(count) {
// rest of code
}
// Create a script.
var script = document.createElement("script");
// Set script's src attribute to API URL + JSONP callback parameter.
// It makes browser send HTTP request to the API.
script.src = url + "?callback=callback";
Then handle server response (inside the callback() function):
// Create a div element
var div = document.createElement("div");
// Insert online count to this element.
// I assume that server response is plain-text number, for example 5.
div.innerHTML = count;
// Append div to iframe's body.
iframe.contentWindow.document.body.appendChild(div);
That's all. Your whole code could look like this:
Snippet to insert into third party website:
<script type="text/javascript">
(function(d){
var f = d.getElementsByTagName('SCRIPT')[0], p = d.createElement('SCRIPT');
window.WidgetId = "1234";
p.type = 'text/javascript';
p.setAttribute('charset','utf-8');
p.async = true;
p.id = "your-widget";
p.src = "//www.example.com/assets/clientwidget/chatwidget.nocache.js";
f.parentNode.insertBefore(p, f);
}(document));
</script>
JavaScript file on your server:
// Select the script tag used to load the widget.
var scriptElement = document.querySelector("#your-widget");
// Create an iframe.
var iframe = document.createElement("iframe");
// Insert iframe before script's next sibling, i.e. after the script.
scriptElement.parentNode.insertBefore(iframe, scriptElement.nextSibling);
// The URL of your API, without JSONP callback parameter.
var url = "your-api-url";
// Callback function used for JSONP.
// Executed as soon as server response is received.
function callback(count) {
// Create a div element
var div = document.createElement("div");
// Insert online count to this element.
// I assume that server response is plain-text number, for example 5.
div.innerHTML = count;
// Append div to iframe's body.
iframe.contentWindow.document.body.appendChild(div);
}
// Create a script.
var script = document.createElement("script");
// Set script's src attribute to API URL + JSONP callback parameter.
// It makes browser send HTTP request to the API.
script.src = url + "?callback=callback";
EDIT:
if you want your widget to not be influenced by any css from the "outside" you have to load into an iframe.
code to add to your website to load any gwt project/widget:
<iframe id="1234" src="//www.example.com/assets/Chatwidget.html" style="border: 1px solid black;" tabindex="-1"></iframe>
notice: that im NOT loading the nocache.js but the yourwidget.html file.
like this all your clases insde the frame wont be affected by any class from the outside.
to access anything outside ofthis iframe you can use jsni methods. this will only work if the domain of your iframe and the thirdpartysite are the same. otherwise youve to use window.postMessage:
public native static void yourMethod() /*-{
$wnd.parent.someMethodFromOutsideTheIframe();
}-*/;
EDIT2:
by using the snippet from above you make sure that your widget is not influened by any css from the hostpage.
to get the hostpage url from inside the widget simply add this function:
private native static String getHostPageUrl() /*-{
return $wnd.parent.location.hostname;
}-*/;
EDIT3:
since you are on 2 different domains, you have to use window.postMessage.
here one little example to get you going:
besides the iframe you have to add a event listener to the window of your example.com, that listens for the messages from your iframe. you also check if the messages comes form the correct origin.
<script>
// Create IE + others compatible event handler
var eventMethod = window.addEventListener ? "addEventListener"
: "attachEvent";
var eventer = window[eventMethod];
var messageEvent = eventMethod == "attachEvent" ? "onmessage"
: "message";
// Listen to message from child window
eventer(messageEvent, function(e) {
//check for the correct origin, if wanted
//if ( e.origin !== "http://www.widget.com" )
// return
console.log('parent received message!: ', e.data);
//here you can set your cookie
document.cookie = 'cookie=widget; expires=Fri, 1 Feb 2016 18:00:00 UTC; path=/'
}, false);
</script>
From inside your widget you call this method:
public native static void postMessageToParent(String message) /*-{
//message to sent, the host that is supposed to receive it
$wnd.parent.postMessage(message, "http://www.example.com");
}-*/;
i put a working example on pastebin:
javascript to insert into your page: http://pastebin.com/Y0iDTntw
gwt class with onmoduleload: http://pastebin.com/QjDRuPmg
Here's a full functional simple widget expample project I wrote in cloud9 (online IDE) with javascript, please feel free to request an access if you want to edit it, viewing is publicly available (for registered users - registration is free).
sources:
https://ide.c9.io/nmlc/widget-example,
result:
https://widget-example-nmlc.c9users.io/index.html
As for the question about how do they do it:
It seems that zopim builds their widgets gradually on the client side, defining and requiring basic modules (like these __$$__meshim_widget_components_mobileChatWindow_MainScreen), which are consist from submodules and then process everything with __$$__jx_ui_HTMLElement builder which creates HTML elements and appends them to provided parent nodes. All that compiles to the resulting HTML of the chatbox. Btw, judging by the names of some components, it seems, they build their widgets with some "meshim" library, but I have never heard of this library.
this.dom.src='about:blank'
this.appendToParent(!0)
var H=this.iwin=this.dom.contentWindow
var I=this.idoc=r.extend(H.document)
I.write(G)
I.close()
This, I guess, is the place where zopim service creates an iframe for their widgets. I'm not sure why they are using document.write instead of appendChild (document.write drops event bindings), but I have implemented both versions - they are pretty much the same except setIframeContents and addHtmlElement functions.
Hope someone will find this useful :).
1) There are many different ways to load content to iframe. Iframe have isolated content. iframe that you put in host page, does not have src, because of browser secure politic, you can't simply load content from other domains. But you can load js from other domain.For this porpuse you need usw JSONP
2) to share cookies with host page and widget iframe, you need use postMessage api like in this post
I am working on a Chrome Extension that works mainly within a pop-up.
I would like the user to enter some text (a string) into an input field in the pop-up, and this string will serve as a "variable" in a script I would like to inject and run on a specific page.
I have tried achieving this by making a content script that will execute the script, using the following well documented way:
var s = document.createElement('script');
s.src = chrome.runtime.getURL('pageSearch.js');
s.onload = function() {
this.parentNode.removeChild(this);
};
(document.head||document.documentElement).appendChild(s);
Basically, I would like to pass the user's input all the way to the code in pageScript.js before executing the script on the page.
What would be the best way to approach this? I will not be getting any information back to the extension.
Thanks.
To pass a variable from the popup to the dynamically inserted content script, see Pass a parameter to a content script injected using chrome.tabs.executeScript().
After getting a variable in the content script, there are plenty of ways to get the variable to the script in the page.
E.g. by setting attributes on the script tag, and accessing this <script> tag using document.currentScript. Note: document.currentScript only refers to the script tag right after inserting the tag in the document. If you want to refer to the original script tag later (e.g. within a timer or an event handler), you have to save a reference to the script tag in a local variable.
Content script:
var s = document.createElement('script');
s.dataset.variable = 'some string variable';
s.dataset.not_a_string = JSON.stringify({some: 'object'});
s.src = chrome.runtime.getURL('pageSearch.js');
s.onload = function() {
this.remove();
};
(document.head||document.documentElement).appendChild(s);
pageSearch.js:
(function() {
var variable = document.currentScript.dataset.variable;
var not_a_string = JSON.parse(document.currentScript.dataset.not_a_string);
// TODO: Use variable or not_a_string.
})();