I have a list of employee list on EmployeesComponent and there is "Education Overview" and "Salary Overview" buttons for each records. When I click one of the overview button it goes to the OverviewComponent first and then load the correponding component (salary or education) into this OverviewComponent. There is also a "Back" button on each of these salary and education components. The structure is as shown on the following image:
components
The problem is that: When I come back to the EmployeesComponent, I need to reload the paging params e.g. the last page number before navigating to the overview pages. For this I use localStorage and check the saved value on each page load of the EmployeesComponent.
searchParams: any;
ngOnInit() {
let searchParams = JSON.parse(localStorage.getItem('routeParams'))?.searchParameters;
if(searchParams){
this.searchParams = searchParams;
window.localStorage.removeItem('routeParams'); // remove routeParams from localStorage
// load list using this.searchParams
}
But I save the page params on the OverviewComponent so that use a single place for salary and education pages. I think it is not a good approach and it may cause the localStorage items to be mixed as they use the same key (for some reason I need to use the same key sometimes).
So, should I set the paging parameters just before navigating to the overview page in the EmployeesComponent? And then check them on loading EmployeesComponent? What is a proper way for this scenario?
You can use the query-params in routing.
So now when you redirect from employess component to overViewComponent, then based on click i.e., Education Overview or Salary Overview just send the query params with the url.
Then now when you get back to employess component, just use the query params value you get in overView component and you can get the information you want back in employess component.
Q- what is the most proper place for adding and removing paging items to local storage
A- Most proper place for adding and removing localstorage item is where it get's change.
In your case, just set localstorage in overView component where you are getting params ( this.activateRoute.params() ) inside this function. And remove the localstorage on ngOnInit function of employee component.
I’m working on a MVC 5 (asp) application. One of the requirements I get is to have multiple navigation paths to point at the same destination page. The problem is going back to a previous page after a post according to the navigation history of the user.
Let’s consider a basic scenario of three webpages
Customers/ShowAll -> Show a list of all customers
Customers /Search -> Show a list of customers according to a search (name, country …)
Customers /Update/1134 -> Show the update page for a specific customer (i.e. customer_id=1134)
So the navigation path goes like this
ShowAll -> Update
or
Search -> Update
If the user navigates to “Customers /Search” then “Customers /Update/1134”, updates the customer information and saves the data, I want the server to redirect to the page “Customers /Search” since it’s the path the user uses.
This is a very basic case but it can be more complex like going back many pages and always return a previously visited page (or a default one if no pages match the history).
What I’ve done so far
I have created a prototype that keeps track of the navigation history of a user on the server side. It uses session storage on the client side to give a unique id for the current browser tab. On each page unload it adds the browser tab id to the cookies. Then on the server side there’s a dictionary (in the session) with the tab id (extracted from the cookies) as the key and a list of visited URLs as the value. The current URL is added to the list of URLs. I found this solution to be working but it has some flaws.
If JavaScript is disabled this solution won’t work (this is not a very big deal since I can require all the users to turn it on (it’s an intranet for a small company))
If a tab is duplicated the resulting two tabs will have the same id. This is due to the implementation of session storage (at least on Chrome). So the history on the server can get corrupted if the user uses both tabs.
I store the dictionary of history in a session variable so if the session timed out, the history is lost. I thought about keeping the history in the database but I feel it’s a little of overheat for the database.
A last thing is that the dictionary of URLs is limited to the last 30 pages visited since I want to limit the server memory. It’s not important to my question but I feel to mention it since I’m sure some of you may see the problem of keep all pages from all users for all tabs in history.
I also thought of a similar solution using cookies to transmit the last 30 pages visited on each request and have the server parse this history when it needs it. Only the pages from the application domain will be kept. This will resolve the problem of persistence after a session times out but it introduce a little more processing to the server since the history will be parsed in about each request.
I want to know if there a better solution to redirect the user according to navigation history. Maybe there’s a build-in functionality in MVC 5 that I don’t know.
Thanks for any advice.
Regards.
Using session state (as you have discovered) is not a very good solution to this problem because:
It times out, in which case the data is lost.
If the user doesn't navigate to the page the way you expect (for example, coming directly to a page via Google SERP), then it doesn't work.
The only way to make it work 100% of the time is to put all of the navigation identifier information into the URL so the system can determine how to build the navigation links.
There is no built-in functionality for this in MVC 5, but you could use MvcSiteMapProvider to solve your issue. It contains HTML helpers for Menu and SiteMapPath, which acts like a breadcrumb trail.
#Html.MvcSiteMap().Menu()
#Html.MvcSiteMap().SiteMapPath()
It works on a different principle - it loads a single shared hierarchy of nodes (a site map) into memory. Then each request that comes in matches one of the nodes and uses the map to determine how to build the links in the HTML helpers. There is no session state used at all.
The trick to getting it to work in your scenario is to make the Customers/Update/1134 page available on 2 different URLs. Then you can configure 2 different node hierarchies and it will know which one to match based on the routing information.
For example, you could add an additional route value that indicates that the page you are navigating from is the search page.
#Html.ActionLink("Customer 1134", "Update", "Customers", new { source = "Search" }, null)
By default, this will build a URL like Customers/Update/1134?source=Search. You can make it look prettier by adjusting your route configuration.
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "SearchSource",
url: "Search/{controller}/{action}/{id}",
defaults: new { source = "Search", controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "ShowAllSource",
url: "ShowAll/{controller}/{action}/{id}",
defaults: new { source = "ShowAll", controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
Now with the same ActionLink shown above, you will get the URL /Search/Customers/Update/1134. That's better. Note that when you put the ActionLink on your ShowAll page, it should be like this instead:
#Html.ActionLink("Customer 1134", "Update", "Customers", new { source = "ShowAll" }, null)
Then when you set up the node configuration in MvcSiteMapProvider, you need to make 2 different parent nodes, like this.
<mvcSiteMapNode title="Home" controller="Home" action="Index">
<!-- Additional nodes here -->
<mvcSiteMapNode title="Search" controller="Customers" action="Search">
<mvcSiteMapNode title="Update Customer" controller="Customers" action="Update" source="Search" preservedRouteParameters="id"/>
</mvcSiteMapNode>
<mvcSiteMapNode title="Show All Customers" controller="Customers" action="ShowAll">
<mvcSiteMapNode title="Update Customer" controller="Customers" action="Update" source="ShowAll" preservedRouteParameters="id"/>
</mvcSiteMapNode>
<!-- Additional nodes here -->
</mvcSiteMapNode>
You will then get a complete navigation solution:
/Customers/ShowAll | Home > Show All Customers
/ShowAll/Customers/Update/1134 | Home > Show All Customers > Update Customer
/Customers/Search | Home > Search
/Search/Customers/Update/1134 | Home > Search > Update Customer
Of course, this is just an example. You can make the URLs and navigation links look any way you want.
Redirecting Back
Finally, there is redirecting back to the location the user came from. That's easy because MvcSiteMapProvider keeps track of the parent node.
[HttpPost]
public ActionResult Update(CustomerModel model)
{
// Update customer here...
var currentNode = this.GetCurrentSiteMapNode();
if (currentNode != null)
{
var parentNode = currentNode.ParentNode;
if (parentNode != null)
{
return Redirect(parentNode.Url);
}
}
return View(model);
}
You may wish to store some additional information (sort order, search term, etc) from the original parent page, in which case you will need to pass those parameters through the Update page and back to the parent page somehow.
One way is to use session and then use some logical default behavior if they are missing when you get to the redirect page.
Another (easier) approach is to add them as parameters (query string or route values) to the URL of the Customer Update page so they will automatically be built into the return URL. Since you have 2 different routes, you would just need to add the information to the appropriate route. You just need to ensure both the routes of the Search page and the Customer Update page include them so the parent URL is built appropriately.
Full Disclosure: I am a major contributor of the MvcSiteMapProvider project.
See also:
https://github.com/maartenba/mvcsitemapprovider/wiki/Multiple-Navigation-Paths-to-a-Single-Page
http://www.shiningtreasures.com/post/2013/08/07/MvcSiteMapProvider-40-a-test-drive
http://www.shiningtreasures.com/post/2013/09/02/how-to-make-mvcsitemapprovider-remember-a-user-position
I'm a little confused about how History.js works at page-load. I've done a few experiments but the results seem indeterministic.
My website is a search engine and the query is stored in the URL parameters: ?Query=cats. The site is written purely in javascript. History.js works great when I do a new search, the new query is updated, and the state is pushed.
My problem is how to create an initial state if the user manually enters in a URL including a Query parameter. Every way I try to do this ends up resulting in running the search query twice in some case. The two use-cases that seem to conflict are:
User manually enters URL (mydomain.com?Query=cats) into address bar and hits enter.
User navigates to an external page, and then clicks the back button
In both cases, the javascript loads, and therefore looks to the URL parameters to generate an initial state.
However, in the second case, History.js will trigger the statechange event as well.
Necessary code:
History.Adapter.bind(window,'statechange',function() { // Note: We are using statechange instead of popstate
var s = History.getState();
if(s.data["Query"]){
executeQuery(s.data);
}
});
and in $(document).ready I have
// Get history from URL
s = getQueryObjectFromUrl(location.href);
if(s["Query"]){
History.pushState(s,'',$.param(s))
}
Is there a better way to handle creating an initial state from URL parameters?
As I had a similar problem to to yours, what i did was to define the function bound to a statechange as a named function, and then all I had it running when the page load as well.
It worked better than trying to parse the URI or anything else, hope it helps.
This is the way I chose to do it (based on Fabiano's response) to store the initial state parameters
var renderHistory = function () {
var State = History.getState(), data = State.data;
if (data.rendered) {
//Your render page methods using data.renderData
} else {
History.replaceState({ rendered: true, renderData: yourInitData}, "Title You Want", null);
}
};
History.Adapter.bind(window, 'statechange', renderHistory);
History.Adapter.onDomLoad(renderHistory);
Of course if you are using a different on DOM load like jquery's you can just place renderHistory(); inside of it, but this way doesn't require any additional libraries. It causes a state change only once and it replaces the empty initial state with one containing data. In this way if you use ajax to get the initData inside the else, and it will not need to get it the next time the person returns to the page, and you can always set rendered to false to go back to initial page state / refresh content.
We're having an issue with our backbone application. We want to provide a user with a notification when a fetch fails (timeout or general error), but we want to display a dialog over the previous page's content rather than showing an error message in the new page (how Facebook/LinkedIn etc. do it)
To trigger a request for the new content, we have to navigate to the new URL first. We can't really change this without a rework, so we want to avoid this if possible. What we need to do is send the user back to the previous URL when there is a connection error, which would cause the route to fire, re-requesting the previous content. We really want to avoid doing this however.
We're aware that we can send a user back using a navigate without triggering a route, but this will mess up the browser history, making backwards become forwards in this case. We could also force a browser back, keeping the history trail correctly, but this would force a re-fetch of the content.
We've also investigated setting a flag of some kind telling our router not to re-request data on the next route change, but this would cause issues when browser back is used to go to a previous screen on which the fetch fails. In this instance we'd need to send the user 'forwards' in their journey instead. As far as we know, this isn't possible using the browser's history manager.
Is there any way of having a dialog how we want, or will we have to go the same way as Facebook/LinkedIn and co.?
Do you have an example of your code / what you have tried?
Going off what you have said, if there is an error fetching the model data after your URL has changed you can silently redirect the user back to the previous URL using the router, e.g:
window.product_v = Backbone.View.extend({
render: function() {
this.model.fetch({
processData: true,
data: this.model.attributes,
success : function(d){
},
error : function(d) {
MyRouter.previous();
}
})
}
});
Then in your router could keep an array of your history so that the route isn't 'triggered' on redirect. or by simply doing:
Backbone.history.navigate(route, {trigger: false, replace: true});
The below question/answer describes this perfectly:
Silently change url to previous using Backbone.js
class MyRouter extends Backbone.Router
constructor: (options) ->
#on "all", #storeRoute
#history = []
super options
storeRoute: ->
#history.push Backbone.history.fragment
previous: ->
if #history.length > 1
#navigate #history[#history.length-2], true
How would I have a JavaScript action that may have some effects on the current page but would also change the URL in the browser so if the user hits reload or bookmark, then the new URL is used?
It would also be nice if the back button would reload the original URL.
I am trying to record JavaScript state in the URL.
If you want it to work in browsers that don't support history.pushState and history.popState yet, the "old" way is to set the fragment identifier, which won't cause a page reload.
The basic idea is to set the window.location.hash property to a value that contains whatever state information you need, then either use the window.onhashchange event, or for older browsers that don't support onhashchange (IE < 8, Firefox < 3.6), periodically check to see if the hash has changed (using setInterval for example) and update the page. You will also need to check the hash value on page load to set up the initial content.
If you're using jQuery there's a hashchange plugin that will use whichever method the browser supports. I'm sure there are plugins for other libraries as well.
One thing to be careful of is colliding with ids on the page, because the browser will scroll to any element with a matching id.
With HTML 5, use the history.pushState function. As an example:
<script type="text/javascript">
var stateObj = { foo: "bar" };
function change_my_url()
{
history.pushState(stateObj, "page 2", "bar.html");
}
var link = document.getElementById('click');
link.addEventListener('click', change_my_url, false);
</script>
and a href:
<a href="#" id='click'>Click to change url to bar.html</a>
If you want to change the URL without adding an entry to the back button list, use history.replaceState instead.
window.location.href contains the current URL. You can read from it, you can append to it, and you can replace it, which may cause a page reload.
If, as it sounds like, you want to record javascript state in the URL so it can be bookmarked, without reloading the page, append it to the current URL after a # and have a piece of javascript triggered by the onload event parse the current URL to see if it contains saved state.
If you use a ? instead of a #, you will force a reload of the page, but since you will parse the saved state on load this may not actually be a problem; and this will make the forward and back buttons work correctly as well.
I would strongly suspect this is not possible, because it would be an incredible security problem if it were. For example, I could make a page which looked like a bank login page, and make the URL in the address bar look just like the real bank!
Perhaps if you explain why you want to do this, folks might be able to suggest alternative approaches...
[Edit in 2011: Since I wrote this answer in 2008, more info has come to light regarding an HTML5 technique that allows the URL to be modified as long as it is from the same origin]
jQuery has a great plugin for changing browsers' URL, called jQuery-pusher.
JavaScript pushState and jQuery could be used together, like:
history.pushState(null, null, $(this).attr('href'));
Example:
$('a').click(function (event) {
// Prevent default click action
event.preventDefault();
// Detect if pushState is available
if(history.pushState) {
history.pushState(null, null, $(this).attr('href'));
}
return false;
});
Using only JavaScript history.pushState(), which changes the referrer, that gets used in the HTTP header for XMLHttpRequest objects created after you change the state.
Example:
window.history.pushState("object", "Your New Title", "/new-url");
The pushState() method:
pushState() takes three parameters: a state object, a title (which is currently ignored), and (optionally) a URL. Let's examine each of these three parameters in more detail:
state object — The state object is a JavaScript object which is associated with the new history entry created by pushState(). Whenever the user navigates to the new state, a popstate event is fired, and the state property of the event contains a copy of the history entry's state object.
The state object can be anything that can be serialized. Because Firefox saves state objects to the user's disk so they can be restored after the user restarts her browser, we impose a size limit of 640k characters on the serialized representation of a state object. If you pass a state object whose serialized representation is larger than this to pushState(), the method will throw an exception. If you need more space than this, you're encouraged to use sessionStorage and/or localStorage.
title — Firefox currently ignores this parameter, although it may use it in the future. Passing the empty string here should be safe against future changes to the method. Alternatively, you could pass a short title for the state to which you're moving.
URL — The new history entry's URL is given by this parameter. Note that the browser won't attempt to load this URL after a call to pushState(), but it might attempt to load the URL later, for instance after the user restarts her browser. The new URL does not need to be absolute; if it's relative, it's resolved relative to the current URL. The new URL must be of the same origin as the current URL; otherwise, pushState() will throw an exception. This parameter is optional; if it isn't specified, it's set to the document's current URL.
Browser security settings prevent people from modifying the displayed url directly. You could imagine the phishing vulnerabilities that would cause.
Only reliable way to change the url without changing pages is to use an internal link or hash. e.g.: http://site.com/page.html becomes http://site.com/page.html#item1 . This technique is often used in hijax(AJAX + preserve history).
When doing this I'll often just use links for the actions with the hash as the href, then add click events with jquery that use the requested hash to determine and delegate the action.
I hope that sets you on the right path.
There is a Yahoo YUI component (Browser History Manager) which can handle this: http://developer.yahoo.com/yui/history/
Facebook's photo gallery does this using a #hash in the URL. Here are some example URLs:
Before clicking 'next':
/photo.php?fbid=496429237507&set=a.218088072507.133423.681812507&pid=5887027&id=681812507
After clicking 'next':
/photo.php?fbid=496429237507&set=a.218088072507.133423.681812507&pid=5887027&id=681812507#!/photo.php?fbid=496435457507&set=a.218088072507.133423.681812507&pid=5887085&id=681812507
Note the hash-bang (#!) immediately followed by the new URL.
A more simple answer i present,
window.history.pushState(null, null, "/abc")
this will add /abc after the domain name in the browser URL. Just copy this code and paste it in the browser console and see the URL changing to "https://stackoverflow.com/abc"
There's a jquery plugin http://www.asual.com/jquery/address/
I think this is what you need.
What is working for me is - history.replaceState() function which is as follows -
history.replaceState(data,"Title of page"[,'url-of-the-page']);
This will not reload page, you can make use of it with event of javascript
I was wondering if it will posible as long as the parent path in the page is same, only something new is appended to it.
So like let's say the user is at the page: http://domain.com/site/page.html
Then the browser can let me do location.append = new.html
and the page becomes: http://domain.com/site/page.htmlnew.html and the browser does not change it.
Or just allow the person to change get parameter, so let's location.get = me=1&page=1.
So original page becomes http://domain.com/site/page.html?me=1&page=1 and it does not refresh.
The problem with # is that the data is not cached (at least I don't think so) when hash is changed. So it is like each time a new page is being loaded, whereas back- and forward buttons in a non-Ajax page are able to cache data and do not spend time on re-loading the data.
From what I saw, the Yahoo history thing already loads all of the data at once. It does not seem to be doing any Ajax requests. So when a div is used to handle different method overtime, that data is not stored for each history state.
my code is:
//change address bar
function setLocation(curLoc){
try {
history.pushState(null, null, curLoc);
return false;
} catch(e) {}
location.hash = '#' + curLoc;
}
and action:
setLocation('http://example.com/your-url-here');
and example
$(document).ready(function(){
$('nav li a').on('click', function(){
if($(this).hasClass('active')) {
} else {
setLocation($(this).attr('href'));
}
return false;
});
});
That's all :)
I've had success with:
location.hash="myValue";
It just adds #myValue to the current URL. If you need to trigger an event on page Load, you can use the same location.hash to check for the relevant value. Just remember to remove the # from the value returned by location.hash e.g.
var articleId = window.location.hash.replace("#","");