27

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

Michael
  • 32,527
  • 49
  • 210
  • 370

4 Answers4

6

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";
Community
  • 1
  • 1
Michał Perłakowski
  • 88,409
  • 26
  • 156
  • 177
  • I was confused with `div.innerHTML = count;`, missed the comment you added. Does JSONP has some advantages over CORS? – Nik Markin Jan 11 '16 at 12:13
  • 1
    no one should support IE <= 7 :D, ok, thank you. Still, for the widget kind app (embedded code) it's kinda scary to rely on global available callback functions :). Obviously it's easy to call it somehow like __[appname]_callback, but it still feels wrong in my opinion. – Nik Markin Jan 11 '16 at 12:25
  • @Gothdo Have a look at https://www.livechatinc.com/ and their chat widget. The iframe has ``src="javascript:false"``. What does this mean? – Michael Jan 11 '16 at 15:57
  • @Gothdo What about using ``p.setAttribute('crossorigin','*');``? – Michael Jan 11 '16 at 19:15
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/100409/discussion-between-confile-and-gothdo). – Michael Jan 11 '16 at 20:17
  • Any chance someone can help me get an idea what servers I can use for such purpose? Can I use AWS? What service of AWS? How about Firebase? Thank so much – TheBen Feb 25 '19 at 22:45
1

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

Tobika
  • 1,037
  • 1
  • 9
  • 18
  • I tried both before. The problem is that if you define a CSS on your main site your GWT widget will use the same CSS rules. I want that the CSS of the site where the GWT widget is integrated should not influence the CSS of the GWT widget. – Michael Jan 11 '16 at 09:05
  • whats the way of loading your css?just including it into the host.html? – Tobika Jan 11 '16 at 09:45
  • Since I have no influence other that my code snipped on the host site, they probably have a css in the header. – Michael Jan 11 '16 at 09:47
  • I will prevent that the CSS of the host page influence the CSS of my GWT widget. Any idea? – Michael Jan 11 '16 at 10:55
  • No not that way. I want to have access to tho host website from my GWT widget too. – Michael Jan 11 '16 at 11:17
  • by the way this was not your question...you just wanted to know how to dont influence your widget by css from the thirdpartywebsite. anyway..did you already check if your widget is affected by the css from the thirdpartysite?? because the class names from your module probably wont have the same name, since they got obfuscated(example: `.GA0432JCGI{color:red;}`if not then you would have easy access to your hostpage with `$wnd` – Tobika Jan 11 '16 at 13:00
  • The problem is that when the host page has ``div { background-color: red; }`` and your FlowPanel does not override background-color, then you get the background color red. – Michael Jan 11 '16 at 13:46
  • if its only that then you could add `#parentElementOfYourWidget.div { background:blue;}` to your css. this changes bg color of all divs inside of your widget – Tobika Jan 11 '16 at 14:08
  • would be nice to get any feedback on the recommened options since all of them work for me – Tobika Jan 11 '16 at 14:08
  • Let me rephrase my goals. 1. The GWT widgets appearance should not be modified by the host page CSS. 2. the GWT widget should be able to access the URL of the host page. How can I do that? – Michael Jan 11 '16 at 15:16
  • updated my comment again. Edit1 makes sure that the widget is not influeced by any css from outside the iframe. Edit2 shows an example of how to access the URL of the host page. – Tobika Jan 11 '16 at 15:28
  • 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. – Michael Jan 11 '16 at 15:49
  • 1
    @confile Pls edit your question and mention all these requirements. – sujit Jan 11 '16 at 16:21
  • updated answer...you should get the concept of communicating between iframe and its parent. if not you should really read my posted links... – Tobika Jan 11 '16 at 16:41
0

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 :).

Nik Markin
  • 931
  • 1
  • 8
  • 13
  • I did not get it. How does this belong to my question and to GWT? – Michael Jan 11 '16 at 15:49
  • @confile Doesn't my example covers some of your questions regarding the build process of such widgets and one particular widget from the list you have provided? the implementation would still be the same in general regardless of how the final js scripts are built. I saw that one of previous answers was also posted not in GWT, and you didn't say that you don't need js solutions, so I decided to bring some basic but full a-z example. – Nik Markin Jan 11 '16 at 16:10
0

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

Community
  • 1
  • 1
Alex Nikulin
  • 8,194
  • 4
  • 35
  • 37