I have an application where you arrive at a photo album listing via various means (searching, drill down by category, etc). From the album, you click open individual photos and iterate through them using left/right arrows, as well as various editing tools. Each of the iterate/tool actions does a history.push() to add the route to the router history. I am trying to implement a button that will go back to the exact place in the history before you started viewing individual photos. To do that, I'd like to walk the history stack backwards to the point where the the history matches 1 of several patterns then do a history.go(-14) (or whatever) to jump back to the route that started the whole chain.
I've been searching the React router code as well as the HTML5 History object and I don't see any way of accessing the history stack directly so I can walk it back. I'd rather not depend on making each individual photo action push their path to a separate place since that makes it fragile (each photo action is built by a different developer and adding new actions in the future will require previous knowledge to remember to do that).
I can't go to a hard coded path because that would be pushed to the top of the history stack and hitting back arrow would just return you to the last individual photo page instead of the search page that generated the album list in the first place.
Any suggestions on accessing the history stack?
I do not know if this would be relevant to your case anymore since this is an old question, but I will put this answer here since I had to solve a similar problem myself.
In my own research, I was not able to find a "history.stack" object, but there is a workaround that is also listed here. Basically, you set up a goBack counter that gets passed to the next route as location state. You can then go back the in the stack however many times the counter says to.
let goBack
if (history.location.state) {
goBack = history.location.state.goBack
} else {
goBack = -1
}
You can pass location state in React Links by replacing the to= prop with an object.
<Link to={
{ pathname: "/next/path", state: { goBack: goBack - 1 } }
}> Click Here! </Link>
You then just call history.go(history.location.state.goBack) whenever you need to go to the entry route.
If you have the possibility of entering one of the changed routes without hitting the entry one (like a user copies and pastes a URL pointing to a single image), then you can add default behaviour.
if (history.location.state) {
history.go(history.location.state.goBack)
} else {
// You can add a default action if a user lands on route from an external link
history.push('/base/url')
}
This solution could also work if you need more complex behaviours like having 'checkpoint' routes that skip any routes in between as you go back. Just change goBack to be a list of numbers (representing the gaps in between each go-back jump) that you can pop each time you revert back to a checkpoint.
Another solution that might tickle your fancy is to just replace the route instead of pushing it to the stack, but this requires writing a custom component that wraps Link. You can find how to do that here
I have an array from ajax and i need to create jQuery Mobile Listview. I don't found a method for this, so is it possible?
Here's a working example: http://jsfiddle.net/Gajotres/SS7vJ/
And another example with an array: http://jsfiddle.net/Gajotres/yHHWQ/
$(document).on('pagebeforeshow', '#index', function(){
$('<ul>').attr({'id':'test-listview','data-role':'listview', 'data-filter':'true','data-filter-placeholder':'Search...'}).appendTo('#index [data-role="content"]');
$('<li>').append('Audi').appendTo('#test-listview');
$('<li>').append('Mercedes').appendTo('#test-listview');
$('<li>').append('Opel').appendTo('#test-listview');
$('#test-listview').listview().listview('refresh');
});
Also don't forget to call .listview( twice, first without refresh parameter, and second time with a refresh parameter. Without it you will receive this error:
cannot call methods on listview prior to initialization
If you want to find out more about how jQuery mobile handles dynamically added content and its markup take a look at this ARTICLE, to be transparent it is my personal blog, or find it HERE.
First of all, I know there's libraries that provide polyfills for location.pushState/popState (History.js, Hash.js, jQuery hashchange), so please don't just link to those.
I need a more powerful library to achieve the following in a RIA:
User clicks a link
library is notified and loads context via Ajax (no complete reload!)
All <a> elements are leveraged with a click handler that
prevents page reloads in 2. (preventDefault) and
calls location.pushState instead / sets location.hash for older browsers
loaded content is inserted in page and replaces current content
Continue with 1.
Also, previously loaded content should be restored as the user navigates back.
As an example, klick through Google+ in Internet Explorer <10 and any other browser.
Is there anything that comes even close? I need support for IE8, FF10, Safari 5 and Chrome 18. Also, it should have a permissive license like MIT or Apache.
I believe Sammy.js ( http://sammyjs.org) (MIT-licenced) has the best focus on what you want to do, with its 2 main pillars being:
Routes
Events
I could quote from the docs but it's pretty straightforward:
setup clientside routes that relate to stuff to be done, e.g: update the view through ajax
link events to call routes, e.g: call the route above when I click an link. (You would have to make sure e.preventDefault is called in the defined event I believe, since this is an app decision really, so that can't be abstracted away by any library that you're going to use imho)
Some relevant docs
http://sammyjs.org/docs
http://sammyjs.org/docs/routes
http://sammyjs.org/docs/events
Example for a route: (from http://sammyjs.org/docs/tutorials/json_store_1)
this.get('#/', function(context) {
$.ajax({
url: 'data/items.json',
dataType: 'json',
success: function(items) {
$.each(items, function(i, item) {
context.log(item.title, '-', item.artist);
});
}
});
});
Or something like
this.get('#/', function(context) {
context.app.swap(''); ///the 'swap' here indicates a cleaning of the view
//before partials are loaded, effectively rerendering the entire screen. NOt doing the swap enables you to do infinite-scrolling / appending style, etc.
// ...
});
Of course other clientside MVC-frameworks could be an option too, which take away even more plumbing, but might be overkill in this situation.
a pretty good (and still fairly recent) comparison:
http://codebrief.com/2012/01/the-top-10-javascript-mvc-frameworks-reviewed/
( I use Spine.js myself ) .
Lastly, I thought it might be useful to include an answer I've written a while ago that goes into detail to the whole best-practice (as I see it) in client-side refreshes, etc. Perhaps you find it useful:
Accessibility and all these JavaScript frameworks
I currently use PathJS in one of my applications.
It has been the best decision that i have made.
For your particular usecase take a look at HTML5 Example.
The piece of code that that makes the example work (from the source):
<script type="text/javascript">
// This example makes use of the jQuery library.
// You can use any methods as actions in PathJS. You can define them as I do below,
// assign them to variables, or use anonymous functions. The choice is yours.
function notFound(){
$("#output .content").html("404 Not Found");
$("#output .content").addClass("error");
}
function setPageBackground(){
$("#output .content").removeClass("error");
}
// Here we define our routes. You'll notice that I only define three routes, even
// though there are four links. Each route has an action assigned to it (via the
// `to` method, as well as an `enter` method. The `enter` method is called before
// the route is performed, which allows you to do any setup you need (changes classes,
// performing AJAX calls, adding animations, etc.
Path.map("/users").to(function(){
$("#output .content").html("Users");
}).enter(setPageBackground);
Path.map("/about").to(function(){
$("#output .content").html("About");
}).enter(setPageBackground);
Path.map("/contact").to(function(){
$("#output .content").html("Contact");
}).enter(setPageBackground);
// The `Path.rescue()` method takes a function as an argument, and will be called when
// a route is activated that you have not yet defined an action for. On this example
// page, you'll notice there is no defined route for the "Unicorns!?" link. Since no
// route is defined, it calls this method instead.
Path.rescue(notFound);
$(document).ready(function(){
// This line is used to start the HTML5 PathJS listener. This will modify the
// `window.onpopstate` method accordingly, check that HTML5 is supported, and
// fall back to hashtags if you tell it to. Calling it with no arguments will
// cause it to do nothing if HTML5 is not supported
Path.history.listen();
// If you would like it to gracefully fallback to Hashtags in the event that HTML5
// isn't supported, just pass `true` into the method.
// Path.history.listen(true);
$("a").click(function(event){
event.preventDefault();
// To make use of the HTML5 History API, you need to tell your click events to
// add to the history stack by calling the `Path.history.pushState` method. This
// method is analogous to the regular `window.history.pushState` method, but
// wraps calls to it around the PathJS dispatched. Conveniently, you'll still have
// access to any state data you assign to it as if you had manually set it via
// the standard methods.
Path.history.pushState({}, "", $(this).attr("href"));
});
});
</script>
PathJS has some of the most wanted features of a routing library:
Lightweight
Supports the HTML5 History API, the 'onhashchange' method, and graceful degredation
Supports root routes, rescue methods, paramaterized routes, optional route components (dynamic routes), and Aspect Oriented Programming
Well Tested (tests available in the ./tests directory)
Compatible with all major browsers (Tested on Firefox 3.6, Firefox 4.0, Firefox 5.0, Chrome 9, Opera 11, IE7, IE8, IE9)
Independant of all third party libraries, but plays nice with all of them
I found the last too points most attractive.
You can find them here
I hope you find this useful.
i'd like to suggest a combination of
crossroads.js as a router
http://millermedeiros.github.com/crossroads.js/
and hasher for handling browser history and hash urls (w/ plenty of fallback solutions):
https://github.com/millermedeiros/hasher/
(based on http://millermedeiros.github.com/js-signals/)
This will still require a few lines of code (to load ajax content etc.), but give you loads and loads of other possibilities when handling a route.
Here's an example using jQuery (none of the above libraries require jQuery, i'm just lazy...)
http://fiddle.jshell.net/Fe5Kz/2/show/light
HTML
<ul id="menu">
<li>
foo
</li>
<li>
bar/baz
</li>
</ul>
<div id="content"></div>
JS
//register routes
crossroads.addRoute('foo', function() {
$('#content').html('this could be ajax loaded content or whatever');
});
crossroads.addRoute('bar/{baz}', function(baz) {
//maybe do something with the parameter ...
//$('#content').load('ajax_url?baz='+baz, function(){
// $('#content').html('bar route called with parameter ' + baz);
//});
$('#content').html('bar route called with parameter ' + baz);
});
//setup hash handling
function parseHash(newHash, oldHash) {
crossroads.parse(newHash);
}
hasher.initialized.add(parseHash);
hasher.changed.add(parseHash);
hasher.init();
//add click listener to menu items
$('#menu li a').on('click', function(e) {
e.preventDefault();
$('#menu a').removeClass('active');
$(this).addClass('active');
hasher.setHash($(this).attr('href'));
});
Have you looked at the BigShelf sample SPA (Single Page Application) from Microsoft? It sounds like it covers how to achieve most of what you're asking.
It makes use of History.js, a custom wrapper object to easily control navigation called NavHistory and Knockout.js for click handling.
Here's an extremely abbreviated workflow of how this works: first you'll need to initialize a NavHistory object which wraps history.js and registers a callback which executes when there is a push state or hash change:
var nav = new NavHistory({
params: { page: 1, filter: "all", ... etc ... },
onNavigate: function (navEntry) {
// Respond to the incoming sort/page/filter parameters
// by updating booksDataSource and re-querying the server
}
});
Next, you'll define one or more Knockout.js view models with commands that can be bound to links buttons, etc:
var ViewModel = function (nav) {
this.search = function () {
nav.navigate({ page: 2, filter: '', ... }); // JSON object matching the NavHistory params
};
}
Finally, in your markup, you'll use Knockout.js to bind your commands to various elements:
<a data-bind="click: search">...</a>
The linked resources are much more detailed in explaining how all of this works. Unfortunately, it's not a single framework like you're seeking, but you'd be surprised how easy it is to get this working.
One more thing, following the BigShelf example, the site I'm building is fully cross-browser compatible, IE6+, Firefox, Safari (mobile and desktop) and Chrome (mobile and desktop).
The AjaxTCR Library seems to cover all bases and contains robust methods that I haven't seen before. It's released under a BSD License (Open Source Initiative).
For example, here are five AjaxTCR.history(); methods:
init(onStateChangeCallback, initState);
addToHistory(id, data, title, url, options);
getAll();
getPosition();
enableBackGuard(message, immediate);
The above addToHistory(); has enough parameters to allow for deep hash-linking in websites.
More eye-candy of .com.cookie(), .storage(), and .template() provides more than enough methods to handle any session data requirements.
The well documented AjaxTCR API webpage has a plethora of information with downloadable doc's to boot!
Status Update:
That website also has an Examples Webpage Section including downloadable .zip files with ready to use Front End(Client) and Back End(Server) project files.
Notably are the following ready-to-use examples:
One-way Cookie
HttpOnly Cookies
History Stealing
History Explorer
There are quite a bit other examples that rounds out the process to use many of their API methods, making any small learning curve faster to complete.
Several suggestions
ExtJs, see their History Example, and here are the docs.
YUI Browser History Manager.
jQuery BBQ seem to provide a more advanced feature-set over jQuery.hashcode.
ReallySimpleHistory may also be of help, though it's quite old and possibly outdated.
Note: ExtJs History has been extended to optimize duplicate (redundant) calls to add().
PJAX is the process you're describing.
The more advanced pjax techniques will even start to preload the content, when the user hovers over the link.
This is a good pjax library.
https://github.com/MoOx/pjax
You mark the containers which need will be updated on the subsequent requests:
new Pjax({ selectors: ["title", ".my-Header", ".my-Content", ".my-Sidebar"] })
So in the above, only the title, the .my-header, .my-content, and .my-sidebar will be replaced with the content from the ajax call.
Somethings to look out for
Pay attention to how your JS loads and detects when the page is ready. The javascript will not reload on new pages. Also pay attention to when any analytics calls get called, for the same reason.
I've made an extension who's purpose is to redirect urls.
I.e: www.google.com becomes: www.mysite.com/?url=www.google.com
I came across this post:
How to modify current url location in chrome via extensions
The problem I'm having is that the url's are both processed. The tab initially loads up google.com and only after it's finished my request is shown ( www.mysite.com/?url=www.google.com).
Is there any way to stop the initial request from being processed?
Something like:
chrome.tabs.onUpdated.addListener(function(tabId,obj,tab){
update.stop() // ??????????? Here I'm missing...
chrome.tabs.update(tabId,{url:....}, function callback); // My update stuff..
});
Thoughts?
thank you all.
You're looking for the webNavigation API.
You can register listeners to handle user navigation by modifying or blocking the request on the fly.
In the example below, when a user navigate to www.google.com, before the page even start loading onBeforeNavigate is fired and you can redirect the user to the CSS validation page for that URL:
chrome.webNavigation.onBeforeNavigate.addListener((details) => {
if(details.url.indexOf("www.google.com") !== -1)) {
chrome.tabs.update(details.tabId, {
url: "https://jigsaw.w3.org/css-validator/validator?uri=" + details.url
});
}
});
Remember to add the "webNavigation" permission to your extension manifest to get this functionality enabled.
chrome.tabs.onUpdated is fired two times per tab load - once a tab starts loading, and another time when it finishes loading. If you attach your update to the tab start loading event then it should work relatively quickly. You will still see original url being loaded for a brief moment, but it won't wait until it finishes, as you are describing.
chrome.tabs.onUpdated.addListener(function(tabId,obj,tab){
if(obj.status == "loading") {
chrome.tabs.update(tabId,{url:....}, function callback);
}
});
I don't think there is a more efficient solution at the moment.
I'm using HTML 5 history for my site, so, for users whose browsers support it, clicking on a link doesn't reload the whole page, but just the main area.
Google analytics doesn't track these partial page loads. How can I get it to track it just like it does for users that don't have HTML 5 history support?
You just need to register the additional pageviews by calling the _trackPageview function again each time your new content loads. This is called a 'Virtual Pageview' but is registered in Google Analytics in the same way as a real one. To set the path of the page you need to add an additional parameter to the function:
_gaq.push(['_setAccount', 'UA-XXXXXXX-X']);
_gaq.push(['_trackPageview', '/new/content']);
This is for the newest Universal Tracking Code.
So recently, I had to revisit my own answer for a new project. I noticed some issues that I should clean up.
To send a pageview programmatically, you want to send only the Path and the Query eg. for http://example.com/path/to/resource?param=1 we will send /path/to/resource?param=1.
Some SPAs use HashBangs (#!) for their urls. So we need to send anything after the Hashbang. e.g. http://example.com#!path/to/resource we will send /path/to/resource?param=1.
The earlier version of my solution was erroneous and would fail for all urls which had a hash in the url. Also, as I was using jQuery + History.js plugin my solution was along of listening to statechange came from there.
Use this new code to send a pageview. It is more resilient and caters for both hashbangs and history.
var loc = window.location,
hashbang = "#!",
bangIndex = location.href.indexOf(hashbang),
page = bangIndex != -1 ? loc.href.substring(bangIndex).replace(hashbang, "/") : loc.pathname + loc.search;
ga('send', 'pageview', page);
If you don't use Hashbangs specifically, simply change hashbang = "#!", to match e.g. hashbang = "##",
The second part of this is detecting when the url changes. For this, you will need to find out from the docs of whatever library you are using.
For jQuery + History.js plugin, the code below works
$(window).on('statechange', function() {
//put code here
});
More information can be found at https://developers.google.com/analytics/devguides/collection/analyticsjs/single-page-applications
$(window).on('statechange', function() {
var loc = window.location,
page = loc.hash ? loc.hash.substring(1) : loc.pathname + loc.search;
ga('send', 'pageview', page);
});
As Ewan already stated, you should send the pageview to analytics in the window.popstate event.
So, in plain javascript, if you have called:
history.pushState({'statedata':''}, 'title', '/new/page/url');
you should simply add:
window.addEventListener('popstate', function(event) {
ga('send', 'pageview');
});
Actually the new Universal Tracking Code automatically gets the current URL, so you don't really need to pass the extra parameter.