How to open a new tab in spa with vanilla JavaScript? - javascript

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.

Related

Using pdf.js to render a PDF but it doesn't work and I don't get any error messages to help me debug the issue

I'm trying to build a Flask app where I upload pdf's and I'm working on previewing them before submitting to the back-end.
The script I'm using is as follows:
const imageUploadValidation = (function () {
"use strict";
pdfjsLib.GlobalWorkerOptions.workerSrc =
"https://mozilla.github.io/pdf.js/build/pdf.js";
const onFilePicked = function (event) {
// Select file Nodelist containing 1 file
const files = event.target.files;
const filename = files[0].name;
if (filename.lastIndexOf(".") <= 0) {
return alert("Please add a valid file!");
}
const fileReader = new FileReader();
fileReader.onload = function (e) {
const pdfData = e.target.result;
let loadingTask = pdfjsLib.getDocument({ data: pdfData })
loadingTask.promise.then(function (pdf) {
console.log("PDF loaded", pdf);
pdf.getPage(1).then((page) => {
console.log("page loaded", page);
// var scale = 1.5;
// var viewport = page.getViewport({ scale: scale });
var iframe = document.getElementById("image-preview");
iframe.src = page
// var context = canvas.getContext("2d");
// canvas.height = viewport.height;
// canvas.width = viewport.width;
// var renderContext = {
// canvasContext: context,
// viewport: viewport,
// };
// var renderTask = page.render(renderContext);
// renderTask.promise.then(function () {
// console.log("Page rendered");
// });
});
})
.catch((error) => {
console.log(error);
});
};
const pdf = fileReader.readAsArrayBuffer(files[0]);
console.log("read as Data URL", pdf);
};
const Constructor = function (selector) {
const publicAPI = {};
const changeHandler = (e) => {
// console.log(e)
onFilePicked(e);
};
publicAPI.init = function (selector) {
// Check for errors.
const fileInput = document.querySelector(selector);
if (!selector || typeof selector !== "string") {
throw new Error("Please provide a valid selector");
}
fileInput.addEventListener("change", changeHandler);
};
publicAPI.init(selector);
return publicAPI;
};
return Constructor;
})();
imageUploadValidation("form input[type=file]");
The loading task promise never seems to run. Everything seems to work up until that point. I'm not familiar with this Promise syntax, so I can't be sure if the problem is there or how I'm passing in the pdf file.
P.S. The commented out code is the original way I had this setup, what
s uncommented was just me testing a different way.
Check Datatype
First you might want to check what your getting back from your FileReader, specifically what is the datatype for pdfData. If you have a look at the documentation (direct link) getDocument is expecting a Unit8Array or a binary string.
Add Missing Parameters
The next problem you have is your missing required parameters in your call to getDocument. Here is the minimum required arguments:
var args = {
url: 'https://example.com/the-pdf-to-load.pdf',
cMapUrl: "./cmaps/",
cMapPacked: true,
}
I have never used the data argument in place of the url but as long as you supply the correct datatype you should be fine. Notice that cMapUrl should be a relative or absolute path to the cmap folder. PDFJS often needs these files to actually interpret a PDF file. Here are all the files from the demo repository (GitHub pages): cmaps You'll need to add these to your project.
Instead of using data I would recommend uploading your files as blobs and then all you have to do is supply the blob URL as url. I am not familiar with how to do that, I just know its possible in modern browsers.
Where Is Your Viewer / You Don't Need iFrame or Canvas
PDFJS just needs a div to place the PDF inside of. It's picky about some of the CSS rules, for exmaple it MUST be positioned absolute, otherwise PDFJS generates the pages as 0px height.
I don't see PDFViewer or PDFLinkService in your code. It looks like you are trying to build the entire viewer from scratch yourself. This is no small endeavor. When you get loadingTask working correctly the response should be handled something like this:
loadingTask.promise.then(
// Success function.
function( doc ) {
// viewer is holding: new pdfjsViewer.PDFViewer()
// linkService is: new pdfjsViewer.PDFLinkService()
viewer.setDocument( doc );
linkService.setDocument( doc );
},
// Error function.
function( exception ) {
// What type of error occurred?
if ( exception.name == 'PasswordException' ) {
// Password missing, prompt the user and try again.
elem.appendChild( getPdfPasswordBox() );
} else {
// Some other error, stop trying to load this PDF.
console.error( exception );
}
/**
* Additional exceptions can be reversed engineered from here:
* https://github.com/mozilla/pdf.js/blob/master/examples/mobile-viewer/viewer.js
*/
}
);
Notice that PDFViewer does all the hard work for you. PDFLinkService is needed if you want links in the PDF to work. You really should checkout the live demo and the example files.
Its a lot of work but these example files specifically can teach you all you need to know about PDFJS.
Example / Sample Code
Here is some sample code from a project I did with PDFJS. The code is a bit advanced but it should help you reverse engineer how PDFJS is working under the hood a bit better.
pdfObj = An object to store all the info and objects for this PDF file. I load multiple PDFs on a single page so I need this to keep them separate from each other.
updatePageInfo = My custome function that is called by PDFJS's eventBus when the user changes pages in the PDF; this happens as they scroll from page to page.
pdfjsViewer.DownloadManager = I allow users to download the PDFs so I need to use this.
pdfjsViewer.EventBus = Handles events like loading, page changing, and so on for the PDF. I am not 100% certain but I think the PDFViewer requires this.
pdfjsViewer.PDFViewer = What handles actually showing your PDF to users. container is the element on the page to render in, remember it must be positioned absolute.
// Create a new PDF object for this PDF.
var pdfObj = {
'container': elem.querySelector('.pdf-view-wrapper'),
'document': null,
'download': new pdfjsViewer.DownloadManager(),
'eventBus': new pdfjsViewer.EventBus(),
'history': null,
'id': id,
'linkService': null,
'loaded': 0,
'loader': null,
'pageTotal': 0,
'src': elem.dataset.pdf,
'timeoutCount': 0,
'viewer': null
};
// Update the eventBus to dispatch page change events to our own function.
pdfObj.eventBus.on( 'pagechanging', function pagechange(evt) {
updatePageInfo( evt );
} );
// Create and attach the PDFLinkService that handles links and navigation in the viewer.
var linkService = new pdfjsViewer.PDFLinkService( {
'eventBus': pdfObj.eventBus,
'externalLinkEnabled': true,
'externalLinkRel': 'noopener noreferrer nofollow',
'externalLinkTarget': 2 // Blank
} );
pdfObj.linkService = linkService;
// Create the actual PDFViewer that shows the PDF to the user.
var pdfViewer = new pdfjsViewer.PDFViewer(
{
'container': pdfObj.container,
'enableScripting': false, // Block embeded scripts for security
'enableWebGL': true,
'eventBus': pdfObj.eventBus,
'linkService': pdfObj.linkService,
'renderInteractiveForms': true, // Allow form fields to be editable
'textLayerMode': 2
}
);
pdfObj.viewer = pdfViewer;
pdfObj.linkService.setViewer( pdfObj.viewer );

Problems using oauth with Electron

Im attempting to obtain an oauth token using "Implicit grant flow" in my electron app. The issue Im having is when the oauth service (in this case Twitch) redirects my electron app to the redirect uri with the token in it. When redirected the BrowserWindow seems to crash (error can be seen below). I've tried listening to multiple events provided by the BrowserWindow but all of them seem to not trigger before the crash. I've following multiple guides on how to make oauth work within Electron but none of them seem to actually work. If anybody has any success in doing this, Id very much appreciate a solution. Thanks.
Error message after being redirected
UnhandledPromiseRejectionWarning: Error: ERR_CONNECTION_REFUSED (-102) loading (redirect uri with token in it)
Code
const redirect = 'https://localhost/';
const authApp = new AuthApp(cid, redirect);
function handleAuthApp(url: string) {
const urlD = new URL(url);
console.log(urlD);
authApp.reset();
}
//Event that will trigger the AuthWindow to appear
ipcMain.on('get-auth', async (event, arg: any) => {
const window = await authApp.getWindow();
window.show();
window.on('close', () => {
authApp.reset();
console.log('closed');
});
// These events seem to never trigger
window.webContents.on('will-navigate', function(event, newUrl) {
console.log(`Navigate: ${newUrl}`);
handleAuthApp(newUrl);
});
window.webContents.on('will-redirect', function(event, newUrl) {
console.log(`Redirect: ${newUrl}`);
handleAuthApp(newUrl);
});
const filter = {
urls: [redirect+'*']
};
const { session } = window.webContents;
session.webRequest.onBeforeRedirect(filter, details => {
const url = details.url;
console.log(url);
event.returnValue = url;
window.close();
});
});
I was awaiting the URL to load before the 'will-navigate' events could be set. So the BrowserWindow would crash before the events could be fired.
Im dumb.

React Router - show PDF in new tab with window.open

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.

update initial router url when running inside iframe / object tags

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;
}
});

Convert PHAsset/AVAsset to mp4 video in Nativescript app

I use the nativescript-imagepicker-plugin for a filepicker.
This returns a PHAsset.
I have to copy it to a temporary directory to upload it.
Im new in iOS, so I tried a bit:
const options = PHVideoRequestOptions.new();
options.version = PHVideoRequestOptionsVersion.Current;
PHImageManager
.defaultManager()
.requestAVAssetForVideoOptionsResultHandler(
phAsset
, options
, (avAsset, audioMix, info) => {
try {
const tempFilePath = path.join(tempFolderPath, `${Date.now()}.mp4`);
const targetURL = NSURL.fileURLWithPath(tempFilePath);
const exportSession = AVAssetExportSession.alloc(avAsset, AVAssetExportPresetPassthrough);
exportSession.outputUrl = targetURL;
exportSession.outputFileType = AVFileTypeMPEG4;
exportSession.exportAsynchronouslyWithCompletionHandler(() => {
console.log(exportSession.status);
});
}
catch (e) {
console.log(e);
}
}
);
My code crashes without error, so I don't know where to start to debug.
I want a MP4, to show it in web too.
At the end I need a string (path) to a mp4 file to upload id with nativescript-background-http.
Your syntax seems to be wrong
const exportSession = AVAssetExportSession.alloc().initWithAssetPresetName(avAsset, AVAssetExportPresetPassthrough);
exportSession.outputURL = targetURL;

Categories