Ok..I notice that each time I push a new page in the navigation controller, the page is rendered the page state is always the default.
Lets say a user goes to a page, modifies some data, later comes back to the page and his changes are gone.
Is there any way I can restore the page, in case he has already been there?
Here is my idea for now, I was wondering if there was a better approach?
//Check if page was not visited already
console.log('Check if page was not already loaded.')
let restoredFromHistory = false;
this.navCtrl.getViews().forEach( view => {
if(view.name == 'CartPage'){
console.log('Page was found in the history.')
this.navCtrl.push(view);
restoredFromHistory = true;
}
})
if(!restoredFromHistory){
console.log('Page was not found in the history. Pushing a brand new.')
this.navCtrl.push(CartPage)
}
Related
So I recently had acceptance criteria for a site I was building that went as such:
After a user logs in to the site in any tab if they navigate to the site in a new tab they must already be logged in
When a user logs out of any tab they must log out of all tabs immediately
A user can refresh the page and stay logged in
Once all tabs are closed the user is logged out and must log back in
I didn't have access to change the server code (so this had to be done on the client)
I found this Question/Answer which was really helpful
When looking through this I had to rule out cookies because outside of doing a request to the server tab A will no know that tab B had changed the cookie
So I took some parts of the answer from the question above and started using local-storage and added an event to check for if the 'logged-in' state was changed which allowed me to log out in one tab and immediately log out in another without using setInterval to continuously check! Yay
But then I still had the issue of once all tabs were closed if you opened a new tab and navigated to the site you were still logged in.
I tried some possible solutions like having a counter of the tabs that has a session open, decrement and increment on tab close/open (using window.onbeforeunload). ISSUE: refresh of the site when there is only one tab active would log you out. Everything I could think of had an edge case where it didnt work.
local-storage + session-storage!
I would store the value logged-in in both the local-storage and the session storage, when a window was loaded (either a new tab or a refresh of the existing one) it would check local-storage for the 'logged-in' value and if it was not there it would check session-storage!
Basically I am using session-storage to handle the refresh of a page and local-storage to handle multiple tabs. Each time a window/tab is unloaded (closed or refreshed) I delete the local-storage 'logged-in' and when I come back into the page if it is in session-storage but not in local-storage I put it back into local-storage from the session-storage and continue as an authenticated user
Here is the code for this:
On login:
localStorage.setItem('logged-in', true);
sessionStorage.setItem('logged-in', true);
In my base component:
window.onbeforeunload = (event) => {
localStorage.removeItem('logged-in');
}
let loggedIn = localStorage.getItem('logged-in');
let sessionLoggedIn = sessionStorage.getItem('logged-in');
if(!loggedIn) {
if(sessionLoggedIn) {
localStorage.setItem('logged-in', JSON.parse(sessionLoggedIn));
//go to authenticated space
window.location.href = '/authenticated';
} else {
//go to login
window.location.href = '/login';
}
} else {
//go to authenticated space
window.location.href = '/authenticated';
}
window.addEventListener('storage', (event) => {
if (event.key == 'logout' && event.newValue) {
sessionStorage.removeItem('logged-in');
localStorage.removeItem('logout');
window.location.href = '/login';
}
});
On logout
localStorage.setItem('logout', true)
Hope this helps some of you if you ever find yourself in a similar situation
I want to use $location to navigate a self-contained comments section on a normal statically loaded page, but it seems to break the back back button.
The problem comes when I've navigated to a few pages using $location, then click on an external link. It goes to that link, but when I hit back, it changes the URL to the last one but doesn't actually change the page (i.e. it stays on the external page). If I then keep on clicking back, it changes the URL (so the url history is fine), but it doesn't actually load up the page from that url until I get to the first one that I've visited (if that makes sense...). So, for example:
So, navigating the app:
www.example.com - loads up my page with the comments
www.example.com?page=2 - uses $location and loads the new comments correctly
www.example.com?page=3 - uses $location and loads the new comments correctly
www.externalexamplepage.com - navigates correctly to the page.
back - changes the address to www.example.com?page=3 but stays on www.externalexamplepage.com
back - changes the address to www.example.com?page=2 but stays on www.externalexamplepage.com
back - changes the address to www.example.com and loads up the page correctly.
So, how can I get it to not break the back button? This is what I've got in my comments directive:
$scope.$on('$locationChangeStart', function(e, newUrl) {
// If moving off current page...
if ($scope.changingCommentsPage === false) {
$window.location.href = newUrl;
}
});
$scope.$on('$locationChangeSuccess', function() {
// Get current urlParams
var urlParams = $location.search();
// Get new comments if page has changed
if ($scope.page != urlParams.page || typeof urlParams.page === "undefined") {
$scope.changingCommentsPage = false;
$scope.page = urlParams.page ? urlParams.page : 1;
if ($scope.page < 1) return;
// Get comments
getComments($scope.model.page);
}
});
In Ember.js, when you click on a link inside your app, you can conditionally prevent transitioning to the target page by using transition.abort(), for example:
afterModel: function(model, transition) {
if (!model.user.get('facebookAuthenticated')) {
if (confirm("You have to log in with facebook. Continue?")) {
this.controller.send('facebookLogin');
}
else {
transition.abort();
}
}
}
This will cancel the transition and you will stay on the current page.
But what if you're landing in the app into this route, for example by following an external URL ? Then the default behaviour is to go ahead and execute transition.abort(), which leaves you on a blank page. Not acceptable at all.
Is there a way to detect if the user is landing on this route, so that the app can redirect or something?
You could use the beforeModel hook to check if the user is authenticated.
I am assuming you have some way of knowing if your user is authenticated, either with a session or a cookie.
If the user is not authenticated, you can redirect to another page right away.
This can be done by reading the transition.sequence property and using a conditional to redirect/abort transition. The sequence property returns the count of the current transition since landing on the page/loading the ember app.
if (transition.sequence === 0) {
// Land on page
window.location.replace('http://a.url.com');
}
else {
// Click link in app
transition.abort()
}
I am using JavaScript to change the pages of my website, but I am running into the issue where I can't use browser navigation to go back or forwards through the history once I have changed pages.
I have tried adding a /#pagename to the URL for each page, and a few other things, but I can't seem to find something that works. I need to be able to make sure that the browser is saving the history of the page changes I make, so it can navigate those pages with the forward and back buttons.
// Change Page Function
function ChangeContent (page) {
var pages={"homepage":{title: "homepage"},"servicespage":{title: "servicespage"}};
//Show home page
for(var homepage in pages) {
if(page!==homepage) {
document.getElementById(homepage).style.display='none';
}
else {
document.getElementById(homepage).style.display='block';
window.scrollTo(0,0);
}
}
//Show services page
for(var servicespage in pages) {
if(page!==servicespage) {
document.getElementById(servicespage).style.display='none';
}
else {
document.getElementById(servicespage).style.display='block';
window.scrollTo(0,0);
}
}
}
You should look at window.history.pushState to add history entries and window.onpopstate to react to the user travelling "backward" through the entries you've added.
When you want to "change" pages, make a call like:
window.history.pushState( stateData, title, url );
where...
stateData is some object containing data you define
title is a string name for the new state
url is the string to which the browser's url should be changed
For example, if you want to change to "servicespage" and make the url change to "/#services":
window.history.pushState( { title: "Services" }, "servicespage", "/#services" );
Note that you still have to manipulate the DOM at this point.
Then, if you want to react to the user going backward through history:
window.onpopstate = function( event ) {
var data = event.state;
// data references the first argument of 'pushState'
// do whatever showing or hiding of <div>s here
};
Hi have the following jQuery event:
listItems.on('click', function(ev) {
ev.preventDefault();
reload = true;
var url = jQuery(this).attr('data-url');
history.pushState({}, '', url);
...
}
Which load dynamic content via AJAX and pushes history state to modify URL in browser from www.domain.com to www.domain.com/page-x. The only reason im loading content via AJAX is that i need to animate page transitions. If you go to www.domain.com/page-x you get normal HTML page.
The problem occurs if after clicking listItem user clicks back button on it's browser. The url changes back from www.domain.com/page-x to www.domain.com, but the page doesn't reload. Thats why i added this event:
window.onpopstate = function() {
if (reload == true) {
reload = false;
location.reload();
}
}
It reloads page after url has changed, if current page was loaded via AJAX. It's working fine, but now i have other problem:
User in frontpage clicks list item;
Browser URL changes, content is loaded via AJAX and animated;
User clicks BACK browser button;
Browser URL changes to previous and page is reloaded;
User clicks FORWARD browser button, URL changes but nothing happens;
Any ideas/solutions would be highly appreciated.
Rather than using reload=true you should rely on event.state so that when popstateoccurs you get a snapshot of what you recorded for that URL.
Manipulating the browser history (MDN)
For example:
listItems.on('click', function(ev) {
ev.preventDefault();
var url = jQuery(this).attr('data-url');
history.pushState({ action: 'list-item-focused' }, '', url);
...
}
And then:
window.onpopstate = function(e) {
/// this state object will have the action attribute 'list-item-focused'
/// when the user navigates forward to the list item. Meaning you can
/// do what you will at this point.
console.log(e.state.action);
}
You probably should avoid a full page reload when the user hits back, and instead animate your content back that used to be there, that way you aren't trashing the page. You can then tell the difference between the landing page and the list pages by checking for event.state.action and code your responses accordingly.
window.onpopstate = function(e) {
if ( e.state.action == 'list-item-focused') {
/// a list item is focused by the history item
}
else {
/// a list item isn't focused, so therefore landing page
/// obviously this only remains true whilst you don't
/// make further pushed states. If you do, you will have to
/// extend your popstate to take those new actions into account.
}
}
I'm sure you are aware of this, but pushState isn't fully cross-browser, so you should also anticipate what could happen for users if these methods aren't supported.
further enhancements
Also, as you are using jQuery, it makes it quite easy for your to store further useful information in the state object, that may help you enhance your reactions in popstate:
history.pushState({
action: 'list-item-focused',
listindex: jQuery(this).index()
});
You have to bear in mind that any data you store is serialised, meaning that it will most likely be converted to some form of non-interactive string or binary data. This means you can't store references to elements or other "live" instances; hence the fact I'm storing the list items index instead.
With the above you now know what action was occurring and on what list item, which you can retrieve at the other end by using:
listItems.eq(event.state.listindex)
what to do onload?
MDN has this to say about what you should do onload.
Reading the current state
When your page loads, it might have a non-null state object. This can happen, for example, if the page sets a state object (using pushState() or replaceState()) and then the user restarts her browser. When your page reloads, the page will receive an onload event, but no popstate event. However, if you read the history.state property, you'll get back the state object you would have gotten if a popstate had fired.
You can read the state of the current history entry without waiting for a popstate event using the history.state property like this:
Put simply they are just recommending that you should listen out for the current history.state when the page loads, and act accordingly based on what your state.action describes. This way you support both events that are triggered within a page's life-time i.e. popstate and when a user directly jumps to a history state which causes a page load.
So with the above in mind it would probably be best to structure things like so:
window.onpopstate = function(e) {
reactToState(e.state);
}
window.onload = function(){
history.state && reactToState(history.state);
}
var reactToState = function(state){
if ( state.action == 'list-item-focused') {
/// a list item is focused by the history item
}
else {
/// a list item isn't focused, so therefore landing page
/// obviously this only remains true whilst you don't
/// make further pushed states. If you do, you will have to
/// extend your popstate to take those new actions into account.
}
}
I've used inline event listeners for simplicity and because your examples do too, however it would be advised to use the addEventListener and attachEvent (IE only) methods instead... or better still, because you are using jQuery.
jQuery(window)
.on('popstate', function(e) {
reactToState(e.originalEvent.state);
}
.on('load', function(){
history.state && reactToState(history.state);
}
;
switching states
Obviously in order to move between two states, you need to know what both those states are; this means having some way to record your current state — so you can compare with the new state and act accordingly. As you only have two states this may not be imperative, but I'd urge you to always be thinking forward about the possibility of having more complexity than you currently have.
I do not know the layout of your code, so it makes it tricky recommending where you should place variables and other such items. However, no matter how you store the information, it doesn't change the fact that it will make your life and code easier if you do:
var reactToState = function(state){
var currentState = reactToState.currentState || {};
if ( !currentState.action ) { currentState.action = 'start'; }
if ( state.action == 'list-item-focused') {
if ( currentState.action == 'start' ) {
/// here we know we are shifting from the start page to list-item
}
}
else {
if ( currentState.action == 'list-item-focused' ) {
/// here we know we are shifting from the list-item to the start
}
}
/// as the state will be global for your application there is no harm
/// in storing the current state on this function as a "static" attribute.
reactToState.currentState = state;
}
Better yet, if you're not averse to switch statements, you can make the above more readable:
var reactToState = function(state){
/// current state with fallback for initial state
var currentState = reactToState.currentState || {action: 'start'};
/// current to new with fallback for empty action i.e. initial state
var a = (currentState.action||'start');
var b = (state.action||'start');
switch ( a + ' >> ' + b ) {
case 'start >> list-item-focused':
/// animate and update here
break;
case 'list-item-focused >> start':
/// animate and update here
break;
}
/// remember to store the current state again
reactToState.currentState = state;
}