Load an Ember.js app from another website - javascript

I'm working on a project where I'd like to load an Ember.js application from another website, hosted on a different server and using a different domain name. In other terms, I'd like to include an Ember app in other website like I would do with an iFrame, but without an iFrame.
I built my Ember.js application using Yeoman and the Ember generator.
In the origin website, I just have a simple markup like this:
<body>
<h1>My website</h1>
<p>Lorem ipsum...</p>
<div id="myEmberApp"></div>
<p>Lorem ipsum...</p>
</body>
I know how to call an external JS file, but don't know from here how to execute or to load my Ember.js app. I tried also with CORS, but I don't think it will suit my needs.
For the records, I can't use an iFrame. On the origin website, I don't want to have any dependencies against jQuery or whatever. In the future, I'd like to be able to offer a step by step how-to to integrate this app on any websites.
Is there any solution? Or should I have to plan to do my app in full JS without Ember.js?
Let me know if you need more information.
Thanks in advance
--- Edit ---
Here's how I call my JS file from the origin website:
<!-- The JS script to be included by the client -->
<script>
(function () {
var eh = document.createElement('script');
eh.type = 'text/javascript';
eh.async = true;
eh.src = '//localhost:9000/scripts/edenhome.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(eh, s)
})();
</script>
I hope this helps.

Perhaps I am missing something, but why not just include your application in a script tag? I'm not familiar with the yeoman generator, but most of these build tools kick out an ember application with templates precompiled into JavaScript functions and then all your files concatenated together in a single file.
For example:
// this would be the precompiler output generated by yeoman, not exactly like
// this, but same idea, the template is part of the JS file
Ember.TEMPLATES['application'] = Ember.Handlebars.compile('<h1>I am an ember app</h1>');
// make an app and set the rootElement
var App = Ember.Application.create({
rootElement: '#myEmberApp'
});
You can see here I'm loading in the ember app from a different domain:
http://emberjs.jsbin.com/mawuv/1/edit
edit follow up
Injecting the script works just as well as a script with a src attribute:
http://emberjs.jsbin.com/mawuv/2/

I would suggest the following,
disclaimer: i have not actually tried what you are asking, but i do usually load everything from separate files but within the context of the same domain.
create a function that loads all templates and appends them to the document.
create a function that runs the complete ember.js app.
create the file that will be referenced by the remote site and will search for a specific element (this could also be parametrized / be a jquery plugin etc) and once found loads all templates calling function from (1) and start the application calling function from (2) with rootElement the specific element mentioned earlier. (Prior to any of the previous loadings, all dependencies of the ember.js app should be loaded e.g. jquery, handlebars.js, ember.js etc. probably in a no conflict state)
All ajax requests to the server from the ember app should be carried out using jsonp.
simplified example,
http://emberjs.jsbin.com/nelosese/1/edit
js
/*it is possible to load templates from independent files, using promises, however here it is just demonstrated the loading of templates*/
/*a callback is added since they are loaded async and only when this is done should the app complete initialization */
function initTemplates(callback){
var templates ='<script type="text/x-handlebars"><h2> Welcome to Ember.js</h2>{{outlet}}</script><script type="text/x-handlebars" data-template-name="index"><ul>{{#each item in model}}<li>{{item}}</li>{{/each}}</ul></script>';
$('body').append(templates);
callback();
}
function initApp(){
App = Ember.Application.create({
rootElement: '#myEmberApp'
});
App.Router.map(function() {});
App.IndexRoute = Ember.Route.extend({model: function() {
return ['red', 'yellow', 'blue'];
}
});
}
/*a reference to your bootstrap script, that executes the following*/
$(document).ready(function(){
if($('#myEmberApp').length>0){
$.get("/",function(){initTemplates(function(){
initApp();
});});
}
});
html
<body>
<div id="myEmberApp"></div>
</body>

Related

Load ReactJS App from another server and execute

Let's assume we have a website, that should show a reactjs application.
The following points are necessary:
The ReactJS application should be embedded by using a short snippet (script / html)
The ReactJS app should be updated without changing the snippet itself
The ReactJS app is hosted on a completely different server
It should not be an iFrame if possible
So what I want to achieve is similary to a Google Map for instance. You have a small snippet and you can show an application on your side.
What are the best practices to do so ? What do I have to take into consideration ?
"Micro frontends":
https://medium.com/#tomsoderlund/micro-frontends-a-microservice-approach-to-front-end-web-development-f325ebdadc16
I came across this idea only recently. So, I don't have much to tell you regarding your requirements. But it looks promising. But also may be an overkill.
And by following links you'll be able to find some code examples.
E.g. https://single-spa.js.org/docs/examples/
simple example without iframe
<script>
(function(window,document, id, scriptUrl, appId){
// create container where to render app
var containerDiv = document.createElement("DIV");
containerDiv.id=appId
document.getElementById(id).appendChild(node);
// add script tag
var scriptTag = document.createElement('script');
scriptTag.type = 'text/javascript';
scriptTag.src = scriptUrl;
document.body.appendChild(scriptTag);
// also you may need to send you app info about where should render (id)
window.MY_WIDGET_ID = appId
})(window,document, 'where-to-render-app-id', 'script-url', 'app-id');</script>
<script >
// inside your react app you should add
render( document.getElementById(window.MY_WIDGET_ID ))
A regular React application is a set of JS(let's ignore the CSS, images, other assets this time) files. And there is a file called the entry which mounts the entire application to a specific dom. You might be familiar with the below code.
ReactDOM.render(<App/>, document.getElementById('app'))
The above code is auto executed usually once the entry is loaded onto a predefined dom. We can expose it as an initial handler of the application.
window.apps = {} // This better be executed in a script hosted by the website
window.apps['my-app'] = dom => ReactDOM.render(<App/>, dom)
The script hosted by the website then is able to start the application by calling the function above.
window.apps['my-app'](document.getElementById('root'))
In this way, the website takes the control of initial a React application, at any time, onto any dom, or even any instance.
ps. Ignore type checks, null checks. You should add it as you need to make sure no runtime error happens.
As an other option, you could wrap your react app into web component. Here's the example. But it could be overengineering, from case to case, mpc's approach could be more reasonable easily.
you can create a shell app that will load your remote code and run it.
btw, check out fronty, it is a micro-frontend tool that can help you with that with no hassle.
This is one of the feature React offers. If you take the Basic files provided by React it is an HTML page with a <div id="root"></div>. By default, React is built as a single page App. In fact, you can edit directly this HTML file (located in Public folder) and the React will still run.
So to achieve what you are looking for, build your React project include it to your HTML project (The same include present in the public/index.html -> <script src="/static/js/main.******.chunk.js"></script>.
In the React project, you add the same render condition:
if (document.getElementById("root")) {
ReactDOM.render(
<App />
document.getElementById("root")
);
}
Wrapping the ReactDom.render in an if is to make sure the desired ID is present in your dom.
That's it, it should be working.
Have fun.

Synchronous XMLHttpRequest on the main thread is deprecated issue in ASP.NET MVC & Page.js

In order to make our application SPA, we have used a third party library Page.js along with ASP.NET MVC.
Page.js Reference: https://visionmedia.github.io/page.js/
We perform basically two tasks by making use of Page.js library.
First, we hit some controller/action (partial view) on MVC application & then inject the returned HTML into DOM.
Secondly, at the same time, we change the URL of the browser using this library.
All the Partial Views which we are fetching through Page.js also has the reference to <script/> tag. So whenever we inject the returned HTML into DOM, we start seeing the deprecation issue in browser's console tab.
[Deprecation] Synchronous XMLHttpRequest on the main thread is
deprecated because of its detrimental effects to the end user's
experience. For more help, check https://xhr.spec.whatwg.org/.
Application Flow:
Below picture explains the flow of our application.
Currently, all the child partial views are also having their own script & that script gets executed as soon as they are injected in DOM.
Attempted approach as a solution to fix this Deprecation issue:
On each partial view(not the child one), that is injected via Page.js library, we are writing below code to load all the dependencies asynchronously. And in the callback function (once all the dependencies are loaded), we are executing the page-specific javascript code like below.
HTML of the Page that is injected into DOM via Ajax (i.e. Page Partial View):
<div>
......
......
</div>
<!-- This part causes issue -->
<!--<script type="application/javascript" src="/script1.js"></script>
<script type="application/javascript" src="/script2.js"></script>-->
<script>
$(function () {
$.getMultiScripts(["#scriptFile1", "#scriptFile2"]).done(function () {
onPageLoad();
});
function onPageLoad() {
// 1. Defer kendo scripts
#Html.Kendo().DeferredScripts(false);
// 2. Initialize partial view 1
partialView1.init();
// 2. Initialize partial view 2
partialView2.init();
// 3. Initialize partial view 3
partialView3.init();
}
});
</script>
Important: $.getMultipleScripts was taken from here - How to include multiple js files using jQuery $.getScript() method
The script at the end of child Partial View looks like below:
<script>
window.partialView1 = (function() {
var init = function() {
// 1. Initialize partial control
tabControl.initControl();
// 2. Initialize partial control
common.bindEvents();
};
return {
init: init
};
})();
</script>
Note: For this, we have also stopped child partial views to execute the javascript code as soon as they are loaded. In fact, we are now just providing the javascript definitions (in the form of module pattern) in partial views & controlling the whole execution from the main page (parent partial view).
I know that this is not a good approach. We could have used some frontend framework. But at this stage, we can't take that decision to make the entire framework shift.
From this post, I just wanted to know, whether this approach is good or there could be a better approach to resolve this issue. If yes, please share.

How to trigger onclick function in router for PugJS?

I'm currently using a expressJs template that uses pug in order to make a simple website. On the site is a button. I want on the click of the button to call a function in the index.js file.
Originally I had the code like this. In the index.pug file:
extends layout
block content
h1= title
p Welcome to #{title}
button#BubbleButton.button.hypeButton.button-3d.button-caution.button-circle.button-jumbo(onclick = 'buttonClick()') Click
In my index.js file, it would look like so:
var express = require('express');
var router = express.Router();
var PubNub = require('pubnub');
module.exports = router;
function buttonClick(){
alert('test');
}
However, I would get a response along these lines:
buttonClick function not found.
I'm frankly more familiar with angular than pug. What does it take to link up the view side to the router file? I understand in Pug, you can add an endpoint REST API style, but this isn't necessarily going to be an API. I may just want to show something simple and not expose it. However, I may be misunderstanding something fundamental.
Thanks!
The JavaScript code contained in the script. tags in your pug file will only run in the browser, and the code in index.js will only run on the server. Therefore they can never speak directly.
You need an ajax call on the client (i.e. in your pug file). Here's an example using jquery but you can use one of the many ajax client packages out there to do the same thing:
script.
function onButtonClick(){
$.ajax({
"url": "/service",
"method": "POST"
});
}
Then you need a route handler in index.js:
router.post('/service', function(req, res){
console.log('test');
});
Note that this will output to the console on the server, not the client. I believe that's what you want to do.
To add jquery to your page you can use a cdn (Content Delivery Network) to quickly bring it into your code. Here is the pug syntax for that:
script(src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous")
Alternatively you could you axios as a lightweight alternative to jquery, all it does is ajax:
script(src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js" integrity="sha256-mpnrJ5DpEZZkwkE1ZgkEQQJW/46CSEh/STrZKOB/qoM=" crossorigin="anonymous")

How to pass a globally configured CDN url to templates in AngularJS?

this should be simple but after extensive googling I havent found an answer.
I use cloudfront as a CDN for my web app. I would like to configure the cloudfront url to a single file, as it might change in the future. I am trying currently to pass the value to the the templates as they use some images via css style background. I have found out that one solution would be to put in the scope in every controller, but there so many of them, and including this to all would make the code more possible for errors if it is forgotten.
So, this is a code example I have tried:
app.js:
.constant('CDNurl', 'https://xxxxxxxxxxx.cloudfront.net/')
main.js:
$rootScope.CDN = CDNurl;
navbar.html:
<div class="avatar__image"
ng-style="{'background-image':'url(' +
$root.CDNurl + currentUser.profileImage+')'}">;
does not work. If I include it in the navbar controller it of course works, but I need to pass it globally.
So what is the correct way of doing this? Configure something and reach it from templates?
If you have a parent controller at the top of the DOM and in that you set $rootScope.CDN = CDNurl;, then you can access CDNurl is available in all the child views.
Or another option might be to use run (I haven't tried this):
var app = angular.module('module', []);
// run blocks
app.run(function($rootScope, CDNurl) {
$rootScope.CDN = CDNurl;
});

How do I make JS know about the application root?

I have some javascript that's referencing /jobs/GetIndex in an MVC project. This works great when I run it locally because I have a Controller called JobsController. When I deploy it, I deploy the application to a 'Jobs' subfolder, which means the URL should become http://site/jobs/jobs/GetIndex but it's going to http://site/jobs/GetIndex, I presume because the javascript doesn't know what the 'root URL' is, and uses the site as the root. How can I get it to work in both environments?
If you simply care about getting the root/base url of the site so you can append that to get the other url you are after, you may simply use / as the first character of your url.
var urlToJobIndex2= "/jobs/GetIndex";
Here is an alternate approach if you want more than just the app root (Ex : Specific urls( built using mvc helper methods such as Url.RouteUrl etc)
You may use the Url.Content helper method in your razor view to generate the url to the app base, assign it to a javascript variable and use that in your other js code to build your other urls.
Always make sure to use javascript namespacing when doing so to avoid possible issues with global javascript variables.
If you want to get url to a specific action method, you may use the Url.Action or Url.RouteUrl helper methods.
So in your razor view (Layout file or specific view), you may do this.
<script>
var myApp = myApp || {};
myApp.Urls = myApp.Urls || {};
myApp.Urls.baseUrl = '#Url.Content("~")';
myApp.Urls.jobIndexUrl= '#Url.Action("GetIndex","jobs")';
</script>
<script src="~/Scripts/PageSpecificExternalJsFile.js"></script>
And in your PageSpecificExternalJsFile.js file, you can read it like
var urlToJobIndex= myApp.Urls.jobIndexUrl;
// Or With the base url, you may safely add the remaining url route.
var urlToJobIndex2= myApp.Urls.baseUrl+"jobs/GetIndex";
Here is a detailed answer about the same , but using this in a specific page/view
Angular Js
You might need the correct relative url(s) in your angular controller / services / directives.The same approach will work for your angular code as well. Simply build the js namespace object and use the angular value provider to pass the data to your angular controllers/services/directives as explained in this post in detail with example code.
Add <base href="/jobs"> to your head tag. It will specify the root.
This is further explained here https://docs.angularjs.org/guide/$location

Categories

Resources