How to link a paper-button to another Polymer page? - javascript

I created a Polymer 2.0 app from the starter kit template in order to get to know the framework a little bit. I have a paper-button that should link to another page of the app. However, I still haven't figured out how to do so, since Polymer is loading pages dynamically via JavaScript rather than the browser just calling another one.
I also noticed something else strange: When I click a link in my app-drawer, the page changes automatically and the URL in my browser tab is being updated. However, when I hit refresh with that new URL in my address bar, I get a 404 error since the URL doesn't exist. So is there any way I can resolve this issue and link my button to another page?
This is my button:
<paper-button id="buttonStartQuiz" on-click="startQuiz">
go! <iron-icon icon="chevron-right"></iron-icon>
</paper-button>
And this is the JavaScript class that corresponds to the layout:
class MyView1 extends Polymer.Element {
static get is() { return 'my-view1'; }
/* This method is the listener for the button click */
startQuiz(e) {
// Found this on a website, but doesn't work
this.set('route.path', '/view-question');
}
}
window.customElements.define(MyView1.is, MyView1);
I don't know if it's any useful, but here are my imports in case you need to know.
<link rel="import" href="../bower_components/polymer/polymer-element.html">
<link rel="import" href="../bower_components/iron-icons/iron-icons.html">
<link rel="import" href="../bower_components/paper-input/paper-input.html">
<link rel="import" href="../bower_components/paper-button/paper-button.html">

The fact is Polymer doesn't do that, some element (app-route which implement with Polymer) do that. The Polymer itself is the library that help you work with custom element easier.
This behavior done by JavaScript and History API. See how to use it on mdn. An application like this, dynamically rewriting the current page rather than loading entire new pages its called a single-page application (SPA).
Basically application like this have only one page (index.html). When you try to load from another path the server will cannot find it.
You can resolve this by config the server to serve every path you used with index.html. For development you can easily use polymer serve command from polymer-cli see here.
To link to another page you can done by many ways:
=> Wrap your element with <a>:
<a href='/another-page'>
<paper-button>...</paper-button>
</a>
=> Change route variable from app-location: in my-app.html
<app-location route='{{route}}'></app-location>
...
<paper-button on-click='changeRoute'>...</paper-button>
class MyApp extends Polymer.Element {
...
changeRoute () {
this.set('route.path', '/another-page')
}
...
}
If you want to do this in your file just import and use app-location.
=> Use History API
window.history.pushState({}, null, '/another-page');
// Notify to `app-location`
window.dispatchEvent(new CustomEvent('location-changed'))

Related

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

How to import external html file to React app?

I have a project in a middle of development. And I need to use external landing page as a home page. Therefore, I need to import landings index.html, but it has its own folders with css and js(mainly Jquery code).
I wanted to import it as <iframe src={html}></iframe> into my project but my app doesn't seem to load htmls.
What are best ways to import html files that use own jquery code to react?
This is a bit tricky, and there might be other ways (perhaps better) to achieve the same result. Also, I would consider the performance impact of loading multiple libraries into an existing React app.
With that humble disclaimer out of the way, one way to do this would be to include jQuery directly into React's main index.html page using <script> tags, this will make $ globally available across the app:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
</body>
</html>
Once this is done, place the landing page project folder (along with its dependencies) inside the public directory:
Then, from the main app component load the desired landing page using fetch, then use .text() to transform the retrieved page into regular text.
Use setState to set the retrieved HTML into the app state, then inside render() use a regular <div> container to store the landing page and use the React attribute dangerouslySetInnerHTML to set HTML inside that target container.
Finally, I pass an anonymous function (as a second parameter to setState) and use jQuery's getScript() to load and execute the required JS libraries that the landing page depends on.
In the example, I loaded Bootstrap's JS, which is needed to power the Carousel.
Bootstrap's CSS is loaded directly from the landing page's HTML file using a standard <link> tag.
import React from "react";
class App extends React.Component {
state = {
page: null
};
componentDidMount() {
fetch("landing-page-one/index.html")
.then(result => {
return result.text();
})
.then(page => {
this.setState(
{
page: { __html: page }
},
() => {
window.$.getScript("landing-page-one/js/index.js");
}
);
});
}
render() {
const { page } = this.state;
return (
<>
<h2>
<span>Inserting project using React's </span>
<code>dangerouslySetInnerHTML</code>:
</h2>
<div
dangerouslySetInnerHTML={page && page}
/>
</>
);
}
}
export default App;
Working example here.
I'm just going to through this out there as a spitball... I've never tried it, but why not directly replace index.html and not render anything on that page? You would have to either adjust the build somehow... I have no idea how to, or manually put it after the react app finishes building. I'm sure it would be a bug nightmare, but it honestly sounds a little less of a hastle than an iframe which might give you some weird UI behavior. React-Server, even though it says server, really just takes stringified JSX and I think html translates it back to workable code. You could also try that approach, but this does sound like a nightmare project.
I was also trying to find out a solution and figured out two ways for different use cases:
If your HTML file contains <div> and other child tags (that can be nested under other <div> or <body> tags) then this approach will work for you:
First you need to configure webpack to be able to parse HTML files.
You can either do that with npm eject or use another module
react-app-rewired - and then add a HTML loader in overrides.
Then import the HTML file and use a parser. I believe it's better than using dangerouslySetInnerHTML.
Example with Method 1:
const parse = require("html-react-parser");
const docs = require("../../public/redoc-static.html").default;
const ApiDocs = (props: any) => {
return (
<Box sx={{ display: "flex", height: "100vh" }}>
<Box>{parse(docs)}</Box>
</Box>
);
};
If your HTML file contains a <html> tag (is a complete file) following the above approach would give the very obvious error <html> cannot appear as a child of <div>. In that case, the workaround I found is to open the whole HTML file in a different tab of the browser with _blank:
Example with Method 2:
window.open(filename, "_blank")
Note: The HTML file has to reside in the public folder of your project.

Is it possible to create a web component anyone can use without installation?

I'm just getting started with web components, and if I understand correctly, the point is for them to be reusable by anyone. Is it possible to create a component that can be used by anyone simply by adding a piece of html to their static site (similar to how JavaScript widgets are added, simply by copy-pasting a few lines of code), or does it need to be installed by someone? Or is this not an intended use case of web components?
Yes. A Web Component is a kind of "Javascript widget".
Typicially, you define a Web Component in a Javascript file.
You can then include it in any HTML with a <script> element.
Example with a minimal custom element called <hello-world>:
In hello-world.js:
customElements.define( 'hello-world', class extends HTMLElement {
constructor() {
super()
this.attachShadow( {mode: 'open' })
.innerHTML = 'Hello World!'
}
} )
In your main page index.html:
<html>
<head>
<!-- import the web component -->
<script src="hello-world.js">
</head>
<body>
<!-- use the new element -->
<hello-world></hello-world>
</body>
</html>
Note: alternately, one could also copy-paste the Javascript code that defines the custom element to its main HTML page.

Using third-party dependencies in polymer (pikadate)

I'm creating a polymer datepicker using pikaday. Sadly it seems like I got something wrong.
I'd like to import pikaday.js and pikaday.css the right way.
At first I had a simple script-tag below my closing dom-module-tag, like
</dom-module>
<script src="../../pikaday/pikaday.js"></script>
<script>
Polymer({
//....
This way, the datepicker was created as expected. But after reading this SO-Thread I was under the impression I was supposed to import the js-file like this:
<link rel="import" href="../../paper-input/paper-input-behavior.html">
<link rel="import" href="../../paper-input/paper-input-error.html">
<link rel="import" href="../../pikaday/pikaday.js">
//more imports....
But after "fixing" my import, the file pikaday.js seems not to be visible from inside my component:
Uncaught ReferenceError: Pikaday is not defined
Furthermore I'm confused about using external css. After reading this guide it seems like I was supposed to copy & paste the contents of the provided css-file into a my-datepicker-style.html and to import it into my template like this:
<dom-module id="my-datepicker">
<template>
<style include="my-datepicker-style"></style>
<style>
:host {
//more css
I'm confused about the need to copy & paste existing code.
Until ES6 imports are more common, you need some kind of workaround for referencing dependencies.
The problem with <script> tag is that when it appears multiple times, it will be processed multiple times. This is not true for <link rel="import">. Same href will be processed only once.
You cannot, however, import javascript directly. The trick is to create pikaday-import.html file with the script reference
<script src="../../pikaday/pikaday.js"></script>
You then import that in your element's html
<link rel="import" href="pikaday-import.html" />
<dom-module id="my-datepicker"></dom-module>
This is the technique for example the <marked-element> uses.
This way instances of <my-datepicker> load pickaday only once. Unfortunately, if there are other components which reference it, you could end up loading the dependency multiple times.

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)?

Categories

Resources