I am trying to display my PDF in the browser in a new tab but I have a feeling React Router is taking over and not displaying it correctly.
Here is my front end React function that is supposed to open the PDF:
const openPDF = (e) => {
const prettyPDFName = e.target.textContent;
openPDFAsync(prettyPDFName);
console.log('pdfTextContent: ', prettyPDFName);
const prettyFileName =
prettyPDFName
.substring(0, prettyPDFName.length - 4)
.replace(/[ ,.]/g, '') + '.pdf';
window.open(`/pdf/${prettyFileName}`, '_blank');
};
That opens a new tab with the correct URL in the address bar. But it just shows my React app with the navigation at the top and blank content in the middle.
I found something on SO to prevent React Router from taking over for a specific route. So in my app.js I tried this:
const pdf_regex = /^\/pdf\/.*/;
// if using "/pdf/" in the pathname, don't use React Router
if (pdf_regex.test(window.location.pathname)) {
return <div />; // must return at least an empty div
} else {
// use React Router
return (
<Router>
...
That just displays a completely blank page and when I inspect, just an empty div which is to be expected I guess.
For more info, here is the Node code that gets called from the openPDFAsync(prettyPDFName); call:
router.get('/openPDFFile', async (req, res) => {
const pretty_PDF_name = req.query.pdf;
const pdfFilename = (await SDS.getPDFFileName({ pretty_PDF_name }))
.dataValues.sheet_file_name;
const cleanPDFName =
pretty_PDF_name
.substring(0, pretty_PDF_name.length - 4)
.replace(/[ ,.]/g, '') + '.pdf';
const pdfFilepath = `./path/to/file/${pdfFilename}`;
console.log(cleanPDFName, pdfFilepath);
router.get(cleanPDFName, async (req, res) => {
res.sendFile(__dirname + pdfFilepath);
});
Why not just open a new window or set the href then, so instead of returning just a div do one of the below
Parent window where your app exists - Has a message <div> Return to homepage </div> and do a window.open(url) which generates a new request, here have your node server handle the page directly without using the App middleware function that passes control to React router.
window.location.href- Remember, you are out of your app scope, I also believe passing target, whether _self or _blank will skip the router.
Related
I am trying to make a SPA with html, css and vanilla JS (I have very little idea of JS). The problem I have is that the method I'm using, works correctly, but when I open one of the sections in a new tab, it does not address the web correctly and gives an error "Cannot GET".
Is there any way to solve this, in a simple way, only with vanilla js?
const route = (event) => {
event = event || window.event;
event.preventDefault();
window.history.pushState({}, "", event.target.href);
handleLocation();
};
const routes = {
404: "./pages/404.html",
"/": "./pages/index.html",
"/vehicles": "./pages/vehicles.html",
"/services": "./pages/services.html",
"/contact": "./pages/contact.html",
"/financing": "./pages/financing.html",
"/locations": "./pages/locations.html",
};
const handleLocation = async () => {
const path = window.location.pathname;
const route = routes[path] || routes[404];
const html = await fetch(route).then((data) => data.text());
document.getElementById("main-page").innerHTML = html;
};
window.onpopstate = handleLocation;
window.route = route;
handleLocation();
Just like this:
window.open(location.href);
The API is a little unintuitive. With other parameters it can invoke a popup, but by default it opens a new browser tab.
I have a relative path in my code here:
//Nextjs config being imported to the react component
const domain = 'https://testsite.com'
let testUrl = `${domain}/images/logos/amt_logo.gif`;
let imageProps = {
src: {testUrl}
}
I have another website with some redirection code which is creating a double slash in the url as per below as you can see before images:
https://testsite.com/images/logos/amt_logo.gif --> https://www.testsite.com//images/logos/amt_logo.gif
How can I have some conditional code on my react page that goes: 'If page being redirected eliminate the second slash'?
You can use the vanilla JS URL constructor.
const base = "https://testsite.com/"; // works with or without trailing slash
const path = "/images/logos/amt_logo.gif"; // works with or without leading slash
const { href } = new URL(path, base);
const imageProps = {
src: href
}
I'm currently rendering Vue apps inside object tags (iframe could work too) of a container/master Vue app. First I setup a fileserver serving that container or the requested sub-app to render inside the div.
For the sake of simplicity I will only show the required routing of my Node/Express server
// serve the sub-app on demand
router.get('/subApps/:appName', function(req, res) {
res.sendFile(path.resolve(__dirname, `../apps/${req.params.appName}/index.html`);
});
// always render the app container if no sub-app was requested
router.get('*', function(req, res) {
res.sendFile(path.resolve(__dirname, '../base/index.html'));
});
My app container / master Vue app is inside the view file rendered on /apps/:appName, requests that sub-app and wraps it into object tags
document.getElementById("customAppContainer").innerHTML = `<object style="width: 100%; height:100%;" data="http://localhost:3000/subApps/${appKey}"></object>`;
This approach works fine but the rendered sub-app uses the startup url http://localhost:3000/subApps/app-one although it should use the url http://localhost:3000/apps/app-one. When the sub-app loads the router instance I have to change the startup url from the iframe url to the browser url (parent).
I thought about fixing that with history.replaceState
const router = new Router({ ... });
router.afterEach((to, from) => {
localStorage.subAppRouteUpdate = to.path;
});
if (window.location !== window.parent.location) {
const browserUrl = top.location.href;
history.replaceState(null, null, browserUrl);
}
export default router;
Why do I want to do this? The App.vue of the master app should append the sub-app route to the browser url. With this approach it's possible to update the browser url while navigating inside the sub-app. I achieve this by storing the sub-app url to the local storage and listen for local storage changes at the master app side. So the App.vue file uses this code
<script>
export default {
created() {
window.addEventListener("storage", () => {
const fullRoute = this.$router.currentRoute.fullPath;
const routeSegments = fullRoute.split("/");
const appsIndex = routeSegments.indexOf("apps");
const newBaseUrlSegments = routeSegments.slice(0, appsIndex + 2);
const newBaseUrl = newBaseUrlSegments.join("/");
const subAppRoute = localStorage.subAppRouteUpdate;
const updatedUrl = newBaseUrl.concat(subAppRoute);
history.replaceState(null, null, updatedUrl);
});
}
};
</script>
to enable a routing while using IFrames. It almost works, this is what I get
Unfortunately it happens that when calling / of the sub-app the browser url gets updated to
http://localhost:3000/apps/app-one/apps/app-one
although I'm expecting
http://localhost:3000/apps/app-one/
Reproduction:
I created a repository for reproduction / testing. Does someone know what might be wrong or how to fix that url updating?
Update:
I think the error occurs because in the router.js of the subApp I'm firing this code
if (window.location !== window.parent.location) {
const browserUrl = top.location.href;
history.replaceState(null, null, browserUrl);
}
router.afterEach((to, from) => {
console.log({ urlToAppend: to.path });
localStorage.subAppRouteUpdate = to.path;
});
The replaceState function will update the IFrame url from /subApps/app-one to the correct browser url /apps/app-one. Unfortunately this will trigger the afterEach event and to.path results in /apps/app-one although it should be /.
If the url would be /apps/app-one/users/create the after each event should trigger with /users/create of course.
But I didn't figured out how to fix this first triggered event.
It may be a bit hacky solution but it works for me. Just check that current url path is not equal to to.path in case if event is triggered twice
router.afterEach((to, from) => {
console.log({ urlToAppend: to.path });
if (router.currentRoute.path !== to.path) {
localStorage.subAppRouteUpdate = to.path;
}
});
UPDATE
In base/src/App.vue in storage event listener before concatenating sub route to base route you don't check for the possible duplicates. This fix should help
window.addEventListener("storage", () => {
const fullRoute = this.$router.currentRoute.fullPath;
const routeSegments = fullRoute.split("/");
const appsIndex = routeSegments.indexOf("apps");
const newBaseUrlSegments = routeSegments.slice(0, appsIndex + 2);
const newBaseUrl = newBaseUrlSegments.join("/");
const subAppRoute = localStorage.subAppRouteUpdate;
if (subAppRoute.startsWith(newBaseUrl)) {
history.replaceState(null, null, newBaseUrl);
} else {
const updatedUrl = newBaseUrl.concat(subAppRoute);
history.replaceState(null, null, updatedUrl);
}
});
And router.afterEach should look like this to navigate to subRoutes which are defined within app-one router:
router.afterEach((to, from) => {
const newPath = '/' + to.path.split('/').pop();
const matchingRoutes = router.options.routes.filter(r => r.path === newPath);
const isPathInRoutes = matchingRoutes.length > 0;
if (router.currentRoute.path !== newPath && isPathInRoutes) {
router.push(newPath);
localStorage.subAppRouteUpdate = newPath;
} else {
localStorage.subAppRouteUpdate = to.path;
}
});
If you want page one to be rendered by default when user goes to http://localhost:3000/apps/app-one you could check whether last part of the entered url is equal to sub apps base route(/app-one) and if it does navigate to default page route(/):
router.afterEach((to, from) => {
let newPath = '/' + to.path.split('/').pop();
const matchingRoutes = router.options.routes.filter(r => r.path === newPath);
const isPathInRoutes = matchingRoutes.length > 0;
if (newPath === router.history.base || !isPathInRoutes) {
newPath = '/';
}
if (router.currentRoute.path !== newPath) {
router.push(newPath);
localStorage.subAppRouteUpdate = newPath;
} else {
localStorage.subAppRouteUpdate = to.path;
}
});
What is the best way to manipulate DOM within an electron app?
I made some tutorials from docs using ipc and webcontents with no luck
My app is so simple, I just want to use the web like a console and showing messages (render proc) comming from the results of several sync functions (main proc)
I updated the question with real code.
I'm going to put another code, more simple to see and more simple to test (I think), is real code and works (but not like I want)
When I launch electron only writes the last message.
Ok, the response is really fast and I may not see the first messsage but to discard that I put a setTimeout and a large for() loop too, to make the uppercase function takes longer
index.js
const electron = require('electron');
const {app} = electron;
const {BrowserWindow} = electron;
const ipc = require('electron').ipcMain
app.on('ready', () => {
let win = new BrowserWindow({width: 800, height: 500});
win.loadURL('file://' + __dirname + '/index.html');
// Emitted when the window is closed.
win.on('closed', () => {
win = null;
});
// Event that return the arg in uppercase
ipc.on('uppercase', function (event, arg) {
event.returnValue = arg.toUpperCase()
})
});
index.html
<html>
<body>
<div id="konsole">...</div>
<script>
const ipc = require('electron').ipcRenderer
const konsole = document.getElementById('konsole')
// Functions
let reply, message
// First MSG
reply = ipc.sendSync('uppercase', 'Hi, I'm the first msg')
message = `Uppercase message reply: ${reply}`
document.getElementById('konsole').innerHTML = message
// Second MSG
reply = ipc.sendSync('uppercase', 'Hi, I'm the second msg')
message = `Uppercase message reply: ${reply}`
document.getElementById('konsole').innerHTML = message
</script>
</body>
</html>
You can comminicate between your frond-end and back-end with webContents and IPC. Then, you'll be able to manipulate your codes in front-end with backend's directive.
For Instance (Backend to Frontend);
Your main.js is sending a message to your front-end.
mainwindow.webContents.send('foo', 'do something for me');
Your frond-end will welcome that message here;
const {ipcRenderer} = require('electron');
ipcRenderer.on('foo', (event, data) => {
alert(data); // prints 'do something for me'
});
For Instance (Frontend to Backend);
Your frond-end;
const {ipcRenderer} = require('electron');
ipcRenderer.send('bar',"I did something for you");
Your back-end;
const {ipcMain} = require('electron');
ipcMain.on('bar', (event, arg) => {
console.log(arg) // prints "I did something for you"
event.sender.send('foo', 'done') // You can also send a message like that
})
UPDATE AFTER UPDATING QUESTION;
I tried your codes on my local, It seems like working.
Could you please try it with insertAdjacentHTML instead of 'innerHTML' to just make sure or just use console.log.
Like this;
document.getElementById('konsole').insertAdjacentHTML('beforeend',message);
"result" is a reference type value. "result" always chenge value when result = funcC() or another; Try this:
$('#msg').text(result.ToString());
I want to create an url-routing script using javascript as much as possible, but also accepting jQuery in the code. The js file has to change the url path (although I used location.hash instead of location.pathname) and the content of a div with the view id (from external files) accordingly.
Example configuration:
root/index.html
root/tpl/home.html
root/tpl/about.html
home.html content:
<p>This is content of home page</p>
about.html content:
<p>This is the content of the about page </p>
What I have done so far:
'use strict';
var Router = {
root: '/',
routes: [],
urls: [],
titles: [],
navigate: function() {
location.hash = this.root;
return this;
},
add: function(thePath, theUrl, theTitle) {
this.routes.push(thePath);
this.urls.push(theUrl);
this.titles.push(theTitle);
},
loading: function() {
this.navigate();
var r = this.routes;
var u = this.urls;
window.onload = function() {
$("#view").load("tpl/home.html");
};
window.onhashchange = function() {
for (var i = 0; i < r.length; i++) {
if (location.hash == r[i]) {
$("#view").load(u[i]);
}
}
};
}
};
Router.add("#/home", "tpl/home.html", "Home Page");
Router.add("#/about", "tpl/about.html", "About Page");
Router.loading();
Desired type of url:
http://mywebsite.com/
http://mywebsite.com/about
I know there are more than enough libraries that make the routing, like AngularJS and Crossroad, I want to know how this could be done.
To make this URL work - http://mywebsite.com/about - you need a server that knows how to route this request. Since the actual file name is about.html your server must know what to do with extensionless URLs.
Usually, the server uses the file extension as a clue for how to serve up content. For example, if it sees file.php it knows to use the PHP component, for .aspx it knows to use the ASP.NET component, and for .htm or .html it knows to respond with plain HTML (and usually serves the file instead of processing it). Your server must have some rules for what to do with any request, whether it has an extension or not, but without an extension you need to provide an explicit routing rule for that request..
The capabilities for JavaScript to do routing are limited because it requires the user to already be on your site. You can do some interesting things if you parse the URL parameters or use hashes and use them for routing, but that still requires requesting a page from your site as the first step.
For example: the server is already doing some level of "extensionless routing" when you give it this request:
http://mywebsite.com/
The parts of the URL are:
http - protocol
(port 80 is implied because it is default HTTP port)
mywebsite.com - domain AKA host
/ the path
The server sees / and uses what IIS calls a "default document" (I think apache calls it "default index" or "default page"). The server has been configured to return a file such as "index.html" or "default.htm" in this case. So when you request http://mywebsite.com/ you actually may get back the equivalent of http://mywebsite.com/index.html
When the server sees http://mywebsite.com/about it may first look for a folder named about and next for a file named about, but since your file is actually named about.html and is in a different folder (/tpl) the server needs some help to know how to translate http://mywebsite.com/about into the appropriate request - which for you would be http://mywebsite.com/#/about so that it requests the routing page (assuming it is the default document in the web app root folder) so that the browser can parse and execute the JavaScript that does the routing. Capisce?
You might be interested by frontexpress.
My library fix your case like below:
// Front-end application
const app = frontexpress();
const homeMiddleware = (req, res) => {
document.querySelector('#view').innerHTML = '<p>This is content of home page</p>';
}
app.get('/', homeMiddleware);
app.get('/home', homeMiddleware);
app.get('/about', (req, res) => {
document.querySelector('#view').innerHTML = '<p>This is the content of the about page </p>';
});
Obviously, you can get the template files from the server.
The #view will be feeded as below:
document.querySelector('#view').innerHTML = res.responseText;
More detailed sample in this gist
I have worked with what your answers and I have build the following Router. The only issue remains that it still uses location.hash
(function() {
var Router = {
root: '#/',
routes: [],
urls: [],
titles: [],
add: function(thePath, theUrl, theTitle) {
this.routes.push(thePath);
this.urls.push(theUrl);
this.titles.push(theTitle);
},
navigate: function() {
var routes = this.routes,
urls = this.urls,
root = this.root;
function loading() {
var a = $.inArray(location.hash, routes),
template = urls[a];
if (a === -1) {
location.hash = root;
$("#view").load(urls[0]);
}
else {
$("#view").load(template);
if (a === 0) {
window.scrollTo(0, 0);
}
else {
window.scrollTo(0, 90);
}
}
}
window.onload = loading;
window.onhashchange = loading;
}
};
Router.add("#/", "tpl/home.html", "Home Page");
Router.add("#/about", "tpl/about.html", "About Page");
Router.add("#/licence", "tpl/licence.html", "MIIT Licence");
Router.add("#/cabala", "tpl/cabala.html", "Cabala Checker");
Router.add("#/articles/esp", "tpl/article1.html", "ESP");
Router.add("#/fanfics/the-chupacabra-case", "tpl/article2.html", "The Chupacabra Case");
Router.navigate();
})();
Your request reads like what you're attempting to do is to add a "path+file" ("/tpl/about.html") to a base url ("www.myhost.com"). If that's the case, then you need to dynamically extract the host name from your current document and then append the new URL to the existing base. You can get the existing host name by executing the following commands in javascript:
var _location = document.location.toString();
var serverNameIndex = _location.indexOf('/', _location.indexOf('://') + 3);
var serverName = _location.substring(0, serverNameIndex) + '/';
This will return a string like: "http://www.myhost.com/" to which you can now append your new URL. All of this can be done in javascript prior to sending to the server.
If you only want the server name, without the leading http or https, then change the last line to be:
var serverName = _location.substring(_location.indexOf('://') + 3, serverNameIndex) + '/';
Lets break your code down a little bit:
function loading() {
"location.hash" is set based on the URL most recently clicked (www.myhost.home/#about). One essential question is, is this the action that you want, or do you want to pass in a value from the html for the onClick operation? It seems like that would be a more effective approach for you.
var a = $.inArray(location.hash, routes),
template = urls[a];
var a will be set to either -1 or the location of "location.hash" in the "routes array. If location.hash does not exist in routes, then a==-1 and the script will fail, because you're setting template = urls[-1]. You may want to move setting template to inside the "else" statement.
if (a === -1) {
location.hash = root;
$("#view").load(urls[0]);
}
else {yada yada yada
}
You could use a sequence in your html analogous to:
<a onclick="loading('#about')">Go to About Page</a>