I've implemented History.js on a local test application. Everything seems to work, however if I press the back button in the browser, the previous content does not get restored.
Do I actually have to load the content manually again (i.e. make another ajax call) when user presses the back button? Then how does github do it? I see they don't make another ajax call when clicking back button in the code tree.
Here is my code:
History.Adapter.bind(window,'statechange',function()
{
var State = History.getState();
History.log(State.data, State.title, State.url);
});
$('a').each(function(index, link) {
if ($(link).attr('data-ajax-disabled') != 'true') {
$(link).click(function(event)
{
var clips = $(this).attr('data-ajax-clips') || '';
$.ajax($(this).attr('href'),
{
data: {_clips:clips},
success: function(data)
{
var data = $.parseJSON(data);
History.pushState({state:1}, data.title || document.title, 'http://127.0.0.1/site/www/');
$.each(data.clips, function(key, val)
{
$(key).replaceWith(val);
});
}
});
return false;
});
}
});
data.clips is a json array which contains id's of html objects as key and the actual html content as value. For example
'#header' => 'content in header div'
As noted, the replacement works fine. I output a random number in the header. Every click on a link spits out another random number in the header. However, if I push the back button the number stays the same, only the title will be restored (also random number).
Ok I got it, also thanks to Tobias Cohen for the hint.
One has to store the loaded data in the history object (State.data). First let's see how the statechange callback changed:
History.Adapter.bind(window, 'statechange', function()
{
var State = History.getState();
$.each(State.data.clips, function(key, val)
{
$(key).replaceWith(val);
});
History.log(State.data, State.title, State.url);
});
As you can see, on each statechange I can access State.data.clips and replace the html content.
NOTE: A statechange does also happen when calling History.pushState(). That means in my initial question the second code snippet is wrong in the fact that I do the content manipulation in there. There's no need for it. Just call History.pushState() and do any content manipulation within the statechange callback.
So for completeness, this is how I push the clips into the history object:
History.pushState({state:1, clips:data.clips}, data.title || document.title, 'http://127.0.0.1/site/www/');
Related
For class, I'm trying to build a single page web page using jquery. One of the components of this is changing the HTML to find the correct ID to show the proper information needed.
I'm running into an issue where after the HTML is changed, the HTML is reverted to its original text. I've done some googling and I learned that it's reverting because of the page switch. Most of the questions already asked about this are dealing with form submissions so I'm not really sure how to deal with it in my case.
What I've tried already is having a global variable that keeps track of the ID but when I switch pages, the global variable also gets reset to its original value. I know that the value is getting reset because I have a console log of before and after.
function createList() {
let liArray = Array.from(document.getElementsByClassName("oneMusic"));
liArray.forEach(function (element) {
element.addEventListener("click", function () {
var parm = this.getAttribute("data-parm"); // passing in the record.Id
document.getElementById("IDparmHere").textContent = parm;
console.log(
"parm: " + document.getElementById("IDparmHere").innerHTML
);
param = parseInt(parm);
console.log("param" + param);
// now jump to our page that will use that one item
setTimeout(() => {
document.location.href = "index.html#details";
}, 1000);
});
});
}
And then the code that handles transferring pages. To note, param is the global var.
document.addEventListener("DOMContentLoaded", function (e) {
createList();
$(document).on("pagebeforeshow", "#details", function (event) {
console.log("param 2: " + param);
let localID = param;
let idx = GetArrayPointer(localID);
console.log("local id: " + localID);
console.log("arrayPointer: " + arrayPointer);
document.getElementById("oneTitle").innerHTML =
"The title is: " + songArray[idx].Song;
});
});
So the createList() does a bunch of things but at the end of it, it adds an event listener to each of the li elements. When you click on it, you pull the specific ID of that li and then you get transferred to the details page.
By the time the code reaches the details page, both the HTML and the global var revert back to their original values, which makes it useless in figuring out the ID.
For example, if #IDparmHere was changed from "blank" to "1", then after the page switch happens, #IDparmHere is changed back to "blank".
I set the global var as null initially, after it's changed from null to 1 or 2 or 3, after the page switch it goes back to null.
Also, parm is supposed to be "param" but the instructor that gave us the skeleton of this code has dyslexia so..
I have an ajax call that builds a small graph in a popup window. The html for the link is re-used in many different links for different devices on the page. What happens, is that when you click a graph for the first device, you get that device. You click a button for the second device, you get that device, however, if you keep clicking away, after the third click or so, you suddenly start getting only the first device, over and over. I think my variables are being cached in some odd way, and I don't understand:
the HTML:
<a class="bluebtn graphbutton ingraph" href="http://wasat/cgi-bin/rrdjson.cgi?res=3600&start=-24h&end=now-1h&uid=28.7B2562040000" data-uid="28.7B2562040000" data-name="Laundry Room Freezer"></a>
<a class="bluebtn graphbutton ingraph" href="http://wasat/cgi-bin/rrdjson.cgi?res=3600&start=-24h&end=now-1h&uid=28.F7A962040000" data-uid="28.F7A962040000" data-name="Garage Temp"></a>
The code in question:
$(document).ready(function() {
$('.graphbutton').click(function(e) {
var formURL = $(this).attr("href");
var uid = $(this).data("uid");
var name = $(this).data("name");
e.preventDefault();
$.ajax({
url: formURL,
dataType: "json",
cache: false,
context: this,
success: function(data){
console.log("calling mkgraph with uid "+uid+" name " +name);
make_graph(data.data, uid, name);
},
error: function(ts) {
console.log(ts.responseText); }
});
}); /* clickfunc */
}); /*docready */
What happens:
Click freezer:
"calling mkgraph with uid 28.7B2562040000 name Laundry Room Freezer"
Click Garage:
"calling mkgraph with uid 28.F7A962040000 name Garage Temp"
Click Garage again:
"calling mkgraph with uid 28.7B2562040000 name Laundry Room Freezer"
Some of these links are being manufactured by the make_graph() function. I'm a bit worried that this is the issue, and somehow the ajax thing needs to be re-initialized after doing this?
By request, the relevant code in make_graph() that I think is causing my issue here. Basically, I'm editing the buttons in the css popup on the fly, and I think this is creating a wierd situation where the ajax binding is bound to the old href, and not being updated, even though the link is correct in the produced html. This is consistent with the effect where the binding only gets mangled on the third attempt.
$(".ingraph").each(function() {
this.href = $(this).attr("href").replace(/uid=.*/g, 'uid=' + uid);
this.setAttribute('data-uid' ,uid);
if (devname.length > 0) {
this.setAttribute('data-name', devname);
}
});
EDIT: adding a long answer:
I have multiple buttons on the main page. Each one specifies a "uid" that gets fed to rrdjson.cgi, which takes the uid and finds the data for that device, and returns it as json. When make_graph() recieves this json data, it populates a css popup, with the graph, and edits 5 buttons so they reference that UID. Those 5 buttons change the timescale of the graph by re-requesting the data from rrdjson.cgi.
What I am worried is happening, is that I click on the frige, it changes the uid's of the buttons inside the popup to reference the frige. Then I close that, click on the garage, it also changes the uid's and correctly shows the garage data. Then I click on one of the buttons inside the popup for the garage, and poof, I get the refrigerator again. I suspect that ajax "remembers" the old values for $(this).attr("href") etc and passes those values to the code, rather than re-reading the contents of the HTML. (perhaps instead of HTML, I meant DOM there, I'm a little vauge on the difference, but I suspect I meant DOM)
Maybe the answer is to somehow un-register the ajax binding to those buttons and re-register it every time make_graph() changes them? How would I do the un-register? .off() ? .unbind() ?
After much gnashing of teeth, and google, I have answered my own question.
https://forum.jquery.com/topic/jquery-data-caching-of-data-attributes
Turns out, jquery caches "data" types, but not attr types. So when I do:
uid = $(this).data("uid");
vs
uid = $(this).attr("data-uid");
I get wildly different results. I guess the moral of the story is that .data is super evil.. :)
If you add a random value to your url like
var formURL = $(this).attr("href")+"?rv="+Math.random();
you'll force the ajax call to reload the URL. You can use the cache property (set it to false) JQuery will load the data again, but any proxy may send a cached version.
(Please check that there are no other attributes set in the url, otherwise set "&rv="+Math.random(); (& instead of ?) use
var formURL = $(this).attr("href");
formURL + (formURL.indexOf("?") > 0 ? "&rv=" : "?rv=" )+ Math.random();
Your problem should not have something to do with make_graph() as uid and name depend on $('.graphbutton')
(if not make_graph(), or some other function, changes the attributes of your buttons)
I am trying to implement a navigation to my ajax controlled site, and I am encountering some strange errors.
I am using History JS, the HTML5 only version.
This is how I initialize it:
function initializeHistory() {
var History = window.History;
if ( !History.enabled ) {
console.log("Not enabled!");
return false;
}
// Changing the main page state to -1.
History.replaceState({id:-1}, baseTitle, baseUrl);
// Bind to StateChange Event
History.Adapter.bind(window,'statechange',function(){
var State = History.getState();
console.log(History.savedStates);
if (historyData.manualStateChange)
{
if (State.data.id=='-1') alert('History start');
historyData.allowHistoryPushPop=false;
var gotoState=historyData.data[State.data.id];
var currentState=historyData.currentNavigation;
/* Some code here to revert to the previous state */
historyData.allowHistoryPushPop=true;
}
History.log(State.data, State.title, State.url);
});
};
I am using a global object, named historyData, in which I store the following things:
var historyData={
data: new Array(), //an array which contains objects that refer to the data that needs to be shown, when back or forward button is pushed
manualStateChange: true, //using this to figure out if the back or forward button was pressed in the browser, or if I am adding or removing a state from History programmatically
allowHistoryPushPop: true, //using this to prevent history from being changed in certain situations
currentNavigation: {
page: 'dashboard',
pageid: null,
module: null,
moduleid: null
}, // stores the current object from the historyData.data array
status: true // boolean that enables or disables History on my page
}
Whenever I click on a link in my page, a function, called navigation fires, which changes the History's state, and eventually runs a chain of functions to display the page which was asked for by the user. The relevant parts from the navigation function are as follows:
function navigation(gotofunc, navData) {
if ((historyData.allowHistoryPushPop) && (historyData.status))
{
if (navData['urlData']==null) // if this is null, then the title and the url for the asked page, will be returned by the server, after an ajax call, so we set it to the current url and current title
{
var curState=History.getState();
urlData={
title: curState.title,
url: curState.url
};
} else {
urlData=navData['urlData'];
if (!urlData['title']) urlData['title']=curState.title;
if (!urlData['url']) urlData['url']=curState.url;
}
navData['parameters']=new Array();
if (arguments.length>2) for (i=2;i<arguments.length ;i++) navData['parameters'].push(arguments[i]);
historyData.manualStateChange=false; // we are making a programmatic change, so we don't want the binded 'statechange' to fire
historyData.data.push(navData); // store the history data in our own array
History.pushState({id:historyData.data.length-1}, urlData['title'], urlData['url']); // push the History state, and set it's id to point to our newly added array element
historyData.manualStateChange=true; // re-enable the manual state change
}
}
If I don't know up front what the URL will be after I fetch the data via ajax, my Ajax call, replaces the current state with the correct data, this way:
if ((dataArray['navDat']) && (historyData.status))
{
historyData.manualStateChange=false;
var State = History.getState();
History.replaceState(State.data, dataArray['navDat']['title'], dataArray['navDat']['url']);
historyData.manualStateChange=true;
}
This works fine for the most part. If I navigate forward a few pages, and then go backwards, everything works greatly, if I then go forward once again(all by using the browsers back and forward button), it works greatly. There is only one exception: if I load the page, load a subpage, then try to click on the back button, this line never fires:
if (State.data.id=='-1') alert('History start');
It basicaly won't figure out that I arrived back at the front page, when I only navigate one page forward.
The other strange thing is the following(perhaps this is what's causing my original problem also): I tried fetching the savedStates of the History object, to see what is going on, and strangely, when I use the replaceState event, it adds a new state in savedStates. One of the objects that is added, is with the correct data id, the other one is with the previous data id.
What could be causing the problem, is there an error in my script somewhere? Or is this completly normal? (the part of adding multiple objects to History.savedStates after replaceState)
Thanks for the help in advance!
If I replace the url, or the title of the page with the first replaceState, the program realizes when I return to the front page, I dunno why, but till then this is the best solution I can figure.
I'll dumb it down a bit, but I have a bit of code in my site that shows some elements one way, lets say I had this : "I have an apple"
I am using a bit of JavaScript so that when a drop down changes to, lets say, orange, the p tag is now written: "I have an orange"
When I click submit on the form I have, that new value is being passed to a database, but the page is not refreshed. When I go to make a new entry, my initial value is: "I have an orange"
But I would like if it reverted back to it's initial state of: "I have an apple"
Is there a way to tell the site, once the submit is completed, clear all changes - without refreshing?
Here's my initial JavaScript:
function getRequirements(client_name,location) {
var strURL="display/getRequirements.php?client_name="+client_name+"&location="+location;
var req = getXMLHTTP();
if (req) {
req.onreadystatechange = function() {
if (req.readyState == 4) {
// only if "OK"
if (req.status == 200) {
document.getElementById('requirementsdiv').innerHTML=req.responseText;
} else {
alert("There was a problem while using XMLHTTP:\n" + req.statusText);
}
}
}
req.open("GET", strURL, true);
req.send(null);
}
}
One solution would be to add a listener to the button which sends your AJAX request and then reverts the variables back to their original state.
You could have a function called revert() which resets all the variables as required.
For example, using JQuery...
$('#button').click(function(){
$.get("myurl",function(data){
if(data.status == 200){
//Successful AJAX request.
revert();
}
});
});
If you had it set up as a form field, you could just reset the form. If you don't want the user to be able to type, just add the readonly attribute, and optionally style it to not look like an input element anymore.
The DOM does not itself record changes and thus only keeps track of the current state.
Your own code could keep track of either the initial innerHTML of the page or the changes you make and after the ajax call, your code could restore the page back to it's initial state. There is no javascript function to do that for you automatically without some sort of page reload.
It would not be hard for you to keep track of each element that you modify and what it's original state was and then when you want to restore the page, you just cycle through that list of elements and put the content back to the way it originally was.
Here's an idea how to implement keeping track of the changes yourself. You call markElement() right before changing any element. You call restoreElements() when you want to restore them all.
var changedElements = {};
var changedCntr = 1;
// call this right before you change the contents of any element
// that you want later restored
function markElement(elem) {
if (!elem.id) {
elem.id = "_changeCnt" + changedCntr++;
}
// if we haven't already saved the state of this element, save it now
if (!(elem.id in changedElements)) {
changedElements[elem.id] = elem.innerHTML;
}
}
// call this when you want to restore all elements
function restoreElements() {
for (var id in changedElements) {
var elem = document.getElementById(id);
elem.innerHTML = changedElements[id];
}
changedElements = {};
}
You should probably only call markElement on leaf nodes (elements with no children) so that you don't run the risk of losing any event handlers when you do the restore.
Note: this code only handles changes to elements, not creation/deletion unless you call markElement on a parent node before creation/deletion of child objects.
I'm trying to create a CMS system based on AJAX using Prototype's library. On a page load, I have HTML, page title and additional Javascript for the page returned via JSON, and I update the HTML on the main area. I also have an event listener that listens for certain ID's to be clicked on.
The listener is working,
var TabMenu = {
selectedTab: 'main',
showTab: function(pid) { alert(pid); alert($(pid));
$(pid).addClassName('selected'); this.selectedTab = pid;
$(this.defaultTab).removeClassName('selected');
}};
After loading, I click on one of the new tabs, and the first "test" alert successfully alerts the element's ID, but the second alert ($(pid)) returns null. I can only surmise that the HTML returned by the AJAX request is not being evaluated and added to the DOM, otherwise it would alert [HTMLDivElement] instead of "null".
Here is the relevant AJAX call:
new Ajax.Request(url, {
onSuccess: function(t) {
data = t.responseText.evalJSON();
Page.update(data.html, data.title, data.js);
Page.destroyLoader();
}
});
And here is the updating function:
update: function(data, title, js) {
document.title = Global.title + title;
if (title != "") { $('HEADING').update(title); }
$('MAIN').update(data);
if (js != "") {
var nuJS = new Element('script', { type: 'text/javascript' }).update(js);
$('MAIN').insert({ top: nuJS });
}
}
Any ideas on how I can get this working?
When is the ajax request triggered? Is it triggered when you click the tab? If so the showTab function is being triggered before data has been inserted into the DOM.
If you have firebug, try using the console to select the html data, after the ajax call has finished, to see what you get. You can also use firebug's html tab to see if the data has been inserted into the DOM.
Also, even though you get the pid parameter that is set to a value, does it refer to a real id that exists in the DOM?
From your code and the comment above.
I think your plan is to load all the tabs after the page loaded immediately.
And hide all of them using the css. Wait until the user click the tab,
Show only the one that is "selected", right?
That's mean you should change:
$('MAIN').update(data);
To something like
$('MAIN').update({after: data});
So it won't overwrite the existed one.
And don't forget to move the code for document.title and eval js into showTab function.
For javascript evaluation you can insert the js into data.html and use this instead:
$('MAIN').innerHTML.evalScripts();