How are Javascript widgets made without iFrames? - reactjs

I have a chat widget that I want to embed it other people's websites. It looks just like Intercom and all the other chat popups. I want to make the chat popup stick to the bottom-right hand corner of the screen regardless of where you scroll. However, when I import the chat app as an iframe and give it position: fixed; bottom: 0px; right: 15px;, the iframe does not go to where I expect it to go.
I realize that iframes are suboptimal for embedded JS widgets, and all the best embedded apps are importing .js files from file storage. After searching online for hours I have yet to find an explanation/tutorial on how to make those JS files that hook onto a and render the widget. How do you even make one of those pure javascript apps, and what are they called? (Not web components I assume, because there have been widgets for a long time).
Sorry if this question is kinda noob. I never knew this was a thing until I tried implementing it myself. Can anyone point me in the right direction on how to get started making JS web widgets? Thank you! (Maybe a ReactJS to VanillaJS converter would be super cool)

A pure Javascript App is called a SPA - Single Page Application - and they have full control over the document (page). But since you ask about embeding a widget, I don't think that is what this question is about (there are tons of info. on the web on SPAs).
I was going to suggest that going forward you do this using Web Components - there are polyfills available today that make this work on nearly all browsers - but since your question mentioned that you wanted to know how it is done without it, I detail below one of my approaches.
When creating a pure JS widget you need to ensure that you are aware that a) you do NOT have control over the global space and b) that it needs to play nice with the the rest of the page. Also, since you are not using Web Components (and are looking for a pure javascript (no libs)), then you also have to initialize the widget "manually" and then insert it to the page at the desired location - as oposed to a declaritive approach where you have an assigned HTML tag name for your widget that you just add to the document and magic happens :)
Let me break it down this way:
Widget Factory
Here is a simple Javascript Widget factory - the create() returns an HTML element with your widget:
const Widget = Object.create({
create(chatId) {
const wdg = document.createElement("div")
wdg.classList.add("chat-box");
wdg.innerHTML = `<h1>Chat: ${ chatId }</h1>`;
// Load your chat data into UI
return wdg;
}
});
To create a new widget (HTML Element) using the above you would:
const myWidgetInstance = Widget.create("chat-12345");
and to insert this widget into the page at a given location (ex. inside of a DIV element with id "chat_box", you would:
document.getElementById("chat_box").appendChild(myWidgetInstance);
So this is the basics of creating a Widget using the native (web) platform :)
Creating a reusable/embeddable Component
One of the key goals when you deliver a reusable and embeddable component is to ensure you don't rely on the global space. So your delivery approach (more like your build process) would package everything together in a JavaScript IIFD which would also create a private scope for all your code.
The other important aspect of these type of singleton reusable/embeddable components is that your styles for the Element needs to ensure they don't "leak" out and impact the remainder of the page (needs to play nice with others). I am not going into detail on this area here. (FYI: this also the area where Web Component really come in handy)
Here is an example of a Chat component that you could add to a page anywhere you would like it to appear. The component is delivered as a <script> tag with all code inside:
<script>(function() {
const Widget = Object.create({
create(chatId) {
const wdg = document.createElement("div");
wdg.classList.add("chat-box");
wdg.innerHTML = `<h1>Chat: ${ chatId }</h1>`;
// Load your chat data into UI
return wdg;
}
});
const myWidgetInstance = Widget.create("chat-12345");
const id = `chat-${ Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1) }`;
document.write(`<div id="${ id }"></div>`);
document.getElementById(id).appendChild(myWidgetInstance);
})();</script>
So you could use this in multiple places just by droping in this script tag in the desired locations:
<body>
<div>
<h1>Chat 1</h1>
<script>/* script tag show above */</script>
</div>
...
<div>
<h1>Chat 2</h1>
<script>/* script tag show above */</script>
</div>
</body>
This is just a sample approach of how it could be done. You would have to add more in order to support passing options to each widget (ex. the chat id), defining styles as well other possible improvements that would make the runtime more efficient.
Another approach
You could add your "script" once and wait for the rest of the page to load, then search the document for a "known" set of elements (ex. any element having a CSS Class of chat-box) and then initialize a widget inside of them (jQuery made this approach popular).
Example:
Note how data attributes can be used in DOM elements to store more data specific to your widget.
<div class="chat-box" data-chatid="123"></div>
<script>(function() {
const Widget = Object.create({
create(chatId) {
const wdg = document.createElement("div");
wdg.classList.add("chat-box");
wdg.innerHTML = `<h1>Chat: ${ chatId }</h1>`;
// Load your chat data into UI
return wdg;
}
});
const initWhenReady = () => {
removeEventListener("DOMContentLoaded", initWhenReady);
Array.prototype.forEach.call(document.querySelectorAll(".chat-box"), ele => {
const myWidgetInstance = Widget.create(ele.dataset.chatid);
ele.appendChild(myWidgetInstance);
});
};
addEventListener('DOMContentLoaded', initWhenReady);
})();</script>
Hope this helps.

The best way to create Javascript widget without third-party library is to create Custom Elements.
The following link : Custom Elements v1 is a good introduction to this technology.
See a minimal example below:
class Chat extends HTMLElement {
connectedCallback () {
this.innerHTML = "<textarea>Hello</textarea>"
}
}
customElements.define( "chat-widget", Chat )
<chat-widget>
</chat-widget>

Related

Trouble implementing Google Analytics gtag.js in React-based Chrome Extension

My problem in a nutshell: The window object that gtag.js operates on and the window object available in my react context (a content.js context) are different objects, and so I can't write events from my react code -- meaning I can't use analytics in my extension.
More deets:
In react <script> tags can't be loaded directly for various reasons. So I've change the documentation implementation:
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'GA_MEASUREMENT_ID');
</script>
To
export const gtag = (...args) => {
window.dataLayer.push(args)
}
export const loadAnalytics = (ga_property) => {
const script = windowdocument.createElement("script")
script.src = `https://www.googletagmanager.com/gtag/js?id=${ga_property}`
script.async = true
window.document.body.appendChild(script)
window.dataLayer = window.dataLayer || []
gtag('js', new Date())
gtag('config', ga_property, { 'transport_type': 'beacon'})
gtag('event',
'test', {
event_category: 'lookup',
event_label: 'test'
}
)
}
...
componentDidMount() {
loadAnalytics("UA-175XXXXXX-1")
}
I've come to understand through much research and gnashing of teeth that the window object in my content.js and the window object that is acted on in gtag.js once it is loaded are not the same object, and are intentionally "shadows" of each other, but still separate objects. From the documentation:
"Content scripts live in an isolated world, allowing a content script to makes changes to its JavaScript environment without conflicting with the page or additional content scripts.
Isolated worlds do not allow for content scripts, the extension, and the web page to access any variables or functions created by the others."
From what I can tell this seems to be irreconcilable without a re-write of the gtag.js source.
For reasons I still don't understand this code which references window.document
const script = window.document.createElement("script")
script.src = `https://www.googletagmanager.com/gtag/js?id=${ga_property}`
script.async = true
window.document.body.appendChild(script)
And this code in the same file which references window.document
export const gtag = (...args) => {
window.dataLayer.push(args)
}
End up pointing to two different window objects.
This post seems to reinforce that these two contexts can't communicated directly in terms of objects and functions (only messages).
For gtag.js to work in an extension, I'd need to be able to call window.dataLayer.push(...) on the window of the main web page from inside my chrome extension. And that doesn't seem possible.
Any bright ideas out there as to how to either:
Make gtag.js be loaded in the proper window.document and/or refer to the content.js context of window
or
be able to access the window object of the main page from the content.js context
Since extension code can have multiple contexts, it would be wise use the principle of separation of concerns to avoid multiple document issue altogether.
When developing extensions it is advised to run majority of your code in the background, to make use of the separate JavaScript runtime allocated for your code by the browser (and avoid slowing down the pages user is visiting or the code which appears as your extension UI). Additionally, in most cases, it is a good idea to ship the code you want to run packaged within the extension bundle. If you want to load an external resource, to your background script "document", you can use XHR and eval to execute code.
When code is executed in the background, it is available to your extension UI and content scripts using the extension and DOM messaging protocols.
First, initialize your extension in the context of your background script(s).
Then, register a message handler which will evaluate messages sent by other extension code and look for a key (usually message.type) that identifies message as carrying analytics data (usually message.payload).
Read the content of the messages that match the criteria in the handler, and use the supplied information to invoke analytics APIs.
Finally, send analytics events occurring in your UI or content scripts as messages to the background script.
This way your background script is the only place where analytics is set up, clearing up document ambiguity, your code is cleaner, because there is only one place where analytics code is accessed and the extension runs smoother because it's UI and content scripts don't need to load or know about analytics code.

Using iframes within React Application

We are building an enterprise application using react, redux and react-redux stack. I got stuck in one of the designs and need your help.
We have got some html content, coming from the backend(PZN) and we don't wanna use dangerouslySetInnerHTML(as it's not the trusted source) , instead we would like to use iframe, where src attribute will point to locally stored one of the templates with some placeholders [values will be replaced after the backend call]. How do I bind values to the templates. do anybody know any client side templating engines like handlebars, EJS?
Ex:
const Frame = ({src}) => {
return(
<iframe src = {src} ref = {(frame) => this.frame = frame} />
)
The above thing holds good If I just wanna dump the html content into the iframe, but I want to bind the some data values into template before being rendered on the UI?

Usercontrol equivalent in html5

I am trying to find the equivalent of .Net usercontrol in the html5 SPA world. Does react component fall under this or angular directives. I want to create self contained sections of page made of multiple rendering components put together (grid,chart etc) that talks to a socket. And I want to reuse those by pointing to a different data source.
An example would be a chart and a grid (with underlying data) together with a button to flip between chart and grid. How will I make this as one component that can be used multiple times in same application and also shared to different applications.
Yes, You can use Reactjs or Angularjs, I would recommend Reactjs in case if the sections' state are changing many times, in reactjs every component have a state, Reactjs knows when to re-render the component because it is able to observe when this data changes. Dirty checking is slow regarding the reactjs.
In other word Reactjs can give you much better performance, but note that Reactjs is only for the UI, no routing or other like in Angularjs.
I hope that helps, good luck
I've done this exact thing with a simple combination of JS objects, jQuery and Handlebars. No need for Angular et al if you don't want them (and nothing wrong with them if you do). I've defined my components as JS Objects:
/**
* A data grid.
*/
function Grid(columns, $container) {
this.columns = $.extend([], columns);
...
this.render($container);
}
/**
* Render the grid into a container.
* #private
*/
Grid.prototype.render = function($container) {
Rendering the view is done with Handlebars:
<div class="grid">
<header>
<div class="label">
{{#each columns}}<div data-id="{{id}}" data-index="{{#index}}" style="width:{{boxWidth}}px">{{name}}</div><nav></nav>{{/each}}
</div>
The Model is obtained by ajax calls:
/**
* Handle a grid filter change.
* #private
*/
MasterCompanyList.prototype.onFilter = function(e, filter) {
App.loading.start();
App.ajax({
url: 'ws/admin/mcl',
data: filter.filter,
context: this,
success: function(json) {
this.grid.setData(json);
App.loading.done();
}
});
};
All at a simpler level than Angular etc (I've used Angular commercially for a few years), but I find this KISS approach gets me where I need to be without having to rely on a heavier framework - and if I want to manipulate something jQuery styles then off I go.
Angular and React can do this, but React is based on a component design, so it would be perfect for what you're trying to achieve.

Node Webkit / Angular / Change Model

Sorry for a not very specific question by someone new to node webkit, new to Angular, new to about everything in web development:
My app is based on a JSON file that I load at the init of my node webkit app and which is at the center of a bunch of calculations.
In the app, one can open a file dialog to create a new JSON file. Now, of course, I would like the app to recalculate everything based on the new JSON. It works when I press the "refresh" button of node webkit, but I couldn't get it running by using either
require('nw.gui').Window.open('index.html');
nor
require('nw.gui').Window.get().reload(3);.
I am also wondering if handling this on the node level is the good way to do it. Shouldn't it rather be done by Angular? But I couldn't really connect to the content of my controller from an "outside" javascript.
Grateful for any hint...
Having logic on the page loading is always tricky and as you mentioned - requires page reloading what is not very elegant and modern applications avoid this.
In your case, I suggest that if your JSON file is not very big - store it in variable and modify it as needed. The elegant way will be to create Angular service, which can act as a "model".
angular.service('JsonService', function() {
var json = {
// content
};
return {
getJson: function () {
return json;
},
setJson: function (newJson) {
json = newJson;
}
};
});
Then, whenever you need to update JSON invoke setJson(newJson) method and modify your controllers to use the service getJson() method.
You can also add the loading/saving to file functions to this service. The loading function can be invoked in your main controller connected to your dashboard page. Then before the first page will be visible, the JSON file will be already loaded and you preserve desired behavior.

GWT, access RichTextArea by non-GWT (or "regular") javascript

I am writing a pretty simple CMS on GAE, and I want my users to be able to upload images.
I have written the part that does the actual uploading and showing the images, and here's what I'd like to do:
Show the usual form for new posts (with a widget that contains a rich text area and a format bar for it) and the list of images the user has ever uploaded (done). Then i want an image to appear in the text area when the user clicks that image.
I generate the list of images on the server, and i can't find a way to call any methods on the Rich Text Area from non-GWT javascript. And I don't really want to generate the list of images by means of AJAX, because it seems quite cumbersome and, hopefully, with the advent of HTML5 it is going to be much simpler.
Well, the question is, how can i access a RichTextArea in a widget from a normal javascript on a page, or is there another way of inserting an image into it (i.e. another is there a way to generate a list of images so that they would be in a kind of widget, but without the use of AJAX).
Thank you.
To answer your general question of getting access to your GWT code from hand written Javascript, you can use the general built in method or Ray Cromwell's gwt-exporter project. This way, you can expose the specific methods of the RichTextArea instance you're trying to access from external Javascript.
As for your other question, generating a list of Images should only require getting the urls for the images and creating a bunch of Image objects with the given URLs. Then display this list in a PopupPanel or some other widget.
Here is how i finally solved the problem (thanks to Arthur's reply above):
public class NewSection implements EntryPoint {
private static RichTextAreaWithFormatBar rta;
private Button pseudoSubmit;
#Override
public void onModuleLoad() {
invokeExternal("hello");
rta = new RichTextAreaWithFormatBar();
pseudoSubmit = new Button(">>>");
<...>
}
<some other code here>
public static void addImage(String a) {
rta.textarea().setHTML(
rta.textarea().getHTML() + "<br /><img src=\"/cms/i/"+ a +"\" alt=\"\"><br />");
}
native void invokeExternal(String int1) /*-{
$wnd.externalJsFunction(function(int1) {
#ur.g05.smc.client.NewSection::addImage(Ljava/lang/String;)(int1);
});
}-*/;
}
And here is the "hand-written" javascript in my templates:
(first the FreeMaker code for creating the image list in that same template):
<#list images as i>
<td><img src="/cms/i/${i.keyString()}.t" alt='' onclick='addImage("${i.keyString()}.p");'/><br /><p>${i.fullWidth()}·${i.fullHeight()}</p><p>${i.previewWidth()}·${i.previewHeight()}</p><p>${i.thumbWidth()}·${i.thumbHeight()}</p></td>
</#list>
And the script itself:
<script language="javascript">
var callBackFunction;
function externalJsFunction(func) {
callBackFunction = func;
}
function addImage(imgid) {
callBackFunction(imgid);
}
</script>
What in fact happens is:
First we create a list of images, adding an "onClick" listener to each of them with the url of each corresponding image as the argument. Image urls are formed from their Keys in the datastore, plus ".t" for thumbnails, ".p" for previews and nothing for full-size images.
Each image than calls the "addImage" function. But the addImage function has to know about the textarea, which it doesn't. To that end we create the "callBackFunction" variable, and the "externalJsFunction", that sets the value of that "callBackFunction" variable. And it sets it to whatever it gets as the argument.
Now, we can call that externalJsFunction from our Widget code and pass the function that adds an image to the textarea. However, i couldn't make it work while the richtextarea was not static.
That's basically it.
And thanks for replies and votes :)

Resources