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.
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 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'm struggling with figuring out how to call/execute a function in jQuery. I've done quite a bit of searching and find what looks like it should be the answer, but it doesn't seem to work. I assume it is a scope issue since everything else seems to match examples I've found here, but I'm relatively new to jQuery and can't quite figure it out.
Basically, when the "bookmark" button is clicked, it uses ajax to create an entry in the database, and changes the format of the clicked button. This acts as expected. The trick is this requires someone to be logged in. The actual click of the button adds a #bookmarkme anchor to the url - if they aren't logged in (this is where things start getting tricky for me), the log in window pops up and they are prompted to sign up/log in, and the page reloads to set all the log in variables properly. This also works as expected. Where it breaks down is once the user logs in and the page reloads, I can't get the "bookmarkFunction" to run.
<script type="text/javascript">
var loggedin = <?php echo $loggedin; ?>;
var headerButtonScript = function(){
var bookmarkFunction = $("#bookmark").click(function(){
var directoryName = "<?php echo $directoryName;?>";
if(loggedin == 1 && $("#bookmark").hasClass("headerButton")){
$.ajax({
type: "POST",
url: "../includes/bookmarkProcess.php",
data: {directory: directoryName},
success: function(data, status){
if(data == "success"){
$("#bookmark").switchClass("headerButton", "headerButtonDisabled", 1000, "easeInOutQuart");
$("#bookmark").text("Bookmarked");
}
}
});
}
else{
$("#signInContent").toggleClass('hidden');
$("#signInPopUp").toggleClass('hidden');
}
});
};
var headerButtonsAfterLoad = function(){
var currentAddress = window.location.href;
var hashPosition = currentAddress.indexOf("#");
var targetLocation = currentAddress.substring(hashPosition+1);
if(targetLocation == "bookmarkme"){
if(loggedin==1){
//CALL bookmarkFunction HERE;
//I know I get to this location when expected, because placing an alert("message") gives me the pop up
}
}
};
$(document).ready(headerButtonScript);
$(window).bind('load',"",headerButtonsAfterLoad);
</script>
Based on my research, I have tried the following lines (one line attempted each time rather than all at once, of course) in the excerpt to try to call the function, but no luck yet.
if(targetLocation == "bookmarkme"){
if(loggedin==1){
//CALL bookmarkFunction HERE;
bookmarkFunction();
bookmarkFunction.run();
bookmarkFunction.call();
bookmarkFunction.apply();
}
}
Any help on locating my issue - scope, methods, or otherwise - is greatly appreciated!
"I need the script to take the same action it would when I click on the bookmark button (ID = bookmark) when the page reloads and has the anchor #bookmarkme"
This will do what you want^
$('#bookmarkme').trigger('click');
$("#bookmark").click(). should work.
Remember, calling a jQuery method on a jQuery object returns the original jQuery object. So if you say:
var bookmark=$('#bookmark')
Then bookmark is set to the jQuery object (which contains the element of id=bookmark as a property).
If you attach methods to the object like this:
var bookmark=$('#bookmark').click(function(){console.log('You clicked!')})
Then, yes, the element with id bookmark will now call this event when you click it, but the click method on a jquery object returns the original jquery object. That means bookmark will still be equal to $("#bookmark"), not the function in the click method.
So in conclusion, when you attach an event to a jquery object, like click or hover, it goes into the dom and attaches the event, and then returns the original jquery object. That way you can do things like:
var bookmark=$("#bookmark").click(function(){console.log("you clicked")}).mouseover(function(){console.log("you moused over")})
And you can keep attaching events forever and ever, and bookmark will always be equal to $("#bookmark")
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/');
I have a pretty simple HTML form where users can enter in information about a person. Below that form is a button which allows them to 'add more'. When clicked, the 'person' form is copied and appended to the page.
The way I used to do this was to take my HTML file, copy out the relevant section (the part that gets 'added more') and then save it into a variable in the Javascript. This became rather annoying when I had to make changes to the form as I would then have to make the same changes to the Javascript variable.
My new method is to create the variable dynamically in Javascript. When the page loads, I use jQuery to grab out the 'add more' part of the code and cache the HTML into a variable. Then when the 'add more' button is clicked, I append that cached HTML to the page.
The problem is with form inputs. The server-side code autofills the form with the user's data from the database. I want to cache that HTML data with no form inputs...
My current function looks like this:
function getHTML($obj, clean)
{
if (clean)
{
var $html = $obj.clone();
$html.find('input').each(function() { $(this)[0].value = ''; });
}
else
{
var $html = $obj;
}
var html = $html.wrap('<div></div>').parent()[0].innerHTML;
$html.unwrap();
return html;
}
It doesn't work. I'm also unsure if this is the best approach to solving the problem.
Any ideas?
I don't know why this wouldn't work. I can't see how the function is being called, or what is being passed to it.
I guess one thing I'd do differently would be to create a .clone() whether or not you're "cleaning" the inputs. Then you're not wrapping and unwrapping an element that is in the DOM. Just use the if() statement to decide whether or not to clean it.
Something like this:
function getHTML($obj, clean) {
var $clone = $obj.clone();
if (clean) {
$clone.find('input').each(function() { this.value = ''; });
}
return $clone.wrap('<div></div>').parent()[0].innerHTML;
}
Or a little more jQuery and less code:
function getHTML($obj) {
return $obj.clone().find('input').val('').end().wrap('<div/>').parent().html();
}
A little less efficient, but if it only runs once at the page load, then perhaps not a concern.
Or if it is going to be made into a jQuery object eventually anyway, why not just return that?
function getHTML($obj) {
return $obj.clone().find('input').val('').end();
}
Now you've returned a cleaned clone of the original that is ready to be inserted whenever you want.
EDIT:
Can't figure out right now why we can't get a new string.
Here's a function that will return the DOM elements. Beyond that, I'm stumped!
function getHTML($obj, clean) {
var $clone = $obj.clone();
if (clean) {
$clone.find('input').each(function() {
this.value = '';
});
}
return $clone.get(); // Return Array of DOM Elements
}
EDIT: Works now.
I ditched most of the jQuery, and used .setAttribute("value","") instead of this.value.
Give it a try:
function getHTML($obj, clean) {
var clone = $obj[0].cloneNode(true);
var inputs = clone.getElementsByTagName('input');
console.log(inputs);
for(var i = 0, len = inputs.length; i < len; i++) {
inputs[i].setAttribute('value','');
}
return $('<div></div>').append(clone)[0].innerHTML;
}
I would wrap the part of the form that needs to be cloned in a <fieldset>:
<form id="my_form">
<fieldset id="clone_1">
<input name="field_1_1">
<input name="field_2_1">
<input name="field_3_1">
</fieldset>
</form>
Add one more
Then for the jQuery script:
$("#fieldset_clone").click(function(event) {
// Get the number of current clones and set the new count ...
var cloneCount = parseInt($("fieldset[id^=clone_]").size());
var newCloneCount = cloneCount++;
// ... then create new clone based on the first fieldset ...
var newClone = $("#clone_1").clone();
// .. and do the cleanup, make sure it has
// unique IDs and name for server-side parsing
newClone.attr('id', 'clone_' + newCloneCount);
newClone.find("input[id^=clone_]").each(function() {
$(this).val('').attr('name', ($(this).attr('name').substr(0,7)) + newCloneCount);
});
// .. and finally insert it after the last fieldset
newClone.insertAfter("#clone_" + cloneCount);
event.preventDefault();
});
This would not only clone and clean the set of input fields, but it would also set new ID's and names so once the form is posted, their values would not be overwritten by the last set.
Also, in case you want to add the option of removing sets as well (one might add too many by mistake, or whatever other reason), having them wrapped in a <fieldset> that has an unique ID will help in accessing it and doing a .remove() on it.
Hope this helps.