Loading a var from window global into React App not working - javascript

I know there are lots of answers to this. My purpose is to have a js file that I can use terraform to inject into a docker image so I can easily change the client logo during deployment and have a single docker image for my react ui. I have tried the solutions in the following answers:
how-can-i-pass-a-variable-from-outside-to-a-react-app
how-to-include-external-javascript-in-react
external-javascript-is-not-working-in-react-js
react-accessing-a-var-from-a-script-in-a-component
The simplest method seems to be to import a script in head in index.html, with the var declared therein. I have the following in my index.html and env.js is in the static folder where other js files (plotly) are loaded successfully.
...
<script src="http://localhost:3000/env.js"></script>
</head>
I have the following in env.js
window.logo_file = '/images/demo.jpg';
Then in my React component I have:
<div className={'clientsLogo'}>
<img
src={window.logo_file}
alt={'client-logo'}
height={61}
style={{ verticalAlign: 'bottom' }}
/>
</div>
I also tried adding export default logo_file using logo_file = '/images/demo.jpg'; in env.js.
I tried assigning the window.logo_file to a const prior to using it.
But the imported script does not have it's variable added to the global window object and it escapes me why not.

The answer is that the env.js, i.e. the file with your window global variable declared, has to go at the top of the head section of your index.html file to be loaded before the react app is loaded.

Related

I would like to use a <script> in my Angular component, how do I do that ? (TypeScript, Angular12)

This is the code that I would like to use in my Angular component:
<form action="https://www.meu-site.com/processar-pagamento" method="POST"> <script
src="https://www.mercadopago.com.br/integrations/v1/web-tokenize-checkout.js"
data-public-key="ENV_PUBLIC_KEY"
data-transaction-amount="100.00"></script></form>
If you have to load the script from remote site you can add it to your index.html file, like you would do in a regular html site.
index.html
<!-- get script from 3rd party site -->
<script
src="https://www.mercadopago.com.br/integrations/v1/web-tokenize-checkout.js"
data-public-key="ENV_PUBLIC_KEY"
data-transaction-amount="100.00">
</script>
Then, to use the the script in your component, you need to manually declare the exported variables / functions to avoid TS compilation errors
** formComponent.ts**
declare let web-tokenize-checkout: any; //declare the name of the global object you got from the script you added
//use your script functionality, That part is really depends on how the script you imported works
web-tokenize-checkout.doWhatEverYouNeed(....);

How to fetch HTML and CSS files for web components when hosted on a different domain than the application itself?

I am looking for a clean solution to split web components into JS, HTML and CSS files and host them on a CDN. I try to avoid the webpack html and css-loader as they dont allow me to export my web component as a plain ES module.
The goal is to use a web component from any frontend app just by importing it from a spcified URL. Thereby seperation of concerns should be preserved. Individual files for style, markup and logic also allow for syntax highlighting.
In a local dev environment I found the following to work great:
WebComponent.js:
export default class WebComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
const style = new CSSStyleSheet();
const template = document.createElement("template");
fetch("./WebComponent.css").then((res) =>
res.text().then((css) => {
style.replaceSync(css);
this.shadowRoot.adoptedStyleSheets = [style];
})
);
fetch("./WebComponent.html").then((res) =>
res.text().then((html) => {
template.innerHTML = html;
this.shadowRoot.appendChild(template.content.cloneNode(true));
})
);
}
}
WebComponent.css:
button {
/* Some styling */
}
WebComponent.html:
<button>Custom buttom</button>
I can import the component by using browser native ES module imports:
index.html:
<!DOCTYPE html>
<html>
<body>
<web-component></web-component>
<script type="module">
import WebComponent from "./WebComponent";
customElements.define("web-component", WebComponent);
</script>
</body>
</html>
This works until I move the web component files to a different location (a google cloud storage bucket) than my index.html and import WebComponent.js from there.
<!DOCTYPE html>
<html>
<body>
<web-component></web-component>
<script type="module">
import WebComponent from "https://storage.googleapis.com/storage-bucket/WebComponent.js";
customElements.define("web-component", WebComponent);
</script>
</body>
</html>
WebComponent.js gets imported correctly but it then tries to fetch WebComponent.css and WebComponent.html from a URL relative to localhost where index.html is served. However it should fetch from a URL relative to where it is hosted (https://storage.googleapis.com/storage-bucket/).
Any ideas how something like that can be achieved? Without hard coding the url into both fetch calls. That's not an option as the url can change automatically from time to time.
You are having issue with linking resources in the JS web page for which :
local component is working
import WebComponent from "./WebComponent";
remote component is failing
import WebComponent from "URL";
It might be that for this to work you should try this :
<script type="module" src="https://storage.googleapis.com/storage-bucket/WebComponent.js">
customElements.define("web-component", WebComponent);
</script>
References :
https://cloud.google.com/storage/docs/hosting-static-website
https://www.npmjs.com/package/webcomponent?activeTab=readme
https://lit-element.polymer-project.org/guide/use
JavaScript file paths are relative to the displayed page. So the behavior you are observing is expected.
You can use a JavaScript variable with a simple js declaration like below and use this variable across whenever you assign URLs dynamically:
<script type="text/javascript">
var webComponentPath = 'https://storage.googleapis.com/storage-bucket/';
</script>

How to use mailerlite popups in a Next.js application

I'm integrating a mailerlite popup for a client's next.js project, and I'm having a difficult time converting the JavaScript snippets into the jsx required to make the popups function properly. On first load it seems to work just fine, but on relaod I'm getting the following error.
window is not defined
I've encountered the issue while dealing with DOM manipulation, but in this case, judging from the code in the snippet, I need the window object.
Install the following snippet of Javascript on every page of your website right before the closing tag.You only need to add this snippet once, even if you plan to have a few different webforms.
<!-- MailerLite Universal -->
<script>
(function(m,a,i,l,e,r){ m['MailerLiteObject']=e;function f(){
var c={ a:arguments,q:[]};var r=this.push(c);return "number"!=typeof r?r:f.bind(c.q);}
f.q=f.q||[];m[e]=m[e]||f.bind(f.q);m[e].q=m[e].q||f.q;r=a.createElement(i);
var _=a.getElementsByTagName(i)[0];r.async=1;r.src=l+'?v'+(~~(new Date().getTime()/1000000));
_.parentNode.insertBefore(r,_);})(window, document, 'script', 'https://static.mailerlite.com/js/universal.js', 'ml');
var ml_account = ml('accounts', '912433', 'd5p1f7l9g0', 'load');
</script>
<!-- End MailerLite Universal -->
I've placed this code in my Layout wrapper. As previously stated, it works fine on first load, but as soon as the user navigates to a new page above error shows up.
PS I found an old question regarding this topic here, but it's old and not quite relevant to my situation. I need to figure out how to convert the above snippet for nextjs. Any help at all would be appreciated.
This approach treats the MailerLite universal tag as its own <script> hosted on your site's domain.
Add a NextJS custom document.
Create a JavaScript file containing the MailerLite universal tag code in ./public. I put mine in ./public/scripts/ml.js.
Add a <script> tag loading #2 in your custom _document.js file:
import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }
}
render() {
return (
<Html>
<Head>
<script async src="/scripts/ml.js"></script>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument
Everything worked as intended from there! (Caveat: I'm only using embedded forms).

VueJS components

I want to make use of VueJS components to clean up my scripts. Therefore I have a component for each page I am loading in into Laravel. Everything works fine so far. But I have issues transfering my current script logic to a component.
This is the current script setup importing the to be used components:
main.js
import HomeView from './components/HomeView.vue';
import IncidentView from './components/IncidentView.vue';
window.app = new Vue({
el: '.content',
components: {
HomeView, IncidentView
},
methods: {
// Init GIS
init: function() {
// Initialize GIS Map
initGISMap(this.$els.map);
},
}
(...)
}
Key for me is the window.app = new Vue... part. I make use of google maps and therefore when the page is loaded it searches for an app.init method. This is part of the script I am loading within the blade template:
<!DOCTYPE html>
<html>
<body class="hold-transition skin-blue sidebar-mini">
<section class="content-header">
<h1>
#yield('page_title')
<small>#yield('page_description')</small>
</h1>
</section>
<!-- Main content -->
<section class="content">
#if (isset($vueView))
<component is="{{ $vueView }}">
#endif
#yield('content')
</component>
</section>
<!-- /.content -->
<script src="https://maps.googleapis.com/maps/api/js?key=KEY&libraries=places&callback=app.init" async defer></script>
</body>
</html>
The individual pages (where I create for each a module in Vue) look like this:
#extends('app', ['vueView' => 'home-view'])
#section('page_title', 'title')
#section('page_description', 'title')
#section('content')
content
#endsection
By defining the vueView variable, the correct module I am importing in my script is used.
The goal is to use HomeView component as the main google maps view. And the other components for different pages I load when clicking the corresponding link in my theme. At the end, I do not want to have all VueJS code in one script. Therefore the models.
When I transfer all the content of this current JS file, I get an error complaining that app.init is not a function. The component looks like this:
<script>
export default {
data: {
// SEARCH
addressComponents: '',
autocompletePlace: '',
autocompleteAddress: '',
(...)
}
How do I modify my component in a way, that the app.init load would still work?
Someone has already mentioned GuillaumeLeclerc/vue-google-maps which is available on npm as vue-google-maps, but be warned that that version only works with vue 1.x
If you are using v2, look at this great vue2 fork xkjyeah/vue-google-maps - it's available as vue2-google-maps on npm.
The API is straightforward and doesn't diverge far from the v1 version, and the repository is much more active than its upsteam counterpart.
It really made my vue map work a lot more painless than rolling my own, which is what I was initially doing.
I hope that helps
Maybe you should use, or at least study the code, of a package that does that.
For example, you can check vue-google-maps on Github : https://github.com/GuillaumeLeclerc/vue-google-maps/
It defines a whole lot of Components related to Google Maps.
If I understand you right, you have a Google Maps script which, when loaded, calls window.app.init, right?
Does your Vue component have an init() method (none is shown above)? If there was one, it would need to be at app.methods.init() remember for VueJS, standard callable methods live under methods key.
Alternatively: you don't mention if your app.init is part of the Vue component or not. If its not it would appear that your app.init function is being overridden because you are redefining window.app as your vue component.
Lastly, if your init is part of Vue, is your google maps fn callback calling this init() before the Vue is loaded and therefore no such method exists (yet)?

Include Javascript on Certain Pages in Phoenix Framework Application

I've got a bit of Javascript that I only want to include on certain pages in my Phoenix application.
Right now I've got the Javascript inside a script tag in myapp/web/templates/post/form.html.eex.
I understand that I can move the JavaScript to web/static/js/app.js ...but I don't want to include the Javascript on every page (it's only required on 2 specific pages).
What's the best way to load this section of Javascript on certain pages in my application without duplication the code and violating the DRY principle?
1.
Put all that javascript from form.html.eex into its own file (maybe something like js/posts.js).
Add this at the bottom:
export var Post = { run: function() {
// put initializer stuff here
// for example:
// $(document).on('click', '.remove-post', my_remove_post_function)
}}
2.
In your app.html, under <script src="#{static_path(#conn, "/js/app.js")}"></script> add this:
<%= render_existing #view_module, "scripts.html", assigns %>
3.
Then, in your view (probably views/post_view.ex), add a method like this:
def render("scripts.html", _assigns) do
~s{<script>require("web/static/js/posts").Post.run()</script>}
|> raw
end
Conclusion
Now the javascript file post.js will only be loaded when the post view is being used.
Here is one way to achieve this.
The JavaScript you have in the script tag, you move that into a separate file.
You divide your "regular" javascript (to be included in every page) and this custom javascript (to be included in some specific pages) into separate directories. e.g. app/common/standard.js and app/custom/unique.js
You modify your brunch-config.js to as follows:
module.exports = {
files: {
javascripts: {
joinTo: {
'custom.js': /^app[\\\/]common[\\\/][\S*?]\.js/,
'app.js': /^app[\\\/]common[\\\/][\S*?]\.js/
}
}
}
Then you include app.js in all pages,
<script src="<%= static_path(#conn, "/js/app.js") %>"></script>
but custom.js only in page (or layout) templates that need it.
<script src="<%= static_path(#conn, "/js/custom.js") %>"></script>
Another way is to make use of page-specific classes/elements. For example, the following code in app.js will ensure that the code only gets executed on the lesson/show page, since only that page has an element with the id #lesson-container:
import { startLesson } from './lesson/show.ts';
if (document.querySelector('#lesson-container')) {
startLesson();
}
This is based on Gazler's comment on the question and is a slightly more general answer than the one submitted by cmititiuc. You don't strictly need to wrap your page-specific JavaScript code like in that answer, nor do anything beyond import your page-specific file in the page-specific script element.
Layout templates
Use Phoenix.View.render_existing/3 in your layouts like this:
<head>
<%= render_existing #view_module, "scripts.html", assigns %>
</head>
... or this:
<head>
<%= render_existing #view_module, "scripts." <> #view_template, assigns %>
</head>
For the first example, this will render a "scripts.html" template if one exists for the relevant view module.
For the second example, a "scripts." <> #view_template template, e.g. scripts.form.html, will be rendered if it exists.
If the 'scripts' template does NOT exist for a view module, nothing will be output in the page HTML.
View modules
For the first example using render_existing/3 in the layout template, you'd add code like this to the post view module:
def render("scripts.html", _assigns) do
~E(<script src="file.js"></script>)
end
... and for the second you'd add code like this:
def render("scripts.show.html", _assigns) do
~E(<script src="show-file.js"></script>)
end
def render("scripts.index.html", _assigns) do
~E(<script src="index-file.js"></script>)
end
Details
The difference between render_existing and render is that the former won't raise an error if the referenced template does NOT exist (and nothing will be output in the page HTML in that case either).
The ~E sigil provides "HTML safe EEx syntax inside source files" and is similar to (in most cases, or maybe even always) the corresponding code from cmititiuc's answer:
~s{<script>require("web/static/js/posts").Post.run()</script>}
|> raw
Conclusion
In general then, for any page for which you want to import specific JavaScript files via script elements in the page head (or at the end of the body), or link CSS files, or do anything to the page output in a portion thereof otherwise handled by the layout, you'd use render_existing in the layout template as above and then implement appropriate render clauses in the view modules for those pages.
And further, there's no reason why you couldn't use something like both of the two examples above so that, for any view module and its templates, you could both:
Include some script(s) (or CSS files or otherwise manipulate the HTML output of in a layout template) for all the view module templates (but not all templates for the entire app)
Include some script(s) (or ...) for only a single template
<script src="myscripts.js"></script>
Put your code in a new .js file. Include the script tag with a source to the file path in the relevant html files.

Categories

Resources