History.js getState() at pageload - javascript

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.

Related

Javascript: window.location.search does not update?

I need to update search (query) parameters in URL if user enters those in search panel (on a page). I'm trying this:
$( document ).ready(function() {
if ($('.o_website_license_search_panel').length) {
$('.o_website_license_search_panel .o_search_submit').click(function () {
var search = $.deparam(window.location.search.substring(1));
console.log('before update')
console.log(window.location.search)
search.license_key = $(".o_website_license_search_panel input[name='license_key']").val();
console.log('new obj ', search, $.param(search))
window.location.search = $.param(search);
console.log('after update')
console.log(window.location.search)
});
}
});
And I get this output:
before update
web.assets_frontend.js:1254 ?license_state=cancel
web.assets_frontend.js:1255 new obj {license_state: "cancel", license_key: "test2"} license_state=cancel&license_key=test2
web.assets_frontend.js:1256 after update
web.assets_frontend.js:1257 ?license_state=cancel
As you can see window.location.search stays the same. Is there something I miss, or it is intended this way?..
Setting search (or any of the other properties on location other than hash) causes a reload of the page. The property will continue to have its previous value until that happens, which is why you see the previous value in your logging statements. After the old value is logged, unless code on your page is preventing it from happening, the page will be reloaded with the new query string, at which point location.search would reveal the new string.
More: window.location's properties on MDN
You can use the history api provided by javascript also to help out, but to also let you know it won't refresh the page but change the browser url in the url pane, Cleverly done you can achieve what you want
Visit this link for more details
https://developer.mozilla.org/en-US/docs/Web/API/History_API
Note
Sometimes people use this method for ajax purpose, thereby enabling them to store history data and also for bookmarking

Attempting to use a global array inside of a JS file shared between 2 HTML files and failing

So I have one HTML page which consists of a bunch of form elements for the user to fill out. I push all the selections that the user makes into one global variable, allTheData[] inside my only Javascript file.
Then I have a 2nd HTML page which loads in after a user clicks a button. This HTML page is supposed to take some of the data inside the allTheData array and display it. I am calling the function to display allTheData by using:
window.onload = function () {
if (window.location.href.indexOf('Two') > -1) {
carousel();
}
}
function carousel() {
console.log("oh");
alert(allTheData.toString());
}
However, I am finding that nothing gets displayed in my 2nd HTML page and the allTheData array appears to be empty despite it getting it filled out previously in the 1st HTML page. I am pretty confident that I am correctly pushing data into the allTheData array because when I use alert(allTheData.toString()) while i'm still inside my 1st HTML page, all the data gets displayed.
I think there's something happening during my transition from the 1st to 2nd HTML page that causes the allTheData array to empty or something but I am not sure what it is. Please help a newbie out!
Web Storage: This sounds like a job for the window.sessionStorage object, which along with its cousin window.localStorage allows data-as-strings to be saved in the users browser for use across pages on the same domain.
However, keep in mind that they are both Cookie-like features and therefore their effectiveness depends on the user's Cookie preference for each domain.
A simple condition will determine if the web storage option is available, like so...
if (window.sessionStorage) {
// continue with app ...
} else {
// inform user about web storage
// and ask them to accept Cookies
// before reloading the page (or whatever)
}
Saving to and retrieving from web storage requires conversion to-and-from String data types, usually via JSON methods like so...
// save to...
var array = ['item0', 'item1', 2, 3, 'IV'];
sessionStorage.myApp = JSON.stringify(array);
// retrieve from...
var array = JSON.parse(sessionStorage.myApp);
There are more specific methods available than these. Further details and compatibility tables etc in Using the Web Storage API # MDN.
Hope that helps. :)

Ember.js when using ModelFor('') in conjunction with the back button fails to load data

I am using Ember data and The RESTAdapter with an extension for Django.
Here is a JSBin
Here is how our routes are set up:
App.Router.map(function() {
this.resource('locations');
this.resource('location', {path:'locations/:location_id'}, function() {
this.resource('items', function() {
this.route('create');
});
this.resource('item', { path:'item/:item_id' }, function() {
this.route('edit');
});
});
});
App.LocationsRoute = Ember.Route.extend({
model: function () {
return this.get('store').find('location');
}
});
App.ItemsRoute = Ember.Route.extend({
model: function(){
//Get the model for the selected location and grab its item list.
//This will do a call to /locations/1/items
return this.modelFor('location').get('items');
}
});
Now this all works fine when we navigate to locations/1/items. The user is presented with a list of items relevant to the location with id 1.
When the user clicks one of these items it brings the url to #/locations/1/item/1 and displays the details of the item with id 1.
Now what doesnt work is this:
When I hit the back button the #/locations/1/items route loads but it does not have its data any more and no REST call to api/locations/1/items occurs. Even though the data displayed just fine when we first navigated to #/locations/1/items.
It is like Ember said "Well we already loaded that data, so we dont need to call the api again" but the data is somehow not being displayed in our template.
If I change the ItemsRoute model to this:
return this.get('store').find('item');
The scenario above works perfectly fine but the data is not based on our location.
Is there something I am missing with using this.modelFor? Or is my route set up incorrect?
Let me know if theres any extra info you need.
Update 1:
Having changed the model function to this I have discovered some new insights:
model: function(){
//Get the model for the selected location and grab its item list.
//This will do a call to /locations/1/items
var location = this.modelFor('location');
var items = location.get('items');
return items;
}
Upon first load of #/locations/1/items the location variable holds the location model and the items variable holds something which has a 'content' property, 'isFulfilled: true' and some other things.
This correctly works and displays the list of items. When i click on a particular item and got to #/locations/1/items/1 then hit the back button. The breakpoint triggers and location is still correctly populating with the location model.
However the items variable seems to just be a PromiseArray, do I need to somehow wait for the promise to be fulfilled before this model hook returns? I thought Ember already did this automatically? I suppose this means that the route is loading before the promise is fulfilled and thats why there is not data displayed?
Therefore my list is not populated correctly.
I'm on a phone, so I'll update this a bit later with more, but the problem is your location resource isn't a child of locations. Becaude of that, ember says why waste time fetching that route if it isn't a part of that resource path. It only hits the location route, which I'm betting you don't have a model hook for fetching the single location (at least based on the code above).
Ok, here is the deal. I have fixed the issue but have no idea why it fixed the issue.
Without further ado:
Here is a jsbin example of the exact setup I have. The only difference between the real one and the jsbin is the fact that I am using the RestAdapter instead of the FixtureAdapter. This is not technically true because I am using the ember-django-rest-adapter which extends the REST one.
The issue described in the question does not present itself in the jsbin but the exact setup with the ember-django-rest-adapter does present the issue.
The fix was to break the cyclic relationship between User -> Site -> Items -> User
For example if I comment out the 'locations' relationship in the User model, the back button works.
Or if I comment out the 'owner' relationship to User in the Item model the back button works.
I might ask a separate question to see what the reasoning behind the problem is, although if someone can shed any light in to why this is happening I'll happily accept the answer here.

How can I bind own data to the history object using JQuery Mobile?

I'm looking for a nice way to bind my own app data (like currentQuestion, currentAnswer) to the history object.
I tried to keep track in the hashchange event, but that gets complicated.
I try it with url parameters, but I want to have status object with some entries for each element on the history stack, so I would have to have many parameters.
Is there a way just to add an object to the history stack?
If you use jquery mobile ajax transition you will have access to the same global jquery object on all pages, you can append data to it, for example in the first page you assign currentQuestion and currentAnswer
$.currentQuestion = 'something';
$.currentAnswer = 'something';
On the next page you can access these variables easily
console.log($.currentQuestion);
That is how I move data between pages inside jquery mobile
I found the way. You can just insert variables into the history stack:
var urlh = $.mobile.urlHistory;
if( "factId" in urlh.stack[urlh.activeIndex] ) {
// load
cp10.score.currentFactId = urlh.stack[urlh.activeIndex].factId;
} else {
// store fact id in history object
urlh.stack[urlh.activeIndex].factId = cp10.score.currentFactId;
}
Here I insert a variable 'factId'. If it is not already in the stack element (what happens when the page is first loaded), then insert it. Later (when hte user hits the back button or so) you can retrieve it from there.

Change the URL in the browser without loading the new page using JavaScript

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("#","");

Categories

Resources