Switching from hash-based nav to HTML5 History API - javascript

I'm trying to figure out how to use the History API for my webpage. Currently, it's a simple page where the navbar toggles content visibility on click by changing an ID's display to none or block.
I'd like to switch over to the History API because of the browser back button memory, but all of the examples I'm finding use AJAX to load data from a separate file on click. My pages aren't big enough to warrant asynchronous loading.
Is there any way for regular URLs to trigger CSS property changes the way that hashes do?

No, CSS can only react to :target via the hash component of the URL. However you can still take advantage of the History API if you want to; you'll just need a little bit of additional javascript. For example you could a data-target attribute to the <html> element which changes depending on the URL, and your css could then react to this through something like:
[data-target="home"] #home-page { display: block; }
The History API allows you to store some state with each entry, so you could use this to store the target identifier and then retrieve this again in your "popstate" handler - e.g.
history.pushState({ target: 'home' }, '', '/home')
document.documentElement.dataset.target = 'home'
window.addEventListener('popstate', function (e) {
document.documentElement.dataset.target = e.state.target
})

Related

Is it possible to mix two GraphQL queries into one query?

I am using Gatsby as a frontend to a WordPress backend. I have successfully fetched the menu items but I now need to link them to the pages I have created. I have added the Menu Links in my Gatsbyconfig.js but I have no idea on how I can go about mapping it to the menu items at the same time as the menu itself. It ends up contradicting each other. Is this possible to do? I am quite new at graphQL. Never touched it till this project. Below is the GraphQl Query
{
allWpMenuItem(filter: {menu: {node: {name: {eq: "Navigation Menu"}}}}) {
edges {
node {
label
}
}
}
site {
siteMetadata {
title
menuLinks {
name
link
}
}
}
}
The label holds the name for each menu and the link holds the link to the pages I have created. I am trying to fix this into this bit of code
<div>
{props.allWpMenuItem.edges.map(edge =>(
<a href="#" key={edge.node.label}>
{edge.node.label}
</a>
))}
</div>
I am trying to query the menu links change the anchor tag to the link item and then point it to the menu link.
The short answer is that you can't, natively each query is a separate entity. However, you can combine queries using createResolvers, for further details check: https://github.com/gatsbyjs/gatsby/discussions/28275 (thanks LekoArts for the feedback).
However, you have a few approaches to bypass this limitation. Given that each query is transformed into a standalone JavaScript object, you can always manipulate them using native JavaScript filtering or doing whatever you need. The idea is to create an empty object and populate it with links and labels, in a single loop or using two different, then, in your component, iterate through this object instead of through the props like you are doing now.
I can't create a minimal example without knowing the internal structure or without knowing what's inside menuLinks.name.
By the way, if you are using internal navigation I would hardly suggest using <Link> component, among other things, using the #reach/router (extended from React's router) will only render the necessary parts of each component, creating smoother navigation than using native anchor tag, what will refresh all the page.

Problems when combining css :target selector with url hash parameters for showing a modal

My javascript and css knowledge is rather limited. I think I am missing some subtleties of when and how css is applied as well as how page loading, combined with asynchronous javascript, calls works.
This is working:
I have a css only modal which displays when the id of my modal (view) matches the hash in the url (#view). I use it to display images (it is the same modal div for all images and the actual image src is updated via event listeners added to links to the modals' anchor). This is working fine.
This is working:
I wanted to extend this so I could directly link to an already open popup by providing the url as a hash parameter. I.e. example.com/#view=imageUrl. Just opening this will not make the css selector :target match, since it now contains the view, but if I split this off by checking var hash = window.location.hash.substr(1); I can separate the url from the anchor and if I then use:
window.history.pushState("", "", window.location.href.split('=')[0]);
updateModal(imageUrl)
where updateModal sets the source of the image to the split off url. All is fine, too, i.e. the site opens with the modal open and the image from the url in there.
This is not working:
Now I thought, it is possible that the image provided as hash parameter does not exist anymore, so I only want to show the modal if there is a valid image provided, otherwise go to the main page. I created a dummy image element, try loading the image from imageUrl and move the splitting and updating bits to the onload method of the dummy image, like so:
dummyImage.onload = function () {
window.history.pushState("", "", window.location.href.split('=')[0])
updateModal(imageUrl)
}
If I do this, the modal does not show. The css does not get applied, why is this?
I also tried changing the visibility of the modal myself:
dummyImage.onload = function () {
window.history.pushState("", "", window.location.href.split('=')[0])
updateModal(imageUrl)
var myModal = document.getElementById('view')
myModal.style.display = 'flex'
myModal.style.visibility = 'visible'
}
Which makes the modal appear, but seems to add a style that cannot be overridden by the css anymore. Specifically, when I click the close link of the modal which just goes to a different anchor #noname the url changes, the css :target selector should not match anymore, but the modal does not close anymore. Why would the css not apply anymore?
Reading the below links, I believe this is a bug and that, on changing the url with pushState, the :target selector is not re-evaluated after page load. I will work around this by always showing the modal, as in the second working example in the question and use a fall-back image and message to alert the users that the image is no longer available (I can trigger this update with dummyImage.onerror).
https://bugs.webkit.org/show_bug.cgi?id=83490
https://css-tricks.com/on-target/
https://github.com/ReactTraining/history/issues/503
https://github.com/whatwg/html/issues/639
If anybody knows a better solution or workaround, I am still interested to learn!

Durandal and Knockout: do a css binding to body element

I am trying to make the body element of a Durandal app (a SPA framework which employs Knockout) by checking a module's state (which account type users are logged in as). In the viewmodel of the app's shell, I have the following code:
function bindingComplete() {
viewhelper.accountVisualTreatment();
}
And accountVisualTreatment is defined in the viewhelper module as such:
if (typeof(appsecurity.userInfo()) == 'undefined') {
$("body").addClass("notloggedin");
$(".container").addClass("notloggedin");
} else {
if (appsecurity.isUserInRole(['Account Manager']) && !appsecurity.isUserInRole(['Administrator'])) {
$("body").addClass("accountmanager");
$("nav").addClass("hidden");
} else {
$("body").removeClass();
$(".container").removeClass("notloggedin");
$("nav").removeClass("hidden");
}
}
Everything works fine if I refresh the pages when logged in as the diff account types. But as a SPA framework, there's no page refreshes while being used. Hence, the classes are not being applied as I want it to. How do I make it so the body, container, and nav elements' classes are being bound as I want?
Edit: I have tried to call viewhelper.accountVisualTreatment() in viewmodels of the landing page of the different account types but to not avail. Still needs page refresh.
Edit: I ugly-ly fixed it by applying a css binding to the container div in my shell.html
<div class="container" data-bind="css: viewhelper.accountVisualTreatment()"> ... </div>
Because you want to do this in the body (so globally). So my assumption is you want to update the css classes as soon the accountinfo changes. You could create a singleton observable with the accountinfo. When it changes you just subscribe to the observable.
The only problem is with elements which aren't in the dom. (like between loading pages). Because you use Durandal you have different hooks to change the dom with jQuery.
To do this in the viewmodels you should use attached() or compositionComplete() see: http://durandaljs.com/documentation/Hooking-Lifecycle-Callbacks.html. This is the callback you get from durandal when the dom is completly attached to the viewmodel and all the observables.
I would say you don't want to do this in every viewmodel. So I would suggest to hook on the router:navigation:composition-complete event on the router, see: http://durandaljs.com/documentation/api.html#class/Router/event/router:navigation:composition-complete. This will make sure you will get the callback for every new hash navigation.
You can register to the global event in the data-main where the app is started:
app.on('router:navigation:composition-complete').then(viewhelper.accountVisualTreatment());
I hope this helped.

Change Page for jQuery Mobile between two html files

I'm trying to make a simple site with two pages, "Search" and "Results".
At first, I had a multi-page template working fairly well. I would change the page, and on page change I would use ajax to get the results. The problem was that I wanted to be able to load the results page without first going back to the search page.
I want to pass parameters to the results page via the querystring so that I can have something like this:
search.html + "some search terms" -> results.html?q=some+search+terms
The problem is that I can't seem to get anything to work right when I split up the html into two files.
I try calling
$.mobile.changePage("results.html?q=" + escape(search))
on the search page, but the $(document).ready function is not firing. I kind of get why it doesn't, since changePage is loading the second page into the DOM?
I also tried manually redirecting, in which case the $(document).ready function does fire on results.html, but using the back button or going back to the search page doesn't fire THAT $(document).ready.
I tried wiring up the pagechange function to search.html, assuming that this would fire when I load the second page, but nothing happened.
Does anyone have suggestions as to how I would pull this off? Or the best way to get the results page to act more independent of the search page?
I've been bitten by this too, it really isn't a good idea to pass parameters through the query string and it makes jQueryMobile behave in an odd way.
Instead I've been using sessionStorage which works perfectly. You could also use a cookie.
I'm not 100% sure where you're actually having issues, but here is some important jQuery Mobile specific info that can help you.
First, read the big yellow section at the top of this page: http://jquerymobile.com/demos/1.1.0/docs/api/events.html
document.ready does not fire when a page is brought into the DOM from an external document. Instead you need to use event delegation and the page-events specified in the link above. Most likely you want to use pageinit as a replacement for document.ready.
Then read the top section of this page: http://jquerymobile.com/demos/1.1.0/docs/api/methods.html (the part about $.mobile.changePage()).
The important part about the second link is that you can pass data via the $.mobile.changePage() function like so:
$.mobile.changePage('results.html', { data : { q : search } });
You can even set the type option to post so there will not be a query-string sent (this should ensure you don't get multiple of the same page in the DOM at a time).
$.mobile.changePage('results.html', { data : { q : search }, type : 'post' });
Another fix would be to manually add the data-url attribute to the <div data-role="page" id="results"> page. When you grab a page like this:
$.mobile.changePage("results.html?q=search+term+here");
It's data-url gets set to: results.html?q=search+term+here. If you manually set the data-url to results.html then you can navigate to the page like this:
$.mobile.changePage("results.html", { data : { q : 'search+term+here' } });
Which will look first for the data-role="page" element that has the data-url attribute set to results.html before re-loading the pseudo-page via AJAX.
Thanks for the input guys. I used a plugin that allows me to use faux-query parameters in the hash for a multi-page layout.
https://github.com/jblas/jquery-mobile-plugins/tree/master/page-params
I just added this in and ran the search on page change, getting those page parameters for the search.

Locations area map with jQuery load function importing external HTML

I have a US HTML image map with the states highlighted with coordinates. Each state when clicked will bring up a modal window with facilities locations.
Right now I have it where each state has an ID, the user clicks on the hotspot and then it loads the facilities locations with external HTML via jQuery .load() to lighten the document load.
Here's an example in the JS
$('#Alabama').load('locations/Alabama.html');
and then the container that will load Alabama html in the main document is
<div class="overlay" id="Alabama"></div>
As you can see I have to enter these in for each state. Obviously this is tedious and probably not the best method. DRY comes to mind but I'm not good at writing Javascript from the ground up.
What would be the best way to access the state IDs without writing each state instance? I'm thinking a JSON object with a click function that constructs the markup and calls the modal window. I'm just not sure where to begin.
If it's a modal window, then you could probably just use the same overlay each time a state is clicked. So basically just do this...
$(".overlay").load("locations/alabama.html");
A full example might look something more like this though...
$("area").click(function(){
var url = $(this).attr("href");
$(".overlay").load(url);
});
You may have to do something to prevent the browser default behavior of navigating on the clicked area. You could remove the href values and put them somewhere else...
$("area").each(function(i, area){
$(area).data("old-href", $(area).attr("href");
$(area).attr("href","#");
});
//Then you do the same as above, but you get your url from the data method.
$("area").click(function(){
var url = $(this).data("old-href");
$(".overlay").load(url);
});

Categories

Resources