Javascript Single Page Application routing & moving programmatically - javascript

So I have a Single Page JavaScript web app that I've built from a tutorial HERE.
It's working well - I've integrated a few changes that people had in the Issues and Pull Requests on the GitHub link as well.
My links are also working well. If you click on a <a href> link, it redirects without reloading the whole page. AKA It's working as expected!
My problem comes when I want to use Javascript to change the page location. IE: As a result of a function/result of API call/something else, I want to be able to force the page to navigate to a different page without reloading the whole screen.
I'm hoping that I have all the required code below for someone to be able to help... Fingers crossed! Please let me know if I've missed something that you need to help.
My thought process goes along the lines of:
The singlePageApp.js adds an event listener to the <a href='' data-link> tags that calls navigateTo
I just need to be able to somehow call that navigateTo function from somewhere else
I don't fully understand the code on that page enough to be able to make it available to the rest of the app because I can't just call navigateTo('/whatever'); from any script. It results in a function not defined error.
What have I tried?
A lot - and I've forgotten what, but a few things
history.pushState(null, null, url); from a script
navigateTo('/whatever'); from a script
window.location.pathname = '/whatever'; router() <- This just results in a router not defined message
Other notes
I'm not using react or other frameworks. Straight Javascript.
I have jquery installed if it helps - Only reason is as a dependance on dataTables, but I'm not using it anywhere else.
Thank you VERY much in advance
Working links in a NAV bar
<a class="dropdown-item" href="#" data-link>List all services</a>
<a class="dropdown-item" href="/service/vm365/step1" data-link>Provision Service</a>
index.html
<script type="module" src="/static/js/singlePageApp.js"></script>
singlePageApp.js
import ServiceVM365Step1 from "./views/ServiceVM365Step1.js";
import ServiceVM365Step2 from "./views/ServiceVM365Step2.js";
import ServiceVM365Step3 from "./views/ServiceVM365Step3.js";
import ServiceVM365Step4 from "./views/ServiceVM365Step4.js";
import Dashboard from "./views/Dashboard.js";
const pathToRegex = path => new RegExp("^" + path.replace(/\//g, "\\/").replace(/:\w+/g, "(.+)") + "$");
const getParams = match => {
const values = match.result.slice(1);
const keys = Array.from(match.route.path.matchAll(/:(\w+)/g)).map(result => result[1]);
return Object.fromEntries(keys.map((key, i) => {
return [key, values[i]];
}));
};
const navigateTo = url => {
history.pushState(null, null, url);
router();
};
const router = async () => {
const routes = [
{ path: "/", view: Dashboard },
{ path: "/service/vm365/step1", view: ServiceVM365Step1 },
{ path: "/service/vm365/step2", view: ServiceVM365Step2 },
{ path: "/service/vm365/step3", view: ServiceVM365Step3 },
{ path: "/service/vm365/step4", view: ServiceVM365Step4 }
];
// Test each route for potential match
const potentialMatches = routes.map(route => {
return {
route: route,
result: location.pathname.match(pathToRegex(route.path))
};
});
var match
potentialMatches.forEach(entry => {
if (entry.result != null) {
match = (match === undefined || entry.route.path.length > match.route.path.length) ? entry : match
}
})
if (!match) {
match = {
route: routes[0],
result: [location.pathname]
};
}
const view = new match.route.view(getParams(match));
document.querySelector("#content").innerHTML = await view.getHtml();
};
window.addEventListener("popstate", router);
document.addEventListener("DOMContentLoaded", () => {
document.body.addEventListener("click", e => {
if (e.target.matches("[data-link]")) {
e.preventDefault();
navigateTo(e.target.href);
}
});
router();
});
//////////////////////////////////////////////////////////////////
//// CODE BELOW WAS ME TRYING TO GET IT WORKING... It doesn't ////
var oldHref = document.location.href;
window.addEventListener("load", () => {
var bodyList = document.querySelector("body")
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if (oldHref != document.location.href) {
oldHref = document.location.href;
//////////////////////////////////////////
//NOTE: This does get called when a <a href> link is clicked, but not when ONLY history.pushState is used to change the path from a script
console.error("CALLED NOW")
router();
}
});
});
var config = {
childList: true,
subtree: true
};
observer.observe(bodyList, config);
});

Related

Issue with Firebase Cloud Messaging Service Worker and self.addEventListener

I've successfully built an FCM notification service worker for my web app, and it's working OK so far. I used toastr to present notifications within the web app. I'm currently having an issue with the service worker when the web site is not open. Here is my code from firebae-messaging-sw.js:
//Firebase initialized above here
messaging.setBackgroundMessageHandler(function (payload) {
const notiTitle = payload.data.title;
var body = payload.data.body;
const opts = {
icon : "/ui/img/icons/android-chrome-256x256.png",
actions : [
{
action: 'view-ticket',
title: 'View Ticket',
icon: null
}
],
body: body
//url: link
};
self.addEventListener('notificationclick', function (event) {
const clickedNotification = event.notification;
clickedNotification.close();
if(!event.action) {
return;
}
switch(event.action) {
case 'view-ticket':
var promiseChain = clients.openWindow(payload.data.link);
break;
}
event.waitUntil(promiseChain);
});
return self.registration.showNotification(notiTitle, opts);
});
It's almost working perfectly except for one issue. When I send my first test notification, payload.data.link is parsed ok. But on the next notification, payload.data.link is not updated, so the wrong link is sent. I think that maybe self.addEventListener is in the wrong place, but I'm not sure how else to put it (I obviously can't do it after the return).
Any idea where I should put the event listener code?
I fixed it! I was able to repair this by adding a variable and moving addEventListener outside of setBackgroundMessageHandler like so:
//Firebase started up above
var clickDestination; //init this variable
//add event listener before background message handler and use clickDestination
self.addEventListener('notificationclick', function (event) {
const clickedNotification = event.notification;
clickedNotification.close();
if (!event.action) {
return;
}
if(event.action === 'view-ticket') {
var promise = new Promise(function () {
return clients.openWindow(clickDestination);
});
event.waitUntil(promise);
}
});
messaging.setBackgroundMessageHandler(function (payload) {
const notiTitle = payload.data.title;
var body = payload.data.body;
clickDestination = payload.data.link; //set clickDestination based on payload
/*self.addEventListener('notificationclick', function (event) {
event.notification.close();
event.waitUntil(self.clients.openWindow(payload.data.link));
});*/
const opts = {
icon : "/ui/img/icons/android-chrome-256x256.png",
actions : [
{
action: 'view-ticket',
title: 'View Ticket',
icon: '/ui/img/icons/ticket-icon.png'
}
],
body: body
};
return self.registration.showNotification(notiTitle, opts);

Cannot refresh information with navigate angular2

I have a little bit of a problem with the angular navigator 2
I'm in my detail projects page and I have a component that is in my detail page that will show me the next project.
So in my component I recall the same page which is' /projects/id
when I click in the navigation bar it changes the id well but the content does not change and I do not understand why because I make a router. navigate of my page.
my init controller projectDetail
ngOnInit() {
this.nextProjectId = this.activedRoute.snapshot.queryParams.nextProject;
const id = this.activedRoute.snapshot.params.id
this.projectsService.projectsAll().then( (projects) => {
if (projects[this.nextProjectId]) {
this.nextProject = projects[this.nextProjectId];
} else {
this.nextProject = projects[0];
}
});
this.projectsService.projectFind(id).then( project => this.project = project);
}
my function navigate in component nextProject
nextProjectDetail(id, nextProject) {
const next = parseInt(nextProject, 0) + 1
this.router.navigate(['projects/', id], { queryParams: {nextProject: next }}).then(
function(){
console.log('navigate success');
},
function(){
console.log('navigate failure');
}
);
}
I even tried to use this function in the init of detail projects to see when the url changes but it doesn't work.
this.params.subscribe(params => {
console.log("dfsd");
});
there is another way to refresh the same page in a component on the same page?
thanks

Error in running unit test for Vue webapp

I am writing a webapp with VueJs, I am trying to setup unit test for it, I got inspired from vue-mdl unit-tests. But the tests are not running properly for my code and I am getting vm.$el as undefined, so not able to move forward at all.
Here is the component, I am trying to test:
Confirmation.vue
<template>
<div>
Your order has been confirmed with the following details.
</div>
</template>
<script type="text/javascript">
export default {
data () {
return {
data_from_pg: null
}
}
}
</script>
and here is test for it, which fails
Confirmation.spec.js
import Confirmation from 'src/components/Confirmation'
import { vueTest } from '../../utils'
describe('Confirmation', () => {
let vm
let confirmation
before(() => {
vm = vueTest(Confirmation)
console.log('vm.$el ' + vm.$el) => this prints undefined
confirmation = vm.$el.querySelector('#confirmation') => so this line gives error
// confirmation = vm.$('#confirmation')
})
it('exists', () => {
confirmation.should.exist
confirmation.should.be.visible
})
})
utils.js
export function vueTest (Component) {
const Class = Vue.extend(Component)
Class.prototype.$ = function (selector) {
return this.$el.querySelector(selector)
}
Class.prototype.nextTick = function () {
return new Promise((resolve) => {
this.$nextTick(resolve)
})
}
const vm = new Class({
replace: false,
el: 'body'
})
return vm
}
My complete code is available here, with all the test config, which I have tried to change many times, but could not figure out how to make it work. Please let me know if you see some error somewhere.
The vueTest function in utils is trying to load the Vue instance into the body tag:
const vm = new Class({
replace: false,
el: 'body'
})
return vm
The unit tests do not load index.html as an entry point into the app, but rather the individual components that you want to test; Therefore, you do not have access to document or html elements and the component is never mounted. I'd suggest using vm.$mount():
If elementOrSelector argument is not provided, the template will be rendered as an off-document element.
You could change the above lines to something like the following
const vm = new Class();
vm.$mount();
return vm;
Your tests should now have access to the $el property.

Why is the driver object defined, but the webdriver object is not?

Question
Why does driver work fine(the title is retrieved and tested), but web driver is undefined(unable to getText)?
Expected Result
The tests will complete successfully.
Actual Result
․ Google when at home Page should have correct title: 141ms
1) Google when at home Page when searching should input search term
1 passing (3s)
1 failing
1) Google when at home Page when searching should input search term:
ReferenceError: webdriver is not defined
Files Used
Test File
Used to run the tests by executing command: mocha -t -R list index.js (assuming index.js is the filename)
var fs = require('fs'),
chai = require('chai'),
assert = chai.assert,
expect = chai.expect,
test = require('selenium-webdriver/testing'),
webdriver = require('selenium-webdriver'),
Page = require('./pageobjects/pages/home');
test.describe('Google', function(){
test.before(function(){
driver = new webdriver.Builder().
withCapabilities(webdriver.Capabilities.firefox()).
build();
//initialize driver and webdriver on the Page Object
Page.webdriver = webdriver;
Page.driver = driver;
});
test.describe("", function () {
test.before(function(){
//console.log(Page);
});
test.describe("when at home Page", function () {
test.before(function () {
Page.get(Page.URL);
});
test.it("should have correct title", function () {
Page.getTitle()
.then(function (title) {
assert.equal(title, 'Google');
});
});
test.describe("when searching", function () {
test.it("input search term", function () {
Page.sendKeys(Page.Search.INPUT, 'test');
Page.getText(Page.Search.INPUT)
.then(function (text) {
assert.equal(text, 'test');
});
});
});
test.after(function () {
driver.quit();
});
});
});
});
Page
object used to create pages
var Page = {
getTitle : function getTitle() {
return driver.getTitle();
},
get : function get(url) {
return driver.get(url);
},
sendKeys : function sendKeys(element, text) {
console.log(webdriver);
driver.findElement(webdriver.By.css(element)).sendKeys(text);
},
click : function click(element) {
return driver.findElement(webdriver.By.css(element)).click();
}
};
module.exports = Page;
Home
object that represents a page, uses mixins to get Page's functions
the search file is left out because it is irrelevant to the problem
var Page = require('./page'),
Search = require('../components/search'),
extend = require('extend');
var Home = {
URL : 'http://google.com',
Search : Search
};
module.exports = Home;
//extend home with page
extend(module.exports, Page);

Backbone router issue

I have a router that does the site navigation nicely and also works when clicking the browsers back / forward button. However, when entering directly an URL I get 404.
Here is my router:
define(function(require) {
var $ = require('jquery'),
_ = require('underscore'),
Backbone = require('backbone');
var AppRouter = Backbone.Router.extend( {
routes: {
'home' : 'homeHandler',
'webdesign' : 'webHandler',
'mobile' : 'mobileHandler',
'javascript' : 'javascriptHandler',
'hosting' : 'hostingHandler',
'contact' : 'contactHandler'
},
initialize: function() {
this._bindRoutes();
$('.link').click(function(e){
e.preventDefault();
Backbone.history.navigate($(this).attr("href"),true);
});
if(history && history.pushState) {
Backbone.history.start({pushState : true});
console.log("has pushstate");
}
else {
Backbone.history.start();
console.log("no pushstate");
}
console.log("Router init with routes:",this.routes);
},
homeHandler: function(e) {
require(['../views/home-content-view', '../views/home-sidebar-view'],
function(HomeContent, HomeSidebar) {
var homeContent = new HomeContent();
homeContent.render();
var homeSidebar = new HomeSidebar();
homeSidebar.render();
});
},
webHandler: function(e) {
require(['../views/web-content-view', '../views/web-sidebar-view'],
function(WebContent, WebSidebar) {
var webContent = new WebContent();
webContent.render();
var webSidebar = new WebSidebar();
webSidebar.render();
});
},
...
});
return AppRouter;
});
Obviously, I'm missing something.
Any clarification would be greatly appreciated.
Thanks,
Stephan
Backbone operates on a web-page (that has already been loaded in the browser). When you enter a URL in the browser directly, you're making a HTTP-request for that URL to the server. The server is not managed by Backbone. You have to define on the server the behavior when such HTTP-requests are encountered.

Categories

Resources